Session Locking in PHP with Memcached
During some cache testing I was doing in Magento this past week I ran into something that at first looked like a bug, but really just turned out to be a limitation of PHP Sessions. It isn’t immediately obvious so I thought I’d share.
I’ve been using the profiler built into Magento as well as New Relic to find the parts of the application that are slow and they have both produced a lot of good information. To test everything, I’ve been opening up dozens of tabs on product and category pages, waiting for them to load and then going into New Relic to see what parts of the store was slow. I noticed that more often than not all of my tabs would just hang there for a long time and then suddenly all load within seconds of each other.
As it turns out, it was taking up to 30 seconds (80% of the page load time) just to create the session!
session_start();
A quick primer on session storage
By default, PHP stores session data on the server file system and stores the session key on the browser in a cookie named PHPSESSID. Because file access is slow and leaves the session information stranded on a single server we are storing session information on a central memcached cluster that all of our application nodes can access.
It took me some time to track down the problem, but it seems to be a case of session locking. Essentially, once a session is opened in a request it cannot be accessed again by anyone else until it is released. This is implemented to prevent data loss, as all of the session data is written when the session is closed.
Example:
- Request A is opened, retrieving all saved session data
- Request B is opened, retrieving all saved session data
- Request B is closed, saving all session data changes.
- Request A is closed, saving all session data changes.
In this example, its as if Request B never existed, because Request A saved all the data as it was originally retrieved plus any modifications made with that request.
Anyway, session locking is important. The unfortunate thing is that if you go back to our example above and Request A takes a full 30 seconds to generate a page, any requests that come after that will have to wait until the session is unlocked before they can proceed.
Okay, last piece of the puzzle
Unfortunately, there are still things on the interwebs that tell you memcached does not support session locking. Just so you know that was added in 2009 and does, in fact, play a role in performance.
So, now that you know all of that, don’t try to open up 50 different products at the same time and expect them to load independently or to accurately reflect how your servers will perform under load. It doesn’t work that way.