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

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:

  1. 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.
  2. 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

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.


Like this article?

   Then Digg it!
Or use another bookmark button below to show your support &
help me spread the word.


tags: php, performance, mysql, PECL, disk IO, memcache
category: How to - Webserver
read: 3,020 times

Add comment

(required, shown)(required, not shown)for syntax highlighting

[CODE="Javascript"]
your_code_here();
[/CODE]

Replace "Javascript"
with "php", "text", etc.
code (to make sure you are not a spammer)

Comments

#6. Kevin (link) on 27 August 2008

Member avatar: KevinThanks guys, I've updated the article. Martin, have you fixed your problem yet? More verbose results will be helpful.

#5. martin on 11 August 2008

Default avatar:martinYaa101....i already solve the problem.it appear that i forget to add extension=memcache on my phpini. but now i found the problem where im trying to make another webserver to save it session in memcache server that i've been done. the error code is the application simply cant save session.

i already done these steps:
aptitude install php5-memcache
pear install pecl/memcache
... [more] Enable memcache session handler support? [yes] : yes
extension=memcache.so
session.save_handler = memcache
session.save_path = "tcp://172.19.3.78:11211" (memcache server ip)
/etc/init.d/apache2 restart

any idea about what i've missed to do this time?

#4. Yaa101 on 06 August 2008

Default avatar:Yaa101@martin,

The first message is because it can't find the memcache demon, checkout if it is running, further checkout what port it assigns itself in the runlevel startup script.

The 2 messages after are caused by the first message, if you solve the first, these will go away too.

#3. martin on 01 August 2008

Default avatar:martinim trying to implement memcache on our intranet with single login for several webapplication.i follow your method step by step and i found no problem in setting it up. but when i try to start using it it show error in every application. here's example error message on my phppostgreadmin


Notice: session_start() [function.session-start]: Server 172.19.3.78 (tcp 11211) failed with: Connection refused (111) in /var/www/phpPgAdmin/libraries/lib.inc.php on line 56

... [more] Warning: session_start() [function.session-start]: Cannot send session cookie - headers already sent by (output started at /var/www/phpPgAdmin/libraries/lib.inc.php:56) in /var/www/phpPgAdmin/libraries/lib.inc.php on line 56

Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent (output started at /var/www/phpPgAdmin/libraries/lib.inc.php:56) in /var/www/phpPgAdmin/libraries/lib.inc.php on line 56

could you help me figure what's wrong?
thanks alot before

#2. Craig on 31 July 2008

Default avatar:CraigI ran some tests. Using memcache is non-locking, so you have to consider that when creating your application.

#1. Craig on 30 July 2008

Default avatar:CraigQuestion: So by enabling Memcache Sessions using method 2, does php retain its session locking mechanism?

As you may be aware, when two requests from the same session id request access to the file based session, php queues the second request until the first request has closed the session. This prevents process race conditions from corrupting session information.

For example, in a non-locking session (such as using a custom database handler), if request A started executing first, then request B started executing. They both got the session value for Authenticated which equaled False.
... [more]
Request A changed the value to True, but request B left it set to false.

If Request A finished first it would write the Authenticated equals true to the session. When request B finished it would then write Authenticated equals false back to the session (overwriting any changes made by request A). Authenticated would then equal false, when you were expecting it to equal true.

File based sessions would lock the session when request A was using it, preventing request b from executing until it had finished. Then request B would start, get the session request A wrote, where Authenticated is equal to True. Any your web app would execute as expected.

So do you know If PHP locks sessions when you tell it to use memcache sessions instead of file sessions?