Posts Tagged ‘xcache’

A Funny Thing Happened on the way to 100% Full tmpfs

I’ve been having trouble with XCache’s datastore lately and was prompted to revisit my SHM/tmpfs File-Based PHP Cache/Datastore in RAM strategy.

I mounted a 256MB tmpfs slice like this:

none                    /mnt/ram        tmpfs           defaults,noatime,size=256M      0 0

Somehow, after the storage was exhausted the number of files kept increasing.

$ watch "ls | wc -l"

To my surprise new, empty files were being created with a random string attached to their name.

36588.posts.object.ychanKJYGF
36588.posts.ychanPIUGN

Interestingly, while these non-files continued to accumulate the reported amount of storage used by tmpfs never changed. One might expect these file entries to be taking up some amount of space, somewhere.

I’ve done as much research as time will permit but haven’t found the purpose of this behaviour. Since the altered file name and lack of content makes these files void as far as my fcache implementation is concerned it highlights the importance of thorough garbage collection.

SHM/tmpfs File-Based PHP Cache/Datastore in RAM

It seems like only yesterday XCache was my knight in shining armour but a burst of segfaults has prompted the creation of a backup plan.

UPDATE It turns out my problem was actually PHP’s fault. Put that armour back on!

We can use files on tmpfs to provide much the same function as XCache or APC’s shared datastore. Start by mounting a slice somewhere appropriate (this line is for fstab):

none                    /mnt/ram        tmpfs           defaults,noatime,size=256M      0 0

Next we’ll create some basic interface functions. Let $config['fcache_path'] be the path to your tmpfs mount or writeable directory:

function fcache_isset($key)
{
	global $config;
	return @file_exists($config['fcache_path'].$key);
}

function fcache_unset($key)
{
	global $config;
	return @unlink($config['fcache_path'].$key);
}

function fcache_get($key)
{
	global $config;
	$val = @file_get_contents($config['fcache_path'].$key);
	if(empty($val))
		return NULL;
	else
		return $val;
}

function fcache_set($key, $val='')
{
	global $config;
	if(!empty($val))
	{
		$tmp = tempnam($config['fcache_path'], $key);
		if(@file_put_contents($tmp, $val))
		{
			if(@rename($tmp, $config['fcache_path'].$key))
				return true;
			else
				return false;
		}
		else
		{
			return false;
		}
	}
	return true;
}

I use rename() instead of flock() to make atomic writes because according to the manual page:

On some operating systems flock() is implemented at the process level. When using a multithreaded server API like ISAPI you may not be able to rely on flock() to protect files against other PHP scripts running in parallel threads of the same server instance!

They mention IIS’ ISAPI specifically but I’ve had enough problems with Apache’s mpm_worker lately that I’m not willing to take the risk. Further, I’d rather have the query run twice than have any lock-related hangups.

Now that we have some very basic functions to interface with we can put them to work in something useful. The following is what I’ve whipped up to switch between XCache, this file-based cache and no cache at all when pulling standard mysql results. cache_set() could easily be replaced with cache_unset() preceeding every update query but I do things this way to make the code more readable to me. You can also increase performance by using only arrays instead of converting between arrays and objects but this software was written entirely using mysql_fetch_object() and the caching was an afterthought.

Let $config['cache'] contain the cache type.

function cache_get($key, $query)
{
	global $config;
	if($config['cache'] == 'xcache' and function_exists('xcache_get'))
	{
		$serialized = xcache_get($key);
		if($serialized != NULL)
		{
			$unserialized = unserialize($serialized);
			$object = (object) $unserialized;
			return $object;
		}
		else
		{
			$result = mysql_query($query);
			if($result === false)
				return false;
			if(mysql_num_rows($result) > 0)
			{
				$object = mysql_fetch_object($result);
				$array = (array) $object;
				$serialized = serialize($array);
				xcache_set($key, $serialized);
				return $object;
			}
			else
			{
				return true;
			}
		}
	}
	elseif($config['cache'] == 'fcache' and function_exists('fcache_get'))
	{
		$serialized = fcache_get($key);
		if($serialized != NULL)
		{
			$unserialized = unserialize($serialized);
			$object = (object) $unserialized;
			return $object;
		}
		else
		{
			$result = mysql_query($query);
			if($result === false)
				return false;
			if(mysql_num_rows($result) > 0)
			{
				$object = mysql_fetch_object($result);
				$array = (array) $object;
				$serialized = serialize($array);
				fcache_set($key, $serialized);
				return $object;
			}
			else
			{
				return true;
			}
		}
	}
	else
	{
		$result = mysql_query($query);
		if($result === false)
			return false;
		if(mysql_num_rows($result) > 0)
		{
			$object = mysql_fetch_object($result);
			return $object;
		}
		else
		{
			return true;
		}
	}
}

function cache_set($key, $query)
{
	global $config;
	if($config['cache'] == 'xcache' and function_exists('xcache_unset'))
	{
		$result = mysql_query($query);
		if($result === false)
			return false;
		xcache_unset($key);
		return true;
	}
	elseif($config['cache'] == 'fcache' and function_exists('fcache_unset'))
	{
		$result = mysql_query($query);
		if($result === false)
			return false;
		fcache_unset($key);
		return true;
	}
	else
	{
		$result = mysql_query($query);
		if($result === false)
			return false;
		return true;
	}
}

function cache_unset($key)
{
	global $config;
	if($config['cache'] == 'xcache' and function_exists('xcache_unset'))
	{
		xcache_unset($key);
		return true;
	}
	elseif($config['cache'] == 'fcache' and function_exists('fcache_unset'))
	{
		fcache_unset($key);
		return true;
	}
	else
	{
		return true;
	}
}

Please note that cache_get() checks if the returned value is NULL, it does NOT use (x|f)cache_isset() because that would introduce a serious race condition.

This implementation leaves out two important features that xcache has: garbage collection and timeouts. Garbage collection can be handled by a cron script and use of the find command to take out stale entries. Timeouts can be implemented by inserting a value into the file and comparing it against the file’s time stamp and the current time – a clever idea I got from looking over http://flourishlib.com/docs/fCache.

Will Bond’s fCache is probably what you’re looking for if you want to be able to port between all of the major datastores easily and have individual control over an item’s expiration. However, this implementation uses a rand()om number for garbage collection and may be subject to the race (or minor hangup depending on how file_put_contents() handles locking) condition we avoid here with atomic writes.

Here are some completely meaningless apache bench benchmarks against an AJAX app’s polling script on a live, production server:

Without datastore

Document Length:        105 bytes

Concurrency Level:      20
Time taken for tests:   122.880 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      2420000 bytes
Total POSTed:           2290229
HTML transferred:       1050000 bytes
Requests per second:    81.38 [#/sec] (mean)
Time per request:       245.761 [ms] (mean)
Time per request:       12.288 [ms] (mean, across all concurrent requests)  
Transfer rate:          19.23 [Kbytes/sec] received          
         18.20 kb/s sent       
         37.43 kb/s total      
 
Connection Times (ms)          
              min  mean[+/-sd] median   max   
Connect:       91  202 341.7    154    9170   
Processing:    19   43  40.3     32     746   
Waiting:       19   40  39.0     31     746   
Total:        114  245 344.1    194    9193   
 
Percentage of the requests served within a certain time (ms) 
  50%    194    
  66%    212    
  75%    224    
  80%    232    
  90%    264    
  95%    408    
  98%    480    
  99%   3170    
 100%   9193 (longest request)

XCache datastore

Document Length:        105 bytes

Concurrency Level:      20
Time taken for tests:   121.803 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      2420000 bytes
Total POSTed:           2290229
HTML transferred:       1050000 bytes
Requests per second:    82.10 [#/sec] (mean)
Time per request:       243.605 [ms] (mean)
Time per request:       12.180 [ms] (mean, across all concurrent requests)
Transfer rate:          19.40 [Kbytes/sec] received
                        18.36 kb/s sent
                        37.76 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       91  201 331.6    154    3418
Processing:    19   42  40.6     32     798
Waiting:       19   39  39.2     31     788
Total:        115  243 334.1    193    3459

Percentage of the requests served within a certain time (ms)
  50%    193
  66%    210
  75%    221
  80%    228
  90%    260
  95%    405
  98%    473
  99%   3163
 100%   3459 (longest request)

fcache

Document Length:        105 bytes

Concurrency Level:      20
Time taken for tests:   121.174 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      2420000 bytes
Total POSTed:           2291374
HTML transferred:       1050000 bytes
Requests per second:    82.53 [#/sec] (mean)
Time per request:       242.347 [ms] (mean)
Time per request:       12.117 [ms] (mean, across all concurrent requests)
Transfer rate:          19.50 [Kbytes/sec] received
                        18.47 kb/s sent
                        37.97 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       90  199 320.8    154    3407
Processing:    19   42  38.7     33     747
Waiting:       19   40  37.3     32     747
Total:        116  242 323.2    193    3486

Percentage of the requests served within a certain time (ms)
  50%    193
  66%    210
  75%    222
  80%    231
  90%    262
  95%    408
  98%    475
  99%   3161
 100%   3486 (longest request)

Interesting to see fcache narrowly beat out xcache but since the testing environment is not perfectly controlled the results are of course useless.

XCache: My PHP Opcode Cache and Datastore in Shining Armour

I recently mentioned using APC with threaded Apache and PHP was incredibly unstable. For a few days I moved back to the prefork (process-based) Apache MPM to see if it was worth bringing back APC. This eventually ended up bringing the load average on the SQL VM to about 40 with what I would normally consider an appropriate number of MaxClients and the odd spike at about 10 when restricted to 128. Page load time was also noticeably increased so I decided to give XCache a whirl (thanks to LiteStar mentioning it in the comments of that article).

XCache is a stable, and (so far it seems) genuinely thread-safe opcode cache and datastore. It is developed my mOo, a lighttpd developer and hosted under the lighttpd domain. Depsite this, it is built as a PHP extension in the same manner as APC meaning it can be used with mod_php, fastcgi etc. Like APC, XCache features shared memory variable storage functions with a simple name, value scheme in addition to the opcode cache. I was able to quickly write an SQL wrapper using these that reduced the database hits in one of my apps by a factor of about 10,000 – making virtually the whole thing run out of RAM.

Also similarly to APC, XCache comes with bundled admin scripts which – while perhaps not as pretty as APC’s – I certainly find more insightful. Some folks find it faster, but I find it works so it looks like I’ll be sold for some time.

Cheers, Litey. And cheers, mOo.

UPDATE Or not… After a few weeks of faithful service xcache finally segfaulted on me:

apache2[17395]: segfault at 332e352e ip b5ba90f7 sp a4b4cb70 error 4 in xcache.so[b5b96000+1d000]
apache2[16754]: segfault at 4320526d ip b5ba4297 sp aab5ad60 error 4 in xcache.so[b5b96000+1d000]
apache2[27959]: segfault at 43205445 ip b5ba90f7 sp aab58b70 error 4 in xcache.so[b5b96000+1d000]
apache2[15215]: segfault at 2 ip b5ba90f7 sp a534db70 error 4 in xcache.so[b5b96000+1d000]
apache2[15536]: segfault at 362e3335 ip b5ba90f7 sp ac35bb70 error 4 in xcache.so[b5b96000+1d000]

I’m doing my best at this moment to reproduce the suituation.

UPDATE It turns out this might actually be a bug in PHP. mOo was kind enough to respond in detail:

A PHP 5.3 bug No.62432 ReflectionMethod random corrupt memory on high
concurrent was fixed that cause PHP itself unstable, bogus reported as
XCache bug. It is false positive to reproduce with XCache
loaded/enabled only, just because XCache make PHP run faster than
with/without other opcode cacher. PHP up to 5.3.14 is affected. It is
highly suggested to upgrade to newer PHP

https://bugs.php.net/bug.php?id=62432

so, upgrade to 5.4 if applicable. upgarde to
http://xcache.lighttpd.net/wiki/Release-2.0.1 if you still have
problem with PHP 5.4 + XCache 1.3.2

Which I actually realized when reading this while he was making his reply https://groups.google.com/forum/?fromgroups#!topic/xcache/pZHjUu3Dq3k:

PHP 5.3.14 is unstable, Please upgrade to new version. You have been warned I’ve tried hard to make it stable even in 2.0 big jump, yet anything that can go wrong goes wrong.

Just goes to show I need to do more reading before jumping the gun :p

Return top
foxpa.ws
Online Marketing Toplist
Internet
Technology Blogs - Blog Rankings

Internet Blogs - BlogCatalog Blog Directory

Technology blogs
Bad Karma Networks

Please Donate!


Made in Canada  •  There's a fox in the Gibson!  •  2010-12