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.
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.
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 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
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.
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:
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.
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.