Power of versioning caching
There are some many great videos about that. Like:
- http://www.bestechvideos.com/2009/03/21/railslab-scaling-rails-episode-8-memcached
- 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:
- Last uploaded images
- Most popular images
- Categorys
- Albums
- Top rated images
- 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
- 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.
- 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:
- Lighttpd
- PHP 5.3.1
- APC Cache
- 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 »