Memcached in PHP on Dapper

What is Memcached and what is it good for?


Memcached is a very simple thing at heart; a daemon that runs in the background and that can be called from your code to store a value with the help of a key, and then retrieve said value with the help of the key used to store it; and it all happens in the RAM.

Since you can connect to Memcached from anywhere it’s easy to imagine scaling a system up by way of having separate parts being cached on different machines.

Installation

What a nightmare getting Memcached up and running on an old Dapper system. Simply doing apt-get install memcached (worked on Jaunty though) wouldn’t do it for me, apt-get complained about libevent1 as a dependency.

Luckily for me Memcached doesn’t depend on any other lib and whatever libs libevent needs (if any) were already installed.

Here is my command history:

wget http://www.monkey.org/~provos/libevent-1.4.13-stable.tar.gz
tar -xzvf libevent-1.4.13-stable.tar.gz
cd libevent-1.4.13-stable
./configure
make
make install
wget http://memcached.googlecode.com/files/memcached-1.4.4.tar.gz
tar -xzvf memcached-1.4.4.tar.gz
./configure
make
make install
nano /etc/ld.so.conf (and adding /usr/local/lib to it)
ldconfig
/usr/local/bin/memcached -d -m 512 -l localhost -p 11211 -u root
pecl install memcache

The ld.so.conf stuff is needed for Memcached to find the libevent library during runtime.

If the last pecl install line fails you probably need the same stuff you need in order to compile PHP.

Note that there is a newer fancier pecl module called memcached, pecl install memcached will therefore also work. However I couldn’t find any proper usage documentation for that one so I went with the older one. Don’t forget to edit php.ini!

The second last line will start Memcached and have it use 500MB RAM.

Implementation

My Memchache class is based on Abhinav Singh’s Memcache class. You should read his article, it’s a good one, especially at the end where you get a few other scenarios where Memcache can make your dev life easier in addition to speeding up your site.

Update: I’ve changed the original key encoding from base64_encode (like Abhinav has) to md5, base64_encode just wouldn’t work, neither is serialization needed (which he is also doing). I still recommend the end of his article though.

class Mcache extends PhModule{
	
	function __construct() {
		$this->con = new Memcache;
		$this->host = 'localhost';
		$this->port = 11211;
		$this->connect();	
	}
	
	function connect() {    
	   if($this->con->connect($this->host, $this->port))
	     return true;  
	   else{ 
	   	trigger_error("Could not connect to Memcached daemon.");
	     return false;
	   }
	}

	function flush(){
		$this->con->flush();
	}
	
	function close() { 
	   if($this->con->close())
	     return true;  
	   else
	     return false;
	}

	function query($query){
		$result = $this->getCache($query);
		if(empty($result)){
			phive()->sql()->query($q);
			$result = phive()->sql()->result();
			$this->setCache($query, $result);	
		}
		return $result;
	}
	
	function getCache($query) {
		$key = md5($query);  
		$resultset = $this->con->get($key);
		if($resultset != null)  
	 		return $resultset;  
		else
			return false;
	}
	
	function setCache($query, $resultset, $useCompression = 0, $ttl = 600){
	    $key = md5($query);   
	    if($this->con->set($key, $resultset, $useCompression, $ttl))
	      return true;  
	    else
	      return false;  
	}  
}

As you can see here the SQL class and the Memcache class are completely separate, no caching happens per default. If I would simply have my SQL class cache every result all the time it would result in too much cache purging and writing, ie. 500MB RAM wouldn’t last long which defeats the whole purpose of Memcached.

Here is an example of the class in action:

public function getFailedLoginsLastDay(){
	$sql = $this->getModule('SQL');
	$table = $this->getSetting('db_activity');
	$ip = $_SERVER['REMOTE_ADDR'];
	
	$q = "SELECT COUNT(*) FROM `$table` WHERE `ipaddress`={$sql->escape($ip)} AND
		`code`=".ACTIVITY_LOGIN_FAIL." AND `timestamp` > DATE_SUB(CURDATE(),INTERVAL 1 DAY)";
	
	if(phive()->moduleExists('Mcache'))
		return phive()->getModule('Mcache')->query($q);
	else{
		$sql->query($q);
		return $sql->result();
	}
	
	$sql->query($q);
	return $sql->result();
}

The above query took 0.3 seconds, which is now reduced to virtually nothing.

Note: If you want your site to be perfectly responsive you will need to clear all or parts of the cache when something new happens, otherwise you will end up serving hour(s) (if that is your automatic flush frequency) old content. That is of course done with the flush() method above.

Currently I’m caching a forum so as soon as someone creates a new thread or post I flush the whole cache, this indiscriminate behavior results in a lot of cache clearing which of course isn’t ideal but it’s the best I could do on short notice; it’s OK as long as the action on the forum doesn’t get too frequent, which it isn’t yet.

Related Posts

Tags: , ,