Saturday, September 4, 2010
 

Power of versioning caching

There are some many great videos about that. Like:

  1. http://www.bestechvideos.com/2009/03/21/railslab-scaling-rails-episode-8-memcached
  2. http://www.infoq.com/presentations/lutke-rockstar-memcaching

But this time i will share my experience about using methods presented in these videos. Averyone before reading futher should watch these videos first. They are great.

So story itself. I have two images gallery one with 130 000 images and another one with 65 000 images. Almoust two years it was runing with http://coppermine-gallery.net/ averything seems ok, except two things it's not very CPU friendly and completely no database friendly. Biggest disatvantages it's architecture i have been playing around with it almoust a year started to cache one part or another part of gallery, but it's writeln in sutch wayt that's it's almoust not possible to use provided ideas in videos above so i gave up and dicaided to write my own gallery based on http://fof.remdex.info in genneraly it's just a little abstraction of ezcomponents. To be easier to understand i'm talking about http://animeonly.org site.

So some importants parts of imge gallery:

  1. Last uploaded images
  2. Most popular images
  3. Categorys
  4. Albums
  5. Top rated images
  6. And so on....

So how can we cache these things?

Very easily. Last uploaded images have it's own cache version key. Example:

Everything needed in view is this:

<?php
$cache = CSCacheAPC::getMem();
if (($Result = $cache->restore(md5('version_'.$cache->getCacheVersion('last_uploads').
'lastuploads_view_url'.'_page_'.$Params['user_parameters_unordered']['page']))) === false)
{

//...Some buisness logic
$cache->store(md5('version_'.$cache->getCacheVersion('last_uploads').'lastuploads_view_url'.
'_page_'.$Params['user_parameters_unordered']['page']),$Result);
}
?>

Caching key consits of cache version variable last_uploads then image is uploaded i increase this number and all lists gets expired automaticly. Meanwhile cache gets expired every 12h automatickly. This time is set in "CSCacheAPC" class. I will paste class code later.

A little bit different logic applies to "Most popular images"

Code snippet:

// Expire all list at once.
$cache = CSCacheAPC::getMem();   
$cacheVersion = $cache->getCacheVersion('most_popular_version',time(),1500);

if (($Result = $cache->restore(md5('version_'.$cacheVersion.'popular_view_url'.
'_page_'.$Params['user_parameters_unordered']['page']))) === false)
{ 

// Some buisness logic
$cache->store(md5('version_'.$cacheVersion.'popular_view_url'.
'_page_'.$Params['user_parameters_unordered']['page']),$Result);
}

This time most popular images expire automatickly after 25 minits. 1500/60 = 25 minits. Also then image is uploadded or deleted i increment "most_popular_version" cache key. Same logic applies to last hits, top rated lists.

Ok now the more interesting part. How to cache album and categorys cache?. Code snippet:

<?php
$cache = CSCacheAPC::getMem();     
if (($Result = $cache->restore(md5('version_'.$cache->getCacheVersion('album_'.(int)$Params['user_parameters']['album_id']).
'album_view_url'.(int)$Params['user_parameters']['album_id'].
'_page_'.$Params['user_parameters_unordered']['page']))) === false)
{  

    
    $cache->store(md5('version_'.$cache->getCacheVersion('album_'.(int)$Params['user_parameters']['album_id']).
'album_view_url'.(int)$Params['user_parameters']['album_id'].
'_page_'.$Params['user_parameters_unordered']['page']),$Result);
}

?>

This time cache version key is calculated automatickly. Then i make changes with album i increment cache key. And also parent category. That way i do not clear all cache, but just related cache. Same logic applies to category view.

Some conclusions

  1.  Then page is caching i make 1 or 2 dabase quries. Minimal number i managet to get with coppermine gallery was a little above 10 queries per page request and that after caching some parts.
  2. So how mutch page request can i handle now?
[root@remdex ~]# ab -n 500 -c 10 http://animeonly.org/Fantasy/Mix-16a.html
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking animeonly.org (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Finished 500 requests


Server Software:        lighttpd/1.4.22
Server Hostname:        animeonly.org
Server Port:            80

Document Path:          /Fantasy/Mix-16a.html
Document Length:        26882 bytes

Concurrency Level:      10
Time taken for tests:   2.563495 seconds
Complete requests:      500
Failed requests:        0
Write errors:           0
Total transferred:      13667977 bytes
HTML transferred:       13494764 bytes
Requests per second:    195.05 [#/sec] (mean)
Time per request:       51.270 [ms] (mean)
Time per request:       5.127 [ms] (mean, across all concurrent requests)
Transfer rate:          5206.56 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   21  15.6     21      78
Processing:     4   27  16.6     26      83
Waiting:        0   21  15.4     21      78
Total:         39   49  11.6     44      83

Percentage of the requests served within a certain time (ms)
  50%     44
  66%     44
  75%     47
  80%     65
  90%     71
  95%     76
  98%     83
  99%     83
 100%     83 (longest request)

So cached pages usualy loads in 5 miliseconds!!! If you do not beleave me just see gallery. :) If anyone interets in software and hardware used here it is.

CPCU:

model name      : Pentium(R) Dual-Core  CPU      E6500  @ 2.93GHz
stepping        : 10
cpu MHz         : 2933.543
cache size      : 2048 KB

Memory: 2GB

Software:

  1. Lighttpd
  2. PHP 5.3.1
  3. APC Cache
  4. Memcache

And like i promissed full class of caching:

class CSCacheAPC extends Memcache {

    static private $m_objMem = NULL;

    public $cacheKeys = array(
    'last_hits_version',        // Last visited pages
    'most_popular_version',     // Most popular images, watched times
    'top_rated',                // Top rated images
    'last_uploads',             // Last uploaded images
    'last_commented',           // Last commented images
    'site_version',             // Global site version
    'album_count_version',      // Album count version
    );
    
    public function increaseImageManipulationCache()
    {
        $this->increaseCacheVersion('last_hits_version');
        $this->increaseCacheVersion('most_popular_version');
        $this->increaseCacheVersion('last_uploads');
        $this->increaseCacheVersion('top_rated');
        $this->increaseCacheVersion('last_commented');        
        $this->delete(md5('index_page'));
    }
    
    function __construct(){

    }
    
    function __destruct() {
        self::$m_objMem->close();
    }
    
    static function getMem() {
        if (self::$m_objMem == NULL) {
            self::$m_objMem = new CSCacheAPC();
            self::$m_objMem->connect('127.0.0.1', '11211') 
                        or die ("The memcached server");
        }
        return self::$m_objMem;
    }

    function delete($key) {
        if (isset($GLOBALS[$key])) unset($GLOBALS[$key]);
        $this->set(SITE_CACHE_PREPEND.$key,false,0);
    }

    function restore($key) {
        if (isset($GLOBALS[$key]) && $GLOBALS[$key] !== false) return $GLOBALS[$key];        
        $GLOBALS[$key] = $this->get(SITE_CACHE_PREPEND.$key);        
        return $GLOBALS[$key];
    }

    function getCacheVersion($cacheVariable, $valuedefault = 1, $ttl = 0)
    {
        if (isset($GLOBALS['CacheKeyVersion_'.$cacheVariable])) return $GLOBALS['CacheKeyVersion_'.$cacheVariable];
        
        if (($version = $this->get(SITE_CACHE_PREPEND.$cacheVariable)) == false){
            $version = $valuedefault;
            $this->set(SITE_CACHE_PREPEND.$cacheVariable,$version,0,$ttl);
            $GLOBALS['CacheKeyVersion_'.$cacheVariable] = $valuedefault;
        } else $GLOBALS['CacheKeyVersion_'.$cacheVariable] = $version;
        
        return $version;        
    }
    
    function increaseCacheVersion($cacheVariable)
    {
        if (($version = $this->get(SITE_CACHE_PREPEND.$cacheVariable)) == false){
             $this->set(SITE_CACHE_PREPEND.$cacheVariable,1);
             $GLOBALS['CacheKeyVersion_'.$cacheVariable] = 1;
        } else {$this->increment(SITE_CACHE_PREPEND.$cacheVariable);$GLOBALS['CacheKeyVersion_'.$cacheVariable] = $version+1;}
        
    }
    
    function store($key, $value, $ttl = 12000) {
        $GLOBALS[$key] = $value;
        $this->set(SITE_CACHE_PREPEND.$key,$value,0,$ttl);
    } 
    
      
    
}

Constant SITE_CACHE_PREPEND is defined in configuration to avoid same cache keys on server then using same gallerys multiple times. They just have different values. That's all for this time :)


 

 

Back »

Comments: 0

Leave a reply »

 
  • Leave a Reply
    Your gravatar
    Your Name
     
     
     
     
     
 
About Remdex site

Simple site for simple peoples.

Get in touch

E-mail: remdex@gmail.com