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

Предисловие


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

Для себя я решил кешировать только результаты запросов к бд, т.к., имхо, загружать кеш огромными (или не очень) 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. Всех с прошедшими праздниками!

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 19

    0
    Эм… А теги чем не угодили?
      0
      насколько я помню, теги — плюшка от ZF?
      Лично мне с ним работать не по душе, от того и использую свои велосипеды (может кому пригодится). А вообще принцип работы практически идентичен.
        0
        Ни разу…
          0
          Просвятите дурака?
            0
            dklab.ru/lib/Dklab_Cache/ — как пример. Вообще, инфы в сети много, мне «в наследство» досталась либа для мемкеша с тегами. ZF не использую, сравнить не могу.
              0
              К сожалению, memcached не поддеживает тэгирование ключей, а потому код, следящий за валидностью кэша, подчас становится очень сложным.

              Если в вашем проекте Zend Framework не используется (в том числе по принциальным соображениям), не расстраивайтесь. Совершенно не обязательно подключать весь Zend Framework, чтобы работать с его подсистемой кэширования. Вы можете взять только несколько необходимых файлов; они, в частности, содержатся в дистрибутиве данной статьи. Конечно, вам следует вначале прочитать документацию по кэшированию в Zend Framework на русском языке.

              Как бы намекает о принадлежности к ZF
                0
                Как-бы я пример дал =). github.com/kohana/cache — тогда это посмотрите, точно ZF не используется.
                  0
                  Да, это уже интереснее. Вот плагин, используемый в кохана, если вдруг кому интересно http://code.google.com/p/memcached-tag/wiki/HowToUseMemcachedTag. Но мой велосипед мне нравится больше (наверное потому, что родной:))
                    0
                    Теги все-же удобнее получаются — не надо следить за параметрами и пр. Ну и база для параметров не нужна.
                      0
                      Возможно. Но лично я бы не был так категоричен. Сравнить бы производительность кеша с и без тегов, тогда будет разговор. А так… удобнее вообще ничего не делать:)
                        0
                        В той реализации, которую я использую, сторонние плагины не используются. Все на уровне php. Да и ссылку по кохане Вы дали неверную, вот правильная: code.google.com/p/memcached-tags/

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

                        // Записываем список пользователей, с id = users и тегами (users, user_1, user_2, user_3, user_4, ...)
                        $cache -> save('users', $users, array_merge(array('users'), extract_array($users, '{n}.id', 'user_')));

                        // удаляем кеш, который сохранили выше
                        $cache -> clean('user_10');

                        Разве не удобно? При этом, работаем только с памятью.
                          0
                          Одна из вариаций реализации тегов — это просто автоинкрементные счётчики. Т.е. для сброса всех данных, принадлежащих кэшу, нужно будет лишь сделать inc(tagname).
        +4
        «Mongodb + memcached» — в этой цитате что-то коробит, а что — не могу понять.

        может быть эта картинка
        www.mongodb.org/download/attachments/590394/featuresPerformace.png?version=12&modificationDate=1258579871490

        сбила меня с толку и намекает, что использование мемкеша для монго — бесполезная в плане буста производительности трата времени программиста?
          0
          если использовать монго в качестве всего backend хранилища и отказаться от кеширования, то вам грозит очень быстрый расход оперативной памяти при больших объемах объектов. Мемкеш по сравнению с монго ест намного меньше.
          К слову, в своих проектах выборки из монго я так же кеширую в мемкеш, что дает не большой, но всеже прирост в производительности.
            0
            И кстати в статье описывается использование монго для мемкеша, а не наоборот. Простите, сразу не приметил)
            0
            >В наше время огромного количества числа людей, которые думаю что у них высоконагруженные проекты…

            починил.
              0
              Да, вы думайте — это полезно. И да, кстати, проектов таки реально много: народ массово с денди в интернет лезет. Вот такая вот несправедливость.
                +1
                Понимаете, само понятие «высокая нагрузка» сравнительное. Оно подразумевает отличие от нагрузки большинства других сайтов. Таких проектов не может быть много по определению.

                Я бы переформулировал так:
                «в наше время, когда народ пытается из 100$-ового сервера выжать производительность 1000$-го, только кеширование всего на свете, как наиболее дешевое решение с точки зрения затрат на разработку, спасает положение».
              0
              По поводу «высокой нагрузки»: если понимать это в прямом смысле, то да вы правы. Но принято нынче любой проект с более менее высокой посещаемостью называть высоконагруженным (причем посещаемость для попадания в «высоконагруженные» каждый определяет по собственным предпочтениям:) )

              А перефразировка годится. Тем более в наше нелегкое кризисное время :)

              Only users with full accounts can post comments. Log in, please.