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

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

 

Задача

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

Проблема

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

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

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

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

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

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

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

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

Свой вариант

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

Бенчмарки

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

Memcache

MongoDB

Монго чуть медленее на записи, но по скорости аналогично 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

10 Comments

    1. Спасибо за ценное замечание. Обязательно поюзаю!

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

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

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

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

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

Leave a Comment