Migrating Sessions in PHP
We just finished migrating our PHP Sessions from the default PHP session save location (file) to a distributed system (Redis) and I wanted to share our experience.
As always, I’ve made every effort to test this but mistakes happen and we take no responsibility for any problems this may cause. Make sure you manually test this on your application BEFORE you deploy it to your live server. The risk of data loss/annoyed customers is very real with this process. I ran over the process several times before deploying the results.
Default Configuration
By default, PHP stores it’s session information to disk. For most applications this is fine because you only have one server. Problems occurs when you try to add a second or even third server. Storing the session information to disk can cause people to be randomly logged out of your site as they are switched between servers. Moving the session data to a system where all the servers can access the information allows the user to seamlessly switch between servers.
Why Redis
When we started looking at options for this change several options came up.
We discarded MySQL early because although we already have it setup there are some performance problems with storing sessions in a database. We’re not sure at what level these become a problem but we didn’t want to tempt fate. :-)
We tried using memcached for the session storage but we found it difficult to reconfigure the settings when we added/removed nodes. Memcache also doesn’t persistent to disk so if a node goes down it starts with a blank slate and can cause people to loose data.
Redis was a good choice because it saves data to disk every so often (depending on your settings) and if a node crashes it will automatically recreate it’s data from the master node. Redis allows you to set an expiration for the keys so PHP doesn’t have to do garbage collection. We’re also already using Redis for Resque so that make it an easy choice. :-)
I should point out that there are other options to solve this problem (glusterfs to replicate disk based data, other in memory solutions, etc) so this is not an exhaustive list.
The Plan
The goal in this process is to not log anyone out. You could easily make this change by forcing all of your users to log back in but we wanted to provide the least number of problems for our customers. I’ve accidentally logged everyone out before (accidental rm
in the wrong directory) and it caused a huge number of calls to our support line because people lost information they were entering into a form.
To this end, we’re going to slowly transition people from disk to in memory storage using the following steps:
- Save to disk and Redis but read from disk
- Perform bulk migrate
- Read just from Redis
- Save just to Redis
New Session Handler
In order to create your own session handler, PHP requires you create a class that implements the SessionHandlerInterface. Below is an implementation for this class and if you look at the SessionHandlerInterface PHP documentation you’ll actually see that most of this file is just a copy/paste of their example. Several of the functions (open, close, gc) don’t actually do anything in the Redis case but they need to return true;
.
We’re also using the Credis Library to make working with Redis easier.
In your PHP code you need to tell PHP to use the new session handler.
We also setup Redis Sentinel so if a node goes down we have automatic fail over. Credis is nice enough to provide an easy way to interact with Redis Sentinel as if it were a single server so we can use the same RedisFileSessionHandler
class and just pass the connection to the cluster.
Testing and Switch Off Disk
Now that we have our application writing to both Redis and disk we can test it to make sure it doesn’t cause any performance problems. I recommend using New Relic for this and I would highly recommend you wait at least a day to make sure nothing horrible happens. While we were testing we found a spot in some older code that was serializing a class into the session and then it was never unserialized. The serialized version of the class was hundreds of megabytes for some users so we removed the call to the serialization and it was easily fixed.
Bulk Migrate
So now that we have our application writing to Redis it’s time to migrate our user’s data over. There are two ways we can do this. The first is to allow it to happen organically. If you expire sessions after a short period of time this might be an excellent way to go. However, if you can’t wait that long you’ll need to run a migration script:
Switch Over
Now that our user’s sessions have been migrated we can switch over to reading from Redis:
Finally, we’re going to make a small change to the write function so it doesn’t save to disk anymore.
Conclusion
This process worked really well for us and was easy to implement once we figured everything out. Let us know in the comments if this has helped you.
Scott Keck-Warren
Scott is the Director of Technology at WeCare Connect where he strives to provide solutions for his customers needs. He's the father of two and can be found most weekends working on projects around the house with his loving partner.
Top Posts
- Working With Soft Deletes in Laravel (By Example)
- Fixing CMake was unable to find a build program corresponding to "Unix Makefiles"
- Upgrading to Laravel 8.x
- Get The Count of the Number of Users in an AD Group
- Multiple Vagrant VMs in One Vagrantfile
- Fixing the "this is larger than GitHub's recommended maximum file size of 50.00 MB" error
- Changing the Directory Vagrant Stores the VMs In
- Accepting Android SDK Licenses From The OSX Command Line
- Fixing the 'Target class [config] does not exist' Error
- Using Rectangle to Manage MacOS Windows