CFileCache: prevent stampeding

Situation:

User A requests an item from the cache (CFileCache::getValue()). This item appears to be expired, so the code will generate a new value and saves this with CFileCache::setValue().

This can be a time-consuming processs, let’s say three seconds.

In this three seconds, User B can come along, finds an expired cacheValue and also tries to rebuild this cachevalue.

On a very busy site, even more users (or requests) can rebuild the same cache value.

This can be prevented by modifying the getValue() function in CFileCache.php.


	

protected function getValue($key)

{

	$cacheFile=$this->getCacheFile($key);

	if(($time=@filemtime($cacheFile))>time())

		return @file_get_contents($cacheFile);

	else if($time>0)

		@unlink($cacheFile);

	return false;

}



Instead of unlinking the cachefile, it might be an option to touch this file again and thus extending the lifetime of this cached item bij a few seconds.

This can be accomplished by adding an public variable:


public $autoExtendExpiredCache = null;

and change the function getValue into:




protected function getValue($key)

{

	$cacheFile=$this->getCacheFile($key);

	if(($time=@filemtime($cacheFile))>time())

		return @file_get_contents($cacheFile);

	else if($time>0) {

		if (is_null($this->autoExtendExpiredCache) {

			@unlink($cacheFile);

		} else {

			@touch($cacheFile,time()+$this->autoExtendExpiredCache);

		}

	}

	return false;

}



Any thoughts on this?

(This idea is based on the same problem occuring with PEAR’s Cache_Lite, and is described here: http://www.4pmp.com/2009/09/pear-cache_lite-preventing-stampeding/)

Any way to test it locally?

What exactly do you want me to test?

I have a development machine, but it’s only used by a few users. No stampeding there.

Well, for example, RPS count from ab or Siege output with and without this patch.

why not use flock on the file?

First one aquires lock and updates file. All other requests that are blocked will be dispatched to wait for access and then read the new contents?

As regards to ‘autoExtendExpiredCache’, are you suggesting to set it to null or should it be the value of how long we want to extend our cache for?

Or should it be set somewhere within your function, as it appears it’ll always be null based on the code you’ve given.

I suggest leaving it to null.

It should be set using the declaration in the config (main.php), something like this:


       

      'cache' => array(

            'class'          => 'system.caching.CFileCache',

            'gCProbability'  => 100,

            'directoryLevel' => 1,

            'autoExtendExpiredCache' => 5

            ),



In this way, it will be backwards compatible