Рабочее тегирования для кэша в памяти

// Июль 20th, 2012 // Memcached, Zend Framework

Недавно встала, казалось бы, типичная задача — орагнизовать блочное кэширование веб-приложения. Имеется в виду кэширование HTML-фрагментов. Однако в процессе решения задачи всплыл один момент.

 

Задача

Необходимо сделать кэширование HTML фрагментов, называемых блоками. Блок генерируется «тяжело» с использованием SQL запросов через ORM слой. Это означает, что надо минимизировать количество обращений к БД для увеличения производительности. Кроме собственно кэширования встаёт задача инвалидации кэша после обновления оригинальных данных. Для того, чтобы связать процесс генерации данных с процессом инвалидации кэша чаше всего используются т.н. «теги». Тег — ключ описывающий связь многие-ко-многим генерирующих процедур и сохранённых блоков в кэше.

Проблема

Главная проблема в том, что используемые хранилища для кэша в оперативнйо памяти (т.е. самые быстрые) имеют чрезвычайно простое API, это хранилища типа ключ-значение. В качестве примера приведу Memcached, Apc. Именно последний бекэнд использовался в текущем проекте. Если мы заглянем в исходники функции clean() в Zend_Cache_Backend_Apc/Memcached, то увидим там вот что:

public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
{
...
case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
$this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_APC_BACKEND);
break;
...
}

А это значит, что нужно самому придумывать очистку кэша по тегам.

Возможные решения

1. Хранить связи в том же кэше. Основаня идея: ранение всех ключей кеширования для конкретного тега в массиве — в самом кеше, с использованием тега как ключа кеширования для этого массива. Подробнее.

2. Использовать модификация для тегирования memcached-tags и класс Dklab_Cache.

3. Использовать версионность для тегов. Подробнее вот тут.

4. Хранение связей тегов и ключей в другом (но тоже быстром хранилище), например в MongoDB. Вкратце — при каждом добавлении новой записи в кеш составные части ключа записываются в монго, давая тем самым возможность полностью воссоздать ключ кеша, зная только его часть. Пример вот тут.

Вариант 1 и 3 мне не понравился своей трудоёмкостью, вариант 2 тем, что надо ставить доп.софт и ещё скорее всего настраивать и тюнить его. Вариант 4… хм, вот тут я призадумался. А почему бы просто не хранить блочный кэш в Mongo. Т.е. не только теги, но и сам кэш рядышком.

Свой вариант

Итак, решение на мой взгляд очень неплохое. Будем хранить блочный кэш в Монго вместе с тегами. Индексы обеспечат быстрый поиск, хранение в памяти — производительность, и в качестве бонуса — кэш может шариться между app-инстансами. Сначала я решил сам писать бекэнд кэша, но потом нашел замечательную штуку Zend_Cache_Backend_Mongo. А вот и мануал по её использованию. После небольшой доработки напильником бекэнд встал, как влитой (люблю за это ZF).

Бенчмарки

Предлагаю вам взглянуть на бенчмарки от автора решения.

Memcache

inserted 1000 items in 201ms (items/second: 4986)
loaded 50000 random items in 5455ms (items/second: 9165)
1000 cache miss requests 109ms (items/second: 9153)
Memory peak usage: 6029312 Byte (5.75 MB)

MongoDB

inserted 1000 items in 339ms (items/second: 2946)
loaded 50000 random items in 5747ms (items/second: 8700)
1000 cache miss requests 115ms (items/second: 8660)
Memory peak usage: 6029312 Byte (5.75 MB)

Монго чуть медленее на записи, но по скорости аналогично memcached при чтении. Думаю теги того стоят. Теперь можно не использовать TwoLevel cache, и юзать на всю катушку все функции MongoDB по работе с документами. Имхо, это лучшее решение.

Ссылки

http://www.smira.ru/2008/10/29/web-caching-memcached-5/

http://habrahabr.ru/qa/7282/

http://habrahabr.ru/post/57142/

http://blog.stoeckl.de/post/14618646779/my-mongodb-cache-backend-adapter-for-zend-framework-is

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


10 Responses to “Рабочее тегирования для кэша в памяти”

  1. IAD:

    Приличные показатели. Проверьте, происходят ли вставки в режиме SaveAndFire (без ожидания результата операции) и, если приемлемо, используйте bulkInserts (пример вставки 10-20 к записей в секунду тут http://lenta.iadlab.ru/2012/01/23/mongodb-bulkbatch-insert/ )

  2. hackPNZ:

    Интересное решение!
    Приму к сведению )))

  3. Для минимизации обращений к БД достаточно кешировать результаты запросов с этой самой БД и чистить кеш при добавлении новых данных в БД. В качестве хранилища кеша советую обратить внимание на Redis в замен скудному функционалу Memcached. В котором более богатый функционал для выборок и поддержка большего количества типов хранилищ, не только ключ — значение.

    • google.com Андрей Токарчук:

      Ага, их есть у меня. Doctrine 2 results cache. Для первой Доктрины даже был класс для автоматической инвалидации. Как кэша результатов, так и кэша SQL->DQL пробразований.

      • tankist:

        Doctrine2 results cache, конечно, прикольный, но я так и не понял, как его заставить работать непосредственно с сущностями. Ну когда бывает что-то типа: $blog->getPosts()

        • google.com Андрей Токарчук:

          Эм, ну никак. Это же Results cache. Он кэширует сырой ответ от БД. А для кэширования сущностей можно юзать например Zend_Cache (Manager+Core+Backend+Frontend).

          • tankist:

            >> Это же Results cache
            И что? В BasicEntityPersister метод load содержит такую строку:
            $stmt = $this->_conn->executeQuery($sql, $params, $types);
            При этом executeQuery принимает 4 параметра и четвертый, как раз — QueryCacheProfile, регулирует использовать cache или нет.
            В общем, копаясь сам ответил на свой вопрос — нужно переопределять BasicEntityPersister::load

          • google.com Андрей Токарчук:

            Отлично! Не знал об этом.

          • tankist:

            Ха, у них еще и персистеры захардкодены в UnitOfWork
            http://www.doctrine-project.org/jira/browse/DDC-391
            Ну, намудрили

Комментировать