20091210

Tutorial de memcache

Memcache es una extensió de PHP que interactua amb memcached, que a la seva vegada es un daemon de cache en memoria, molt simple, que enmagatzema parells key=>value on value pot ser qualsevol cosa serialitzable amb PHP.

Per instal.lar amb Ubuntu farem el següent:
apt-get install memcached
apt-get install php5-memcache

Reiniciar apache:
/etc/init.d/apache2 restart

Es pot fer un php amb un phpinfo(); per veure si hi ha un apartat memcache

Per veure si esta funcionant el memcached (el daemon):
netstat -tap | grep memcached

Per modificar els parametres de memoria etc. editar /etc/memcached.conf

per exemple, per configurar 300Mbs de memoria disponible per variables a memcached modificarem el flag -m del fitxer de configuració:
-m 300

Reiniciar memcached
/etc/init.d/memcached restart

Per consultar les estadistiques en temps real del memcached:

telnet localhost 11211
>stats

Exemple de output:


Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 4666
STAT uptime 153661
STAT time 1260523656
STAT version 1.2.2
STAT pointer_size 64
STAT rusage_user 4.850000
STAT rusage_system 15.100000
STAT curr_items 16001
STAT total_items 57971
STAT bytes 282532500
STAT curr_connections 1
STAT total_connections 120074
STAT connection_structures 32
STAT cmd_get 120016
STAT cmd_set 57971
STAT get_hits 66178
STAT get_misses 53838
STAT evictions 0
STAT bytes_read 1108602586
STAT bytes_written 1317300490
STAT limit_maxbytes 314572800
STAT threads 1
END


Ara només cal modificar el nostre codi per enmagatzemar rows de la base de dades, durant un cert temps, en aquests exemples, creem una clau $action.'/'.$param1.'/'.$param2 o sigui per exemple 'category/23/1' i el valor serà el resultat de la base de dades (una array de rows).

La instrucció set enmagatzema a memcached:

$mc->set($action.'/'.$param1.'/'.$param2, $rows, 1, 3600);

El primer parametre es la clau, el segon el valor, el tercer si volem comprimir el contingut o no i el quart el temps en segons que tindrem el valor al memcache (1 hora)

La següent vegada que haguem de fer la consulta farem el següent:

$rows=$mc->get($action.'/'.$param1.'/'.$param2);

Si ens retorna null, es que no hi es, pero durant una hora, no ens caldrà repetir la consulta.

Exemples de codi:


function get_last($num=16, $offset=1, $flag = 1)
{
$sql = "SELECT v.*
, p.title portal_title
, p.url portal_url
, p.id portal_id
FROM ep_videos v, ep_portals p
WHERE v.portal_id = p.id
AND v.status = 'O'
ORDER BY v.added DESC
LIMIT ".(($offset-1)*$num).", ".$num;

$action='home';
$param1='1';
$param2=$offset;
$mc=new Memcache;
$cr=$mc->connect('localhost', 11211);
if ($cr)
{// tenim memcached
$rows=$mc->get($action.'/'.$param1.'/'.$param2);
if ($rows==null)
{// no el tenim a cache
$rows=parent::get_data($sql);
$mc->set($action.'/'.$param1.'/'.$param2, $rows, 1, 3600);
}
}
else
{// no tenim memcached
$rows=parent::get_data($sql);
}

return $rows;
}

function get_last_in_category($id, $num=16, $offset=1)
{
$sql = "SELECT v.*
, p.title portal_title
, p.url portal_url
, p.id portal_id
FROM ep_portals p
, ep_videos v
, ep_categories_videos cv
WHERE cv.video_id = v.id
AND cv.category_id = ".$id."
AND v.status='O'
AND v.portal_id = p.id
ORDER BY v.added DESC
LIMIT ".(($offset-1)*$num).", ".$num;

$action='category';
$param1=$id;
$param2=$offset;
$mc=new Memcache;
$cr=$mc->connect('localhost', 11211);
if ($cr)
{// tenim memcached
$rows=$mc->get($action.'/'.$param1.'/'.$param2);
if ($rows==null)
{// no el tenim a cache
$rows=parent::get_data($sql);
$mc->set($action.'/'.$param1.'/'.$param2, $rows, 1, 3600);
}
}
else
{// no tenim memcached
$rows=parent::get_data($sql);
}

return $rows;

//return parent::get_data($sql);
}

function get_last_in_tag($id, $num=16, $offset=1)
{
$sql = "SELECT v.*
, p.title portal_title
, p.url portal_url
, p.id portal_id
FROM ep_portals p
, ep_videos v
, ep_tags_videos cv
WHERE cv.video_id = v.id
AND cv.tag_id = ".$id."
AND v.status='O'
AND v.portal_id=p.id
ORDER BY v.added DESC
LIMIT ".(($offset-1)*$num).", ".$num;

$action='tag';
$param1=$id;
$param2=$offset;
$mc=new Memcache;
$cr=$mc->connect('localhost', 11211);
if ($cr)
{// tenim memcached
$rows=$mc->get($action.'/'.$param1.'/'.$param2);
if ($rows==null)
{// no el tenim a cache
$rows=parent::get_data($sql);
$mc->set($action.'/'.$param1.'/'.$param2, $rows, 1, 3600);
}
}
else
{// no tenim memcached
$rows=parent::get_data($sql);
}

return $rows;

//return parent::get_data($sql);
}

8 comentarios:

Carlos dijo...

Molt bon tutorial!

La veritat es que es nota molt el rendiment, i lo facil que es fer-ho servir.

El que he trobat problemes, no se si sabras com solucionar-ho, o per que ho tinguis en conte, es el moment de petar aquesta cache. En el meu cas, quan s'edita un element, peto la cache del element, pero si aquest element surt per exemple en un llistat... ¿com petes totes les cache's on intervingui?

No se si m'explico, pero si edites un element, i petes la seva cache, nomes estas segur de que en el llistats (amb paginacio) esta canviat si buclas el delete cache, i sempre amb algun limit...

Se que hi ha un sistema de tagging per la cache, pero em sembla que no està del tot desarrollat.

Salutacions!!

Agustí Pons dijo...

Hola Carlos, per lo que he vist t'has de fer tu la gestio manualment, guardar-te en cache per exemple un array dels elements que vas guardant per poder saber que hi ha i que no hi ha.

Vaja, que es molt manual.

Jo de moment nomes estic guardant les primeres pagines de totes les categories, tags i portals del projecte que tu i jo sabem :)

Carles dijo...

Molt clar i entenedor Agus.
Saps si hi ha forma de monitoritzar la memoria de caché que portes ocupada?
Merci!

Agustí Pons dijo...

Hola Carles,

si, amb lo del telnet i la comanda stats pots veure la quantitat total de memoria emprada, així com el número de hits, misses, num registres actuals, num registres totals etc.

He afegit un output d'exemple de la comanda, així queda més clar

Carlos dijo...

Per si us interesa, al telnet si es fa flush_all peta tota la cache, molt util a vegades ;)

Agustí Pons dijo...

Gracies pel comentari Carlos, molt util!

Nejo dijo...

guay, si algún dia el vull probar ho seguiré q sembla molt fàcil així explicat :P

però pensaba q seria més transparent, no que hauries de retocar minimament el codi, que ja parlaria ell amb el compilador directament per a detectar si s'havia executat l'script abans i si no hi han modificacions agafar-ho de la cache.

És així igualment, o només serveix per a peticions des del codi ?

Agustí Pons dijo...

Hola Nejo,

el que comentes es totalment diferent, es una cache de bytecodes.

La que explico aqui es una cache de dades per evitar accesos a mysql.

L'altre si que es més transparent, compila el php i es guarda la versió compilada si no hi ha modificacions al fitxer.