Memcached и метки. Реализация для фреймворка Kohana

Приветствую всех.

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

Собственно, в выборе системы кэширования особых вопросов не было — на ум сразу приходит всем известный memcached.

Если вы хотя бы немного смотрели memcached, то должны были отметить, что, по большому счету, он поддерживает только 2 операции: получение значения, запись значения. Нет никакой возможности вытянуть все ключи по определенному признаку или паттерну. Сделано это сознательно, с целью сделать его максимально простым, а значит, максимально быстрым.

Итак, представим ситуацию, что мы храним в кэше данные записей блога под ключами post_. В определенный момент нам требуется сбросить все записи с постами, при этом не очищая кэш полностью (там могут хранится другие данные, значения которых все еще актуальны). Ситуация получается безвыходная. Мы не знаем список ключей постов, получить их нет возможности, сбросить ключи по паттерну тоже невозможно. Что же делать?


Я не буду вдаваться в теоретическое описание способов, как на базе memcached можно реализовать систему тэгирования записей, по этому поводу и так есть много статей. Например, в блоге Андрея Смирнова: www.smira.ru/2008/10/29/web-caching-memcached-5. Именно этот способ я и использовал при создание системы кэширования на своем сайте.

Чтобы вы понимали о чем речь, мы собираемся хранить данные нашего кэша в массиве вида:

array (
   'tags' => array (
     'tag1' => <...значение...>,
     'tag2' => <...значение...>
   ),
   'data' => <....хранимые данные...>,
);

* This source code was highlighted with Source Code Highlighter.

Также, каждая метка у нас хранится в memcached с ключем tag_<имя тэга>. В значении мы храним некое число. В нашем случае, для надежности, мы храним в нем время создания/обновления метки с точностью до миллисекунд.
При добавлении нового элемента в кэш мы считываем это число и сохраняем его в том массиве, который описан выше. При считывании значения ключа мы проверяем, не изменилось ли значение метки относительно хранимых в значении ключа значений. В случае, если они различаются — мы считаем, что ключ сброшен. Таким образом, сброс ключей по метке производится путем изменения значения у ключа метки.

Например:
// значение post_1:
array (
   'tags' => array (
     'posts' => 1
   ),
   'data' => 'post content'
)
// значение tag_posts:
array (
   'data' => 1
)


* This source code was highlighted with Source Code Highlighter.


При считывании ключа post_1 алгоритм также считывает значение tag_posts и сравнивает его со значением, которое хранится в ['tags']['posts'] ключа post_1. В случае, если они равны — все в порядке, post_1 валиден. В случае, если значения отличаются — мы считаем, что post_1 не валиден и возвращаем NULL. Таким образом, мы можем помечать ключи не валидными просто изменив значения, которое хранится в «метках».

Если я объяснил не очень понятно — не беда. Сверху я давал ссылку на статью, где используемая мною методика описана более подробно.

Драйвер memcached в Kohana по умолчанию не поддерживает метки в memcached. Точнее, пару версий назад оно было реализовано, но работало оно так, что в итоге авторы решили отказаться от меток вообще. В кратце — там создавался один ключ в memcached, который хранил массив вида <название ключа> => <список меток>. Вы можете представить, какой размер был у этого массива на мало-мальски крупном сайте.

Скачать мою версию драйвера вы можете тут: github.com/Kolger/kohana-memcacheimp
Это обычный кохана-модуль. Скачиваете код, создаете директорию memcachedimp в папке application/modules вашего проекта. Не забудьте подключить модуль в config.php:

$config['modules'] = array
(
   //.....
   MODPATH.'memcacheimp',   // Memcacheimp driver
   //.....
);
// а также, выбрать драйвер в cache.php:

$config['default'] = array
(
   'driver'  => 'memcacheimp',
   'lifetime' => 3600,
   'requests' => 1000,
);


* This source code was highlighted with Source Code Highlighter.


Теперь, класс Cache будет работать с новым драйвером.
Использование ничем не отличается от способа, описанного в документации: docs.kohanaphp.com/libraries/cache
$cache = new Cache();
// Добавление в кэш:
$cache->set('post_1', 'post content', array('posts'));
$cache->set('post_2', 'post content', array('posts'));
// Получаем значение
$value = $cache->get('post_1');
// $value == post content
// "Удаляем" метку. А точнее, изменяем значение, которое хранится в ключе tag_posts, из за чего ключ post_1 и post_2 - сбрасываются
$cache->delete_tag('posts');
// Пробуем получить значение ключа post_1
$value = $cache->get('post_1');
// $value === NULL, чтд. На самом деле в значении ключ post_1 все еще хранится значение, но оно считается устаревшим, т.к. значение метки не совпадает с текущим.

* This source code was highlighted with Source Code Highlighter.


Думаю, немного изменив мой класс можно его использовать не только в Кохане, но и в любом другом проекте на PHP. Если есть идеи как улучшить, или оптимизировать класс — милости просим на гитхаб github.com/Kolger/kohana-memcacheimp :)

Что стоит усовершенствовать:
— Получение тэгов в методе get с помощью getMulti, чтобы не плодить запросы к memcached.
— Удаление ключей в случае, если метка изменилась.
+15
26 октября 2009, 18:01
40
Kolger 29,0

комментарии (19)

0
anycolor #
Ключевой вопрос: «Почему именно выбрали Memcached»?

Понятно, что это вариант пришедший на ум сразу у вас, но ведь есть и другие варианты. Почему выбран был этот вариант? Какие причины заставили остановиться на нем?

Очень надеюсь, что выбор пал не по принципу «вот я тут вспомнил про Memcached»…
0
Kolger #
Если честно, больше выбирать особо не из чего.
Есть файлы. Есть xCache. Есть другие, более редкие способы.
С memcached я работал и ранее, правда, немного менее плотно. Он работает на куче сайтов, начиная от хабра, заканчивая facebook.com и livejournal.com (для которого и разрабатывался). Поэтому, выбор пал именно на него.
+1
remal #
На тему «он работает на куче сайтов» хотелось бы вам дать ссылку: Lisp: побеждая посредственность.

В России большинство (точных данных у меня нет) ездит на ВАЗе. Не потому что это хорошая машина, а потому что доступная. Аналогично и с софтом: доступный, популярный, легкий в освоении — не значит лучший.
+1
kai #
Простите, но я привык критиковать предлагая свои решения. Что вы можете предложить лучше, чем мемкешед?
0
remal #
В моей работе на данный момент важна стабильность и уверенность в технологии. Поэтому я выберу мемкешед. Не потому что он лучший, а потому что знаю его лучше всего.

Как только у меня будет проект, где можно будет вволю экспериментировать, я буду искать нечто другое. Например, меня совершенно не устраивает отсутствие нормальной репликации и тех же самых тегов.
+1
stoune #
Очень легко критиковать не предлагая ничего взамен.
Имеете на примете лучшую систему? Имеете опыт работы с другими более удобными?
Тогда предложите, или напишите статью. А так ваш коментарий демагогия.
–3
bolk #
Выбирайте: bolknote.ru/files/shared/
0
korchasa #
Просмотр файликов поломался: bolknote.ru/files/shared/source/Apachenote.phps
0
bolk #
Спасибо, починил.
0
CAH4A #
Двоеточие после http в ссылке на еАкселератор забыли.
0
bolk #
Спасибо, поправил.
0
LaggyLuke #
Текущая, на данный момент версия — 0.1.0 от 21 сентября 2005
Да, библиотека очень активно развивается :)
Кстати, где там вообще тэги?

0
bolk #
Чего там развивать? Там нет тегов. Нужно — сделайте.
0
stoune #
В чём преимущества?
Почему кто-то должен выбрать именно ваше решение?
Для каких приложений лучше использовать?
Чего пытаетесь достичь?
Пока не будет ответов, а их нет на сайте, это будет вашей личной самоделкой которой маловероятно что кто-то воспользуется.

Ну и по сорцам:
К чему там всюду строчка «Copyright © 1997-2002 The PHP Group » Разве они разработали ваш модуль?
Именно внимание к деталям отличает профи от школьников которые прочли ПэХаПэ за 24 часа, а у вас этих деталей вагон и тележка.
0
bolk #
Кто говорил о моём решении? Список API для работы с shared memory (и аналогами) видели? Вот что-нибудь из этого и используйте.

Строчка копирайта там потому что именно так оформлялись PEAR модули в то время. А ваши слова про профи и школьников забавны :) Вы, вероятно, не знаете кто я :)
+1
bolk #
memcached обладает рядом недостатков, поэтому выбирать его нужно осознавая его отрицательные стороны, а именно:

1) «корзины», это значит данные на 1025 байт займёт 2КБ
2) выталкивание, когда заканчивается память: вы не контролируете какая из корзин исчезнет, когда закончатся корзины какого-то размера
3) работа через сокет, что оправдано, если память используется на другой машине и не оправдано, если использовать локально.
0
seriyPS #
Может стоило просто прислать этот модуль разработчикам Kohana?
0
vladsm #
Если у вас есть доступ к установке memcached, то что мешает его пропатчить до того, чтобы он сам начал поддерживать тэги?
0
kashey #
осталось дописать две вещи
1. «не уничтожение» устаревших тэгов если генерация уже идет( читаем тогоже смиру про одновременое перестроение)
2. «полное уничтожение данных» — так как евикшен у мемкешеда скажем так хреновый — реализуем свой обход всех сотен миллионов ключей и стирание лишнего.
про это скоро напишу топик — но крайне не желательно чтобы кешед был заполнен более чем на 75%( максималка у него 85%) так как он начинает дропать ключи малек не верно — иногда новые дропает а не старые

техническая реализация второго пункта — habrahabr.ru/blogs/webdev/71707/, только писать надо не на пхп :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.