Enhance PHP Session Management

In PHP, sessions can keep track of authenticated in users. They are an essential building block in today's websites with big communities and a lot of user activity. Without sessions, everyone would be an anonymous visitor.

In system terms, PHP sessions are little files, stored on the server's disk. But on high traffic sites, the disk I/O involved, and not being able to share sessions between multiple webservers make this default system far from ideal. This is how to enhance PHP session management in terms of performance and shareability.

Update

I've turned around on this subject and recommend reading Revisiting Faster PHP Sessions instead.

Session sharing in web clusters

If you have multiple webservers all serving the same site, sessions should be shared among those servers, and not reside on each server's individual disk. Because once a user gets load-balanced to a different server, the session cannot be found, effectively logging the user out.

A common way around this is to use custom session handlers. Writing a class that overrules default behavior and stores sessions in a MySQL database.

Sessions in Database

All webservers connect to the same database and so, as soon as www01 registers a session (insert in a sessions table), www02 can read it. All servers can now see all sessions: problem solved?

Yes, and no. This sure is functional and tackles the shareability issue. But databases seem to be the biggest bottlenecks of web clusters these days. They are the hardest to scale, and so in high traffic environments you don't want to (ab)use them for session management if you don't have to. We have to tackle the 'performance' issue.

Database memory

Memory is about 30 times faster than disk storage. So storing our sessions in memory somehow, could deliver great performance.

MySQL query caching

One form of using database memory is the standard MySQL query caching. But MySQL query caching isn't very effective because it invalidates all cache related a table, if only one record in that table is changed.

Of course the session table is changed all the time, so the session cache is purged all the time, rendering it quite useless for our purposes.

Heap tables / Memory tables

We're really closing in to our goal now. Storing the sessions in a heap/memory table (a table that lives in your database server's RAM) speeds up things greatly. Many demanding sites have opted for this solution.

In my eyes however, it's still not optimal. Because it still requires a lot of additional queries that your database server(s) shouldn't necessarily have to process.

One other possible solution is using Memcache. And you will find it's easier to setup and has a smaller footprint than most alternatives. For one thing, because you will not have to code custom session handler classes in PHP. Memcache session support comes native.

Memcache

Memcache is a little program originally written by Live Journal. It's quite straight forward: It reserves some memory, opens a socket and just stays there.

We can connect to the socket and store variables in it, and later retrieve them later on. The storage of the variables is done in RAM. So it's lighting fast ; )

Memcache is used for caching a lot things: function results, entire html blocks, database query results. But now we're going to use it to store our site's user sessions.

Architecture

From system point of view, Memcache looks a lot like MySQL. You have a:

  • Server

Where information is stored. Should be running at all times.

  • Client module

Interface to save & get information from the server.

It's integrated in our programming language.

There is one important difference though. If the Memcache server is shut down, the information in it is lost. So remember to use memcache as a cache only. Don't store information in it, that can't be retrieved in some other way. For sessions this is a risk I'm willing to take. Worst case scenario is that my users will be logged out. If you cannot live with this, you could combine database & memcache session handlers. Database will be the safe storage, memcache will be in front of it for performance. If it crashes, you will only lose performance, and not the data.

Installing a Memcache server

For session sharing, use a centralized server. If you only have one webserver, it still makes sense to use Memcache from performance point of view. Just limit it's maximum allowed memory size to 64MB (depending on your server & wishes), and use the localhost (127.0.0.1) to connect to it.

If you don't have a Memcache server already, you can install it very easily with package management. I use Ubuntu so in my case that would translate to:

$ aptitude install memcached

Adjust the settings in /etc/memcached/memcached.conf. In my case the defaults were OK, I only increased the max allowed memory, and allowed more hosts to connect to it.

Now let's spawn the Memcache Daemon (yes that's what the 'd' stands for):

$ /etc/init.d/memcached start

Done, we're ready to use it... But how?

Installing a Memcache 'client'

You could just open a socket and go talk to Memcache, but that would eventually cause headaches. So there is a standard PHP module we can use that does a lot of work for us, and allows us to talk object oriented to it. This works much like installing a MySQL module. If it's in your distro's package management, good for your, let's:

$ aptitude install php5-memcache

If not, no problem. Make sure you have pecl available and:

$ pear install pecl/memcache

(I used 'pear' and not directly pecl to circumvent the bug that caused a Fatal error: Allowed memory size of 8388608 bytes exhausted).

And please choose:

Enable memcache session handler support? [yes] : yes

You must enable PHP to use the memcache.so module we now have. So please add a

extension=memcache.so

to your php.ini (usually located at /etc/php5/apache2/php.ini)

Great, we have all the prerequisites to start Memcaching!

Sessions in Memcache

PHP allows you to overrule the default session handler in two ways:

  • session_set_save_handler(). By programming your own session handlers, allowing you to virtually use any type of storage, as long as you can read/write to it from PHP. This example uses a MySQL database. We could also use this method to connect to Memcache.
  • session.save_handler. By specifying one of the default handlers in the php.ini file using the session.save_handler & session.save_path directives.

Option 1 allows greater flexibiliy. And it even allows you to create a combined database/memcache mechanism. Resulting in a fallback-on-database in case memcache goes offline and loses all of it's sessions (effictively logging out all users).

Option 2 is very easy to implement, doesn't require changing your existing code, and is the one I'm going to show you today.

session.save_handler

Assuming that you have one webserver, and installed the Memache server on that same machine, the hostname will be 127.0.0.1. If you have it on a different server, you will know what IP to substitute it with.

session.save_handler = memcache
session.save_path = "tcp://127.0.0.1:11211"

Done! Huh? what just happened?

Well, because we enabled Memcache session handler support, all work is done for us. PHP will now know not to use the default files handler so save session files in /var/lib/php5 but uses memcache running at 127.0.0.1 instead.

Don't forget to restart your webserver to activate your changes to the php.ini

$ /etc/init.d/apache2 restart

The Catch

Update - As Manuel de Ruiter says in the comments, the following is no longer true thanks to some updates

As with anything too cool, there is a catch: Locking. The standard PHP Session module locks the whole session until the request finishes. Memcache is build for speed and as a result, does not support this kind of locking. This could lead to problems when using frames or ajax. Some actions may request a session-variable before it's actually saved.

Further reading

What we have just done with Memcache is the low-hanging fruit. We've enabled RAM sessions with minimum effort and without changing even one line of your existing code.

But now that you have memcache running, you might want to use it for storing other often-used, rarely-changed variables as well. Feel free to expiriment and review the documentation. You will learn that Memcache can enhance your serverside performance dramatically.