hi,
i'm using memcache, but not that class, im using other that splits the load
in serveral servers.


but still, i can try it and i'll let u know

ps: Cache will collapse with classes in many production systems. we should
Namespace it.

regards

ropu

On Tue, Jun 10, 2008 at 7:23 AM, Chris Chabot <[EMAIL PROTECTED]> wrote:

> Bruno your using the memcache backend on a larger scale then me, could you
> give this a spin?
>
> It should give you a bit more performance and cache stampeding prevention,
> which is good mmmkay :)
>
> I also switched from a connect/disconnect- to a pconnect situation, should
> also give a tiny performance benefit (as long as memcache doesn't complain
> about the amount of connections)
>
> Ps, did you do any modifications to this back-end to allow multiple
> memcache servers or your just using the default class?
>
>        -- Chris
>
>
> On Jun 10, 2008, at 4:03 PM, [EMAIL PROTECTED] wrote:
>
>  Author: chabotc
>> Date: Tue Jun 10 07:03:44 2008
>> New Revision: 666115
>>
>> URL: http://svn.apache.org/viewvc?rev=666115&view=rev
>> Log:
>> Added some cache stampeding protection, should make a few things a lot
>> smoother and faster; Also added a expiration glad to cache::get as
>> preperation for the refreshInterval in the proxy code
>>
>> Modified:
>>   incubator/shindig/trunk/php/src/common/Cache.php
>>   incubator/shindig/trunk/php/src/common/CacheFile.php
>>   incubator/shindig/trunk/php/src/common/CacheMemcache.php
>>
>> Modified: incubator/shindig/trunk/php/src/common/Cache.php
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/Cache.php?rev=666115&r1=666114&r2=666115&view=diff
>>
>> ==============================================================================
>> --- incubator/shindig/trunk/php/src/common/Cache.php (original)
>> +++ incubator/shindig/trunk/php/src/common/Cache.php Tue Jun 10 07:03:44
>> 2008
>> @@ -22,7 +22,7 @@
>>
>> abstract class Cache {
>>
>> -       abstract function get($key);
>> +       abstract function get($key, $expiration = false);
>>
>>        abstract function set($key, $value);
>>
>>
>> Modified: incubator/shindig/trunk/php/src/common/CacheFile.php
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/CacheFile.php?rev=666115&r1=666114&r2=666115&view=diff
>>
>> ==============================================================================
>> --- incubator/shindig/trunk/php/src/common/CacheFile.php (original)
>> +++ incubator/shindig/trunk/php/src/common/CacheFile.php Tue Jun 10
>> 07:03:44 2008
>> @@ -28,15 +28,75 @@
>>  * cache dir, and removes all files that are older then 24 hours (or
>> whatever your
>>  * config's CACHE_TIME is set too).
>>  */
>> +class CacheFile extends Cache {
>>
>> -//TODO add cache stampeding prevention using file locking mechanisms
>> +       function isLocked($cacheFile)
>> +       {
>> +               // our lock file convention is simple: /the/file/path.lock
>> +               return file_exists($cacheFile . '.lock');
>> +       }
>>
>> +       private function createLock($cacheFile)
>> +       {
>> +               $cacheDir = dirname($cacheFile);
>> +               if (! is_dir($cacheDir)) {
>> +                       if (! @mkdir($cacheDir, 0755, true)) {
>> +                               throw new CacheException("Could not create
>> cache directory");
>> +                       }
>> +               }
>> +               @touch($cacheFile . '.lock');
>> +       }
>>
>> -class CacheFile extends Cache {
>> +       private function removeLock($cacheFile)
>> +       {
>> +               // suppress all warnings, if some other process removed it
>> that's ok too
>> +               @unlink($cacheFile . '.lock');
>> +       }
>> +
>> +       private function waitForLock($cacheFile)
>> +       {
>> +               // 20 x 250 = 5 seconds
>> +               $tries = 20;
>> +               $cnt = 0;
>> +               do {
>> +                       // make sure PHP picks up on file changes. This is
>> an expensive action but really can't be avoided
>> +                       clearstatcache();
>> +                       // 250 ms is a long time to sleep, but it does
>> stop the server from burning all resources on polling locks..
>> +                       usleep(250);
>> +                       $cnt ++;
>> +               } while ($cnt <= $tries && $this->isLocked());
>> +               if ($this->isLocked()) {
>> +                       // 5 seconds passed, assume the owning process
>> died off and remove it
>> +                       $this->removeLock($cacheFile);
>> +               }
>> +       }
>> +
>> +       private function getCacheDir($hash)
>> +       {
>> +               // use the first 2 characters of the hash as a directory
>> prefix
>> +               // this should prevent slowdowns due to huge directory
>> listings
>> +               // and thus give some basic amount of scalability
>> +               return Config::get('cache_root') . '/' . substr($hash, 0,
>> 2);
>> +       }
>> +
>> +       private function getCacheFile($hash)
>> +       {
>> +               return $this->getCacheDir($hash) . '/' . $hash;
>> +       }
>>
>> -       function get($key)
>> +       public function get($key, $expiration = false)
>>        {
>> +               if (! $expiration) {
>> +                       // if no expiration time was given, fall back on
>> the global config
>> +                       $expiration = Config::get('cache_time');
>> +               }
>>                $cacheFile = $this->getCacheFile($key);
>> +               // See if this cache file is locked, if so we wait upto 5
>> seconds for the lock owning process to
>> +               // complete it's work. If the lock is not released within
>> that time frame, it's cleaned up.
>> +               // This should give us a fair amount of 'Cache Stampeding'
>> protection
>> +               if ($this->isLocked($cacheFile)) {
>> +                       $this->waitForLock($cacheFile);
>> +               }
>>                if (file_exists($cacheFile) && is_readable($cacheFile)) {
>>                        $now = time();
>>                        if (($mtime = filemtime($cacheFile)) !== false &&
>> ($now - $mtime) < Config::get('cache_time')) {
>> @@ -49,13 +109,14 @@
>>                return false;
>>        }
>>
>> -       function set($key, $value)
>> +       public function set($key, $value)
>>        {
>> -               // use the first 2 characters of the hash as a directory
>> prefix
>> -               // this should prevent slowdowns due to huge directory
>> listings
>> -               // and thus give some basic amount of scalability
>>                $cacheDir = $this->getCacheDir($key);
>>                $cacheFile = $this->getCacheFile($key);
>> +               if ($this->isLocked($cacheFile)) {
>> +                       // some other process is writing to this file too,
>> wait until it's done to prevent hickups
>> +                       $this->waitForLock($cacheFile);
>> +               }
>>                if (! is_dir($cacheDir)) {
>>                        if (! @mkdir($cacheDir, 0755, true)) {
>>                                throw new CacheException("Could not create
>> cache directory");
>> @@ -64,26 +125,19 @@
>>                // we serialize the whole request object, since we don't
>> only want the
>>                // responseContent but also the postBody used, headers,
>> size, etc
>>                $data = serialize($value);
>> +               $this->createLock($cacheFile);
>>                if (! @file_put_contents($cacheFile, $data)) {
>> +                       $this->removeLock($cacheFile);
>>                        throw new CacheException("Could not store data in
>> cache file");
>>                }
>> +               $this->removeLock($cacheFile);
>>        }
>>
>> -       function delete($key)
>> +       public function delete($key)
>>        {
>>                $file = $this->getCacheFile($key);
>>                if (! @unlink($file)) {
>>                        throw new CacheException("Cache file could not be
>> deleted");
>>                }
>>        }
>> -
>> -       private function getCacheDir($hash)
>> -       {
>> -               return Config::get('cache_root') . '/' . substr($hash, 0,
>> 2);
>> -       }
>> -
>> -       private function getCacheFile($hash)
>> -       {
>> -               return $this->getCacheDir($hash) . '/' . $hash;
>> -       }
>> }
>> \ No newline at end of file
>>
>> Modified: incubator/shindig/trunk/php/src/common/CacheMemcache.php
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/CacheMemcache.php?rev=666115&r1=666114&r2=666115&view=diff
>>
>> ==============================================================================
>> --- incubator/shindig/trunk/php/src/common/CacheMemcache.php (original)
>> +++ incubator/shindig/trunk/php/src/common/CacheMemcache.php Tue Jun 10
>> 07:03:44 2008
>> @@ -38,11 +38,44 @@
>>                $this->port = Config::get('cache_port');
>>        }
>>
>> -       public function __destruct()
>> +       private function isLocked($key)
>>        {
>> -               // if we were connected, close the connection again
>> -               if (is_resource($this->connection)) {
>> -                       memcache_close($this->connection);
>> +               $this->check();
>> +               if ((@memcache_get($this->connection, $key . '.lock')) ===
>> false) {
>> +                       return false;
>> +               }
>> +               return true;
>> +       }
>> +
>> +       private function createLock($key)
>> +       {
>> +               $this->check();
>> +               // the interesting thing is that this could fail if the
>> lock was created in the meantime..
>> +               // but we'll ignore that out of convenience
>> +               @memcache_add($this->connection, $key . '.lock', '', 0,
>> 5);
>> +       }
>> +
>> +       private function removeLock($key)
>> +       {
>> +               $this->check();
>> +               // suppress all warnings, if some other process removed it
>> that's ok too
>> +               @memcache_delete($key . '.lock');
>> +       }
>> +
>> +       private function waitForLock($key)
>> +       {
>> +               $this->check();
>> +               // 20 x 250 = 5 seconds
>> +               $tries = 20;
>> +               $cnt = 0;
>> +               do {
>> +                       // 250 ms is a long time to sleep, but it does
>> stop the server from burning all resources on polling locks..
>> +                       usleep(250);
>> +                       $cnt ++;
>> +               } while ($cnt <= $tries && $this->isLocked());
>> +               if ($this->isLocked()) {
>> +                       // 5 seconds passed, assume the owning process
>> died off and remove it
>> +                       $this->removeLock($key);
>>                }
>>        }
>>
>> @@ -50,7 +83,7 @@
>>        // so this potentially saves a lot of overhead
>>        private function connect()
>>        {
>> -               if (! $this->connection = memcache_connect($this->host,
>> $this->port)) {
>> +               if (! $this->connection = @memcache_pconnect($this->host,
>> $this->port)) {
>>                        throw new CacheException("Couldn't connect to
>> memcache server");
>>                }
>>        }
>> @@ -62,34 +95,34 @@
>>                }
>>        }
>>
>> -       // using memcache_add behavior for cache stampeding prevention
>> -       private function add($key, $var, $timeout)
>> +       public function get($key, $expiration = false)
>>        {
>>                $this->check();
>> -               if (! memcache_add($this->connection, $key, $var, 0,
>> $timeout)) {
>> -                       throw new CacheException("Couldn't add to cache");
>> +               if (!$expiration) {
>> +                       // default to global cache time
>> +                       $expiration = Config::Get('cache_time');
>>                }
>> -       }
>> -
>> -       public function get($key)
>> -       {
>> -               $this->check();
>> -               if (($ret = memcache_get($this->connection, $key)) ===
>> false) {
>> +               if (($ret = @memcache_get($this->connection, $key)) ===
>> false) {
>>                        return false;
>>                }
>> -               return $ret;
>> +               if (time() - $ret['time'] > $expiration) {
>> +                       return false;
>> +               }
>> +               return $ret['data'];
>>        }
>>
>>        public function set($key, $value)
>>        {
>>                $this->check();
>> -               if (memcache_set($this->connection, $key, $value, 0,
>> Config::Get('cache_time')) === false) {
>> +               // we store it with the cache_time default expiration so
>> objects will atleast get cleaned eventually.
>> +               if (memcache_set($this->connection, $key, array('time' =>
>> time(), 'data' => $value), 0, Config::Get('cache_time')) === false) {
>>                        throw new CacheException("Couldn't store data in
>> cache");
>>                }
>>        }
>>
>> -       function delete($key)
>> +       public function delete($key)
>>        {
>> -
>> +               $this->check();
>> +               @memcache_delete($key);
>>        }
>> }
>>
>>
>


-- 
.-. --- .--. ..-
R o p u

Reply via email to