Thursday Nov 19, 2009

PHP: session.gc_maxlifetime vs. session.cookie_lifetime

PHP and sessions: Very simple to use, but not as simple to understand as we might want to think.

session.gc_maxlifetime

This value (default 1440 seconds) defines how long an unused PHP session will be kept alive. For example: A user logs in, browses through your application or web site, for hours, for days. No problem. As long as the time between his clicks never exceed 1440 seconds. It's a timeout value.

PHP's session garbage collector runs with a probability defined by session.gc_probability divided by session.gc_divisor. By default this is 1/100, which means that above timeout value is checked with a probability of 1 in 100.

session.cookie_lifetime

This value (default 0, which means until the browser's next restart) defines how long (in seconds) a session cookie will live. Sounds similar to session.gc_maxlifetime, but it's a completely different approach. This value indirectly defines the "absolute" maximum lifetime of a session, whether the user is active or not. If this value is set to 60, every session ends after an hour.

Friday Nov 13, 2009

Little-known PHP commands: scandir()

Always messed around with a combo of opendir(), readdir(), and closedir() if you wanted to read the contents of a directory? Since PHP 5 there is a new sheriff in town: scandir():

<?php   
	$files=scandir("/etc/php5");
	print_r($files);
?>

Outputs:

Array
(
    [0] => .
    [1] => ..
    [2] => apache2
    [3] => conf.d
)

Okay, you still need to traverse an array, but it's much easier to use than the traditional way.

Wednesday Nov 04, 2009

Store PHP sessions in memcached

My last week blog topic was very much marked by Apache load balancing. Well, I promised to leave this topic alone for a while, but there is one related topic that is worth spending a minute on.

The Theory

If your web application is distributed across multiple servers you'll quickly run in sessions problems because each backend server (aka worker) usually stores its session informations locally. Now, if subsequent HTTP requests are handled by different workers, every time a new sessions is created or, even worse, sessions getting mixed up.

To overcome this problem there are two solutions:

  1. Use a session-aware load balancer that binds a user session to the same worker.
  2. Or keep all session data in a central storage.

Both solutions have the similar drawback: if a worker goes down, all session data of this worker are lost. If the central storage goes down, all sessions are lost. But consider the following: you'll probably have tons of workers, and since every computer is supposed to fail after a specific period of time, the probability of a worker failure is much higher than for a single storage server. It depends on what do you want: A system that runs all the time with small failures or a system that fails completely from time to time?

And finally, losing session data sounds worse than it actually is: usually the users only have to login again to restore their session data. That's sad, but it's not the end of the world. Okay, your system may get into trouble if thousands of users try to re-login at the same time, but that's another problem.

The Solution

My favorite solution is the second one: keep all session data in a central place. And in this scenario I'll use Apache/PHP as my "application server" and memcached as central storage for my session data. If you read and still remember the title of this post, you're probably not surprised.

phpmemcached.jpg

On the left: my load balancer, in the middle my worker farm, and on the right: my single and central memcached server. By the way: You can also have multiple memcached servers, but for this blog post I'll keep it simple.

The Requirements

First, let's check if PHP was build with memcached support:

serverA ~% php -m | egrep memcache
memcache

...on each worker node: serverA to serverD.

Second, I check if memcached is running on serverM:

serverM ~% ps -efa | egrep memcached
oswald  1543     1   0 15:21:17 ?         0:00 /home/oswald/webstack1.5/lib/memcached -d ...

Perfecto.

The Configuration

Now I need to change the PHP configuration on each worker node: Open php.ini on serverA to serverD and search for these lines:

[Session]
; Handler used to store/retrieve data.
session.save_handler = files

And change the configuration like this:

[Session]
; Handler used to store/retrieve data.
session.save_handler = files
session.save_handler = memcache
session.save_path = "tcp://serverM:11211"

Make sure that the settings are the same on all your workers.

That's all. Yes, that's the basic configuration. PHP's sessions will now get stored on the memcached node serverM. No more magic needed.

The Proof

But as we say in Germany: "Prudence is the mother of the china cabinet." Before we can grab the beer, we should make sure everything works as we expect it to.

I put this code in a file named session.php in the document root directory of all my worker nodes:

<?php   
	session_start();
	if(isset($_SESSION['zaphod']))
	{       
		echo "Zaphod is ".$_SESSION['zaphod']."!\\n";
	}       
	else    
	{       
		echo "Session ID: ".session_id()."\\n"; 
		echo "Session Name: ".session_name()."\\n";
		echo "Setting 'zaphod' to 'cool'\\n";
		$_SESSION['zaphod']='cool';
	}       
?>

From the outside I use lynx to access this file:

% lynx -source 'http://serverA/session.php'
Session ID: df58bc9465f27aa20218c11caba6750f
Session Name: PHPSESSID
Setting 'zaphod' to 'cool'

A new session with the ID df58bc9465f27aa20218c11caba6750f was created and PHP uses the session name PHPSESSID to identify the session parameter. And the session variable zaphod was set to the value cool.

Now I add the session information PHPSESSID=df58bc9465f27aa20218c11caba6750f to my URL and rerun the new lynx command:

% lynx -source 'http://serverA/session.php?PHPSESSID=df58bc9465f27aa20218c11caba6750f'
Zaphod is cool!

Yes, I got the expected output: Zaphod is cool! Proving the session data is available on serverA. But that's not a big surprise, what's about the other nodes? I replace serverA with serverB in my URL:

% lynx -source 'http://serverB/session.php?PHPSESSID=df58bc9465f27aa20218c11caba6750f'
Zaphod is cool!

Bingo, serverB also has the same session data as serverA.

And for serverC? It's also the same:

% lynx -source 'http://serverC/session.php?PHPSESSID=df58bc9465f27aa20218c11caba6750f'
Zaphod is cool!

And so on... for each worker node the session data will be the same.

A dream came true.

About

Kai 'Oswald' Seidler writes about his life as co-founder of Apache Friends, creator of XAMPP, and technology evangelist for web tier products at Sun Microsystems.

Search

Archives
« November 2009 »
SunMonTueWedThuFriSat
1
2
3
6
7
8
9
10
11
12
14
15
16
17
20
21
22
23
24
25
26
27
28
29
30
     
       
Today