Pull to refresh

Умное удаление кеша (php 5 + Mongodb + memcached)

Reading time 3 min
Views 5K

Предисловие


В наше время огромного колличества высоконагруженных проектов тема кеширования чего бы то не было как никогда актуальна. Кешируют как запросы из бд, отдельные блоки страницы, так и все страницы целиком.

Для себя я решил кешировать только результаты запросов к бд, т.к., имхо, загружать кеш огромными (или не очень) html блоками — в некотором роде расточительство ресурсов. (Это утверждение скорее всего справедливо только если вы не используете шаблонизатор). Ну и конечно же в качестве службы кеширования использую всем известный memcached.
Теперь давайте посмотрим — какие проблемы нас ждут при таком кешировании.
А проблема то всего одна, но зато не самая лицеприятная — поддерживание кеша всегда в актуальном состоянии.

Под катом — мое решение проблемы, позволяющее кешу жить вечно (если конечно он актуален и у вас не закончилась оперативка).


Подробнее


Придерживаясь модной нынче модели MVC все запросы к бд у нас будут проводится в моделях. В своих проектах для каждого раздела сайта у меня своя отдельная модель, а так же общая модель, данные из которой необходимы в каждом разделе.

Ключ записи кеша таким образом состоит из имени раздела, названия функции и параметров, передаваемых этой функции. Все это оборачивается в md5 для того, чтобы предотвратить слишком длинные стоки или передачу массива параметров как части ключа кеша.

Теперь при обновлении информации в базе данных в 90% случаев мы точно будем знать кеш каких функций нам нужно будет обнулить. Но так происходит не всегда. К примеру пользователь удалил последние 5 сообщений из своего личного кабинета, но, поскольку все сообщения разбиты скажем по 10 на страницу, обнулив кеш только последней страницы, все остальные страницы останутся закешированны в старом состоянии, а это уже не хорошо. Задача — обнулить кеш функции, зная всего 1 ее параметр (ид пользователя), но не зная второго (номер страницы).

Итак, мое решение


Вкратце — при каждом добавлении новой записи в кеш составные части ключа записываются в монго, давая тем самым возможность полностью воссоздать ключ кеша, зная только его часть. Не буду вникаться в подробности, просто пробежимся по коду.

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)));
        }
    }

}



И пример вызова, для того чтобы все окончательно встало на свои места:

$cache = new Cache();

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


Как видите, все до неприличия просто и достаточно много профита:
  1. Удаление всех записей из определенного раздела
  2. Удаление всех записей раздела только по имени функции
  3. Удаление всех записей функции, зная только необходимый параметр и отбрасывая лишние.


Конечно, при том, что реализовано сейчас нельзя удалить запись, зная только второй параметр и не зная первый, что обязывает продумывать порядок параметров в момент создания функции. Или удалить записи одинаковых функций из разных разделов (мало ли зачем), но, думаю, если будет свободное время все это вполне реализуемо.

P.S. Всех с прошедшими праздниками!
Tags:
Hubs:
+5
Comments 19
Comments Comments 19

Articles