Под нагрузкой : реализация зависимостей кэшируемых данных через метки в кэше
По службе потребовалось реализовать дублирование наиболее часто используемых данных из СУБД в более быстром кэше memcached в приложении на базе фреймворка Yii. Две сложности, с которыми столкнулся — это поддержка релевантности кэша при удалении записей из базы данных и реализация этого в условиях балансировки нагрузки.
В базе есть внешние ключи между таблицами и при строк одной таблицы можно автоматически удалять строки других таблиц. В реализации кэша в Yii тоже есть возможность связать данные между собой с помощью так называемых «зависимостей» (Dependency).
В документации перечислены следующие виды зависимостей:
- CFileCacheDependency: зависимость меняется, если изменилось время последней модификации указанного файла.
- CDirectoryCacheDependency: зависимость меняется, если изменился любой файл в каталоге или в подкаталогах.
- CDbCacheDependency: зависимость меняется, если изменился результат запроса некоторого определённого SQL-выражения.
- CGlobalStateCacheDependency: зависимость меняется, если изменилось значение определённого глобального состояния. Глобальное состояние — это переменная, являющаяся постоянной в многократных запросах и сессиях приложения. Её значение устанавливается с помощью методаCApplication::setGlobalState().
- CChainedCacheDependency: зависимость меняется, если изменилась любая зависимость цепочки.
- CExpressionDependency: зависимость меняется, если изменился результат определённого PHP выражения.
Все они не подошли мне потому что приложение распределено на несколько серверов (load balancing) и общими ресурсами у них являются только базы данных и сервера memcached. В поисках решения я наткнулся на статью «Теггирование кеша в Yii» на хабрахабре. Решение мне уже известное, но в данном случае адаптация под Yii.
Принцип реализации кэширования с тегами
Данные для хранения в кэше сопровождаются списком тегов. При сохранении данных проверяется, есть ли в кэше ранее сохранённое значение каждого тега. Если нет — генерируется и сохраняется новое. Список имён тегов и их текущих значений попадает в кэш вместе с данными.
При извлечении данных из кэша значения каждого тега сравнивается с текущим. Если текущее значение не известно или не соответствует, данные считаются невалидными. Значение тега можно удалить. Ну и конечно, он может быть удалён автоматически из кэша, даже если ему установить бесконечное время жизни — как известно, наличие данных в кэше не гарантируется.
Решение автора не оптимальное: слишком много запросов в кэш при активной работе приложения. Я ввёл упрощение — за миллисекунды обработки одного запроса значения тегов в кэше не могут измениться извне, а только внутри процесса. Это позволило ввести статический кэш в памяти для хранения текущих значений тегов. Кроме того, многие реализации кэшей поддерживают одновременный запрос нескольких значений из кэша, а при отсутствии такой возможности она имитируется. Поэтому при извлечении данных из кэша все её теги можно проверить одним запросом. Фактически будет два запроса в кэш — чтение данных + чтение всех тегов.
Код расширения yii-cache-tag-dependency
Код с примерами и тестами выложен на github: https://github.com/pvolyntsev/yii-cache-tag-dependency
Вопросы по расширению задавайте здесь в комментариях или в github/Issues.
Полезные ссылки:
- Общие сведения о кэшировании в Yii
- Зависимости и кэширование запросов в Yii
- Кэширование фрагментов в Yii
- Теггирование кеша в Yii Хабрахабр