kvz.io
Published on

Enhance PHP Session Management

Authors
  • avatar
    Name
    Kevin van Zonneveld
    Twitter
    @kvz

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 retrieve them later on. The storage of the variables is done in RAM. So it's lightning 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 its 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 you, 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 flexibility. 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 its sessions (effectively 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 experiment and review the documentation. You will learn that Memcache can enhance your serverside performance dramatically.

Legacy Comments (31)

These comments were imported from the previous blog system (Disqus).

Craig
Craig·

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

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?

Craig
Craig·

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

martin
martin·

im 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

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

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

martin
martin·

Yaa101....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
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?

Kevin
Kevin·

Thanks guys, I\'ve updated the article. Martin, have you fixed your problem yet? More verbose results will be helpful.

martin
martin·

hey..kevin. glad that you finally done with your moving. hows life in new place btw? :D
im still cant solve my problem. since there isnt much articles about memcache as detail as yours, i got stuck and decide to pending this memcache project for a while.

so...can you give me a clue? my webserver simply cant save its session in main memcache webserver. thanx in advance kevin.

Kev van Zonneveld
Kev van Zonneveld·

@ martin: Home is still not finished, just made some time for my blog ;) thanks though, we\'re getting there.
Concerning memcache: Verbose output would be very helpful. Viewing the phpinfo(); output and determining whether there is memcache support can be a good test. Others tests may be to try and open a telnet connections from the \'malfunctioning\' webserver and access the memcache port directly. Let me know when you have more details

Artur Ejsmont
Artur Ejsmont·

Well i think you dont have to worry about memcache making data inconsistent in your session like in case of files as memcache sets are atomic.

What you have to worry about is sequence of session updates. Data stored in session by one request could be overridden by another request.

Having your own handler you could get data just before saving again to minimize that risk of dirty update.

Kev van Zonneveld
Kev van Zonneveld·

@ Artur Ejsmont: Since memcache doesn\'t support locking, e.g. ajax calls may indeed cause an unexpected sequence of saves.

You can make it locking:
http://www.slideshare.net/f... (slide 59)

But this would require writing your own sessions hander (instead of using the .ini default memcache handler) and sacrificing some performance.

Manuel de Ruiter
Manuel de Ruiter·

You got some very nice articles on your blog, my compliments about that! Only there are some updates about memcache, memcache now supports (session) locking ([url=http://pecl.php.net/package...]Clicky[/url]).

By the way, you got a dead link in your article, namely: \" Feel free to expiriment and [u]review the documentation.[/u]\".

Kev van Zonneveld
Kev van Zonneveld·

@ Manuel de Ruiter: Thanks a lot for bringing that to my attention Manuel, I\'ve updated the article. Turns out this is stuff is now too good to be true ;)

Leenix
Leenix·

If your running a cluster of webservers, or dont want to lose session data on a memcache restart, take a look at the latest features memcache/php ext offer for mirroring session data on 2+ memcache servers.

Found a nice description here;
http://phpslacker.com/2009/...

Kev van Zonneveld
Kev van Zonneveld·

@ Leenix: Thank you for sharing!

Zeeshan Zakaria
Zeeshan Zakaria·

Hi Kevin,

You have wonderful articles here and I am now going to tweak my server to enhance its performance.

There are three things however which I wanted to ask:

1. Regarding memcache, my CentOS server has a file in /etc/php.d/ and it is memcache.ini. There are many other ini files here too. The memcache.ini is as follows:

; Enable memcache extension module
extension=memcache.so

; Options for the memcache module

; Whether to transparently failover to other servers on errors
;memcache.allow_failover=1
; Defines how many servers to try when setting and getting data.
;memcache.max_failover_attempts=20
; Data will be transferred in chunks of this size
;memcache.chunk_size=8192
; The default TCP port number to use when connecting to the memcached server
;memcache.default_port=11211
; Hash function {crc32, fnv}
;memcache.hash_function=crc32
; Hash strategy {standard, consistent}
;memcache.hash_strategy=standard

; Options to use the memcache session handler

; Use memcache as a session handler
session.save_handler=memcache
; Defines a comma separated of server urls to use for session storage
session.save_path=\"tcp://localhost:11211?persistent=1&weight=1&timeout=1&retry_interval=15\"

Now there is also a file /etc/php.ini with session.save_handler = files and a path for the files, but these files are never created. This mean s that somehow the settings in the memcache.ini take precedence over the settings in php.ini file, or maybe php.ini file is never read and only the *.ini files in php.d folder are read. Do you have any comments on what is happening here?

2. For a page where the data doesn\'t change frequently, I am caching it using simple PHP techniques, i.e. doing ob_start() and ob_get_contents(). Is it fine or the method you explained is faster?

3. The main page of my website which is updated as the users post comments, I tried a tricky way to cache it and update the cache file only when a new comment is added, and so far it didn\'t work right, however I plan to try it again later sometime. But I have noticed that if a lot of visitors come to my website at once, e.g. 100 users in a few minutes, I see in the log that this many users hit the server, but don\'t see that this many sessions were initiated or pages were served. I think this is because that apache and mysql gets overloaded with requests and can\'t serve all the users. I don\'t have a super server, just an average Dell server. Do you think I am right here?

Thanks again for the wonderful knowledge you shared here.

Kev van Zonneveld
Kev van Zonneveld·

Both can be useful, but keep in mind that saving sessions in memcache is totally different from saving content in memcache.

Jeff
Jeff·

Hi Kevin,

I\'m using memcache to store user sessions, it works well most of time , but it will throw exceptions when the connection to cache server is failed . have you come across this problem ? how to solve this kind of problem ?

The exceptions looks like below,

Error #8 Memcache::get() [<a href=\'memcache.get\'>memcache.get</a>]: Server 10.10.24.9 (tcp 11212) failed with: Connection timed out (110)

Error #8 Memcache::get() [<a href=\'memcache.get\'>memcache.get</a>]: Server 10.10.24.9 (tcp 11212) failed with: Failed reading line from stream (0)

Thanks,
Jeff

Kev van Zonneveld
Kev van Zonneveld·

@ Jeff: Well, fix the connection errors : ) If it\'s because you\'re restarting memcache, consider sending it a flush command instead.
If memcache is dead, sessions won\'t work and it\'s totally in order to get some errors.
Wether they should be shown to your customers is a second; but you can control that in your php.ini

Tom
Tom·

Hi Kevin,

Great article, what do you think about using a nosql solution such as Casandra or MongoDB instead of Memcache?

As far as I understand it (disclaimer: I dont understand it that well), a nosql solution would seem to be a good match here as it scales well horizontally and dosent have a lot of the traditional overheads of a RDBM system.

I'd be interested to hear your thoughts?

David
David·

I understand how memcache can be used across load-levelled servers, but is it possible to share sessions between url's?
I have a server running 2 vhosts, say api.mydomain.com and stats.mydomain.com - I'd like to use memcache to share sessions as you pass between these url's. Is this possible, or do I need to go down the manual, database route?

Serge Rivest
Serge Rivest·

Great article; clear and concise. Installed memcached in a flash. Thanks!

Mostafa
Mostafa·

Thanks for this wonderful article! it saved me a lot of time and trouble searching around the net :)

Kev van Zonneveld
Kev van Zonneveld·

@ David: Sure, it's important to get your cookie settings right though.

@ Tom: Memcache doesn't have a lot of overhead either. The solutions you mention are more persistent. Something that isn't a requirement per se for sessions.
Besides, support for session storage to memcache comes out of the box with PHP.

Eric Marden
Eric Marden·

@Tom: For one, memcache support is built in.

Suresh Kamrushi
Suresh Kamrushi·

Good article. I have used memcache long time before but after reading this it seems i remember everthing cleary and understand few new usage of Memcache.

Thanks Kevin...

Sam
Sam·

If you're using just a single server it's worth checking APC as session backend. It performs significantly better than memcached.

aditya menon
aditya menon·

This is one of the best and the most informative articles I've read in a long time. You packed a great deal of knowledge in the few paragraphs above. Thanks a lot!

I heard the word 'memcache' many times, but I always thought it's some zany esoteric concept like xPath. Whoah, I never thought it was something THIS awesome! Directly accessing the RAM from my scripts! FTW!

Daniel Obando
Daniel Obando·

Amazing and so well explained post. Helped me tremendously and opened the gateway to a whole new world of possibilities and performance on web development.

Thanks!

Stefan
Stefan·

Scache (http://scache.nanona.fi) addresses some of memcached's shortcomings and is persistent storage.

But still in beta.

geeta
geeta·

Amazing and so well explained post. Helped me tremendously and opened the gateway to a whole new world of possibilities and performance on web development.

Kelly Jones
Kelly Jones·

Awesome post. Here’s a tool that lets your build your online database without programming. There is no need to hand code PHP. Cut your development time by 90%
http://www.caspio.com/