Clever cache removal (php 5 + Mongodb + memcached)

Foreword


Nowadays, a huge number of highly loaded projects, the topic of caching would be more relevant than ever. Both database queries, individual page blocks, and all pages as a whole are cached.

For myself, I decided to cache only the results of queries to the database, because, IMHO, loading the cache with huge (or not so) html blocks is a waste of resources in some way. (This statement is most likely true only if you are not using a template engine). And of course, I use the well-known memcached as a caching service.
Now let's see what problems await us with such caching.
And the problem is just one, but not the most personal one - maintaining the cache is always up to date.

Under the cut - my solution to the problem, allowing the cache to live forever (unless of course it is relevant and you have not run out of RAM).


More details


Adhering to the now fashionable MVC model, all queries to the database will be carried out in our models. In my projects for each section of the site I have my own separate model, as well as a general model, the data from which are necessary in each section.

The cache entry key in this way consists of the name of the section, the name of the function, and the parameters passed to this function. All this wraps up in md5 in order to prevent too long streams or passing an array of parameters as part of the cache key.

Now, when updating information in the database in 90% of cases, we will definitely know the cache of which functions we will need to reset. But this does not always happen. For example, a user deleted the last 5 messages from his personal account, but since all the messages are broken, say 10 per page, resetting only the cache of the last page, all other pages will remain cached in the old state, and this is no longer good. The task is to reset the function cache, knowing only 1 of its parameter (user id), but not knowing the second (page number).

So my decision


In short - each time a new entry is added to the cache, the key components are written in mongo, thereby giving the opportunity to completely recreate the cache key, knowing only its part. I will not go into details, just go over the code.

class Cache extends Memcached {
    private $registry;
    public function __construct() {
        parent::__construct();
        $this->addServer('localhost', 11211);
        $m = new Mongo();
        $this->registry = $m->local->cache_registry; //выбираем коллекцию, где будут храниться записи ключей
    }
    public function set($name, $content) { //добавляем запись в кеш
        $this->registry->insert(array('id' => $name));
        parent::set(md5(print_r($name, 1)), $content);
    }
    public function delete($name) { //простое удаление по ключу
        $this->registry->remove(array('id' => $name));
        parent::delete(md5(print_r($name, 1)));
    }
    public function get($name) { 
        return parent::get(md5(print_r($name, 1)));
    }
    public function smart_delete($params) { //удаление по части ключа
        $criteria = array();
        $size = sizeof($params);
        for ($i = 0; $i < 2; $i++) { // перебираем название модели и названия функции
            $criteria['id.' . $i] = $params[$i];
        }
        if ($size == 3) { //параметры функции идут вложенным массивом
            for ($i = 0; $i < sizeof($params[2]); $i++) {
                $criteria['id.2.' . $i] = $params[2][$i];
            }
        } elseif ($size > 3)
            throw new Exception('Size of input parameters is out of expected range');
        $cursor = $this->registry->find($criteria);
        while ($cursor->hasNext()) { // удаляем все найденые записи из базы и из кеша
            $data = $cursor->getNext();
            $id = $data['_id'];
            parent::delete(md5(print_r($data['id'], 1)));
            $this->registry->remove(array('_id' => new MongoId($id)));
        }
    }
}


And an example of a call, so that everything finally fell into place:

$cache = new Cache();
$cache->smart_delete('user', 'messages', array(1));/*удаляем весь кеш функции messages из раздела user где первый параметр = 1*/


As you can see, it is indecently simple and quite a lot of profit:
  1. Delete all entries from a specific section
  2. Delete all section entries by function name only
  3. Deleting all function entries, knowing only the necessary parameter and discarding unnecessary ones.


Of course, despite the fact that it is implemented right now, it is impossible to delete a record, knowing only the second parameter and not knowing the first, which obliges you to think through the order of parameters at the time of creating the function. Or delete records of identical functions from different sections (you never know why), but, I think, if there is free time, all this is quite feasible.

PS All with the past holidays!

Also popular now: