Category Archives: Blog

The official Vocanic blog

Switch Your Sessions to Redis (Without Downtime)

By default PHP’s session management is to store them in temporary files on the file system. This doesn’t scale up if you are developing a web application to run on multiple machines. You could set up your load balancer to have sticky sessions – where a session is associated to a machine – but the major drawback here is to have sessions become “bad” when that machine is out of rotation – primarily due to an error.

The typical solution, then, is to store the PHP sessions in Database, or some such store. We, at Vocanic, started off by storing the sessions in MySQL, but then as our traffic went up, scaling that instance of MySQL became a headache and so we replaced it with a MySQL+Memcache solution, which was good enough for quite a while.

Recently, we saw a bigger problem – MySQL tends to be blocking if you are doing edits, and if the MySQL instance is shared between Sessions and any other data, then any major edit operation on the other databases also slows down the Sessions code and eventually blocks all the users out – since Sessions is the first module that needs to be loaded up for all of this to work.

So, we wanted to a non-blocking, fast, scalable store for our session management. These days, I am personally going through a Redis-Admiration phase and hence I decided to migrate this code to use Redis.

(Src: http://rethinkdb.com/blog/thanks-redis/)

The pseudo code for this is something like this:

class RedisSessionHandler implements SessionHandlerInterface {

public function open($save_path, $name) {
// Set up your Redis Connection
}

public function close() {
/* */
return true;
}

public function read($id) {
// GET '$id' // unserialize and return
}

public function write($id, $data) {
$wrapper = new stdClass();
$wrapper->id = $id;
$wrapper->access = time();
$wrapper->data = $data;
// SET '$id' serialize($wrapper);
// ZADD 'ALL_SESSIONS' $wrapper->access, $id
}

public function destroy($id) {
// DEL '$id'
// ZREM 'ALL_SESSIONS' $id
}

public function gc($maxlifetime) {
$old = time() - $maxlifetime;
// $keys = ZRANGEBYSCORE 'ALL_SESSIONS', 0, '($old'
foreach($keys as $id) {
$this->destroy($id);
}
}
}

$sessionDataHandler = new RedisSessionHandler();
session_set_save_handler($sessionDataHandler, true);

// Send any headers you want to

// Rest of the code

While this will just work for a new application, we wanted to ensure that your current sessions are migrated – so we wrote a compound session handler that wrote to both old session handler and the new session handler and compared the read results to ensure that we weren’t seeing anything inconsistent.

Once we ran it for a few cycles of “$maxlifetime” – ours was set at 1440 seconds, we were sure that the new handler was working as expected. Once that happened, we replaced it with code that was just the Redis Handler and we had the entire system shift to Redis.

At the moment, my nascent profiling is showing me that we are saving somewhere close to about 30ms savings in page views that uses Sessions.