Pull to refresh

Comments 57

Оправданны ли все эти навороты.

Может проще и однозначно с большей производительностью задействовать механизм _by_key из libmemcached?
Сама библиотека написана на чистом PHP.

А как же немножко уличной магии?
DmitryKoterov, извиняюсь за оффтоп. эта библиотека у вас уже с незапамятных времён, почему вы только сейчас разактивничались на хабре? кризис коснулся вас или предреклама нового проекта?
UFO just landed and posted this here
я глюк, не стоило обращать внимания, вопрос не к тебе.
Причины две:
1) получить фидбэк от профессионального сообщества, представителей которого здесь больше, чем где-либо;
2) узнать альтернативные (возможно, более эффективные) методы реализации того, что делают предложенные библиотеки.
На тот момент, когда я ее проверял (несколько месяцев назад) она сегфолтилась, а иногда висла и начинала бесконечно отжирать память. Возможно, сейчас ее уже починили. Кстати, в дистрибутиве Dklab_Cache есть Dklab_Cache_Backend_MemcachedTag, который именно для memcached-tags предназначен (адаптер для Zend_Cache_Backend).
memcached-tag и memcached-tags — разные вещи (см мой коммент ниже), я так понимаю вы тестировали только memcached-tag и поддержка в Dklab_Cache_Backend_MemcachedTag только для ...tag (надеюсь только пока?).
Memcached-tags — это, насколько я понимаю, форк от memcached-tag, совместимый по интерфейсу. Поэтому для нее Dklab_Cache_Backend_MemcachedTag подхоит (правда, Dklab_Cache_Backend_MemcachedTag не поддерживает tags_delete, но такую поддержку нетрудно реализовать, позволив методу clean() принимать список, каждый элемент которого может быть именем тэга, а может — массивом имен).
Посути это fork, только с заново переписанным core, но протокол — тот же.

> но такую поддержку нетрудно реализовать
Было бы здорово если бы библиотека поддерживала и tags_delete.
Никто статью не читал что-ли?
Повтор 2х абзацей
>>Обратите внимание, что кэш-ячейки всех остальных пользователей при этом не пострадают: очистятся только >>те, которые зависели от $ownerUserId.

>>Собственно, фраза «пометить ячейку C тэгом T» означает то же, что утверждение «ячейка C зависит от >>данных, описанных как T». Тэги — это зависимости, ничего более.
Читали.
Именно поэтому вопрос и возник.

В libmemcached понятие _by_key по сути является является аналогом тэга в понимании DmitryKoterov.
И служит именно для управления зависимостями. При этом производительность будет похоже даже не в разы, а на порядки выше и кода меньше.

Советую почитать
tangent.org/
pecl.php.net/package/memcached
Это очень интересная тема.
А можете дать прямую ссылку на документацию по by_key-функциям?

А то я за 10 минут в Гугле не нашел внятных подробностей, как оно работает. Ссылки в основном ведут на доки по UDF-функциям MySQL. Также есть ссылки на листы рассылки, из которых следует, что by_key-функции применяются для привязки некоторого набора ключей гарантировано к одному серверу. Про тэги ни слова.
Абсолютно разные вещи.

С помощью ByKey можно несколько записей привязать к одному ключу, но только к одному. То есть, грохнув этот ключ, мы грохнем все записи, с ним ассоциированные. И сделано это ни разу не для теггирования — это дает возможность разносить записи по «виртуальным серверам» в терминологии libmemcached, которые в свою очередь могут быть размещены на одном или нескольких физических серверах.

Исходники я не смотрел, но судя по тому, что они говорят «The larger the the set of nodes, the fewer misses that will occur. » каждый виртуальный сервер — на низком уровне ни что иное, как обычная запись в мемкэше. То есть выборка getByKey($serverKey, $recordKey) двухэтапная — сначала делаем get($serverKey) по всем серверам из нашего кластера, затем к полученной хэш-таблице get($recordKey). Перегон хэш-таблицы (которая может быть достаточно больше) из памяти в память — сомнительное мероприятие в плане производительности.

Возможно, эта либа более интеллектуальная — в первом проходе она запоминает сервер, на котором был найден виртуальный сервер и слабы, в которых он содержится, а на втором только к этим слабам применяется поиск по аггрегатному ключу ($serverKey, $recordKey). Тогда все более производительно (не ищем записи там, где их гарантировано нет), но более сложно.

Очевидно, что ничего общего с тегированием не имеет.
По производительности для php в свое время бенчмарил.
Аналогичные методы при использовании libmemcached от не хуже до 30% быстрее чем у данги.
mget при выборке 16 ключей приблизительно в 7 — 10 раз делал стандартный get. Если тянуть за раз два ключа — выигрыш процентов 20 получился.

Естественно влияют настройки дистрибуции. Если загнать на предел, MEMCACHED_BEHAVIOR_KETAMA и т.п., set процентов на 15-20 сливал данге, get на 0-10, mget тоже проседал. Правда получаем с другой стороны полноценную кетаму.
Прогнал. Просто в свое время когда запнулся за них _by_key натолкнуло на мысль как крайне просто контроль зависимостей реализовать. Обертку уже и не помню за час или полтора накидал. Больше про нее и не вспоминал — работает ведь. А в голове почему то отложилось что _by_key использовал.
На коленке упрощенный пример накидал как это реализовано.
Это для pecl.php.net/package/memcached. Для данговского клиента по моему достаточно будет Memcached на Memcache поменять :-(

Код обрезался.

class MyMemcached extends Memcached
{
/**
* Сохраняет в кэше сущность от которой зависят остальные
* @param str $byKey тэг
* @param str $key ключ
* @param any $value значение
*/
public function set_and_key($byKey, $key, $value){
// В реале конечно по другому, тут кому как удобнее главное уникальный
if ($this->set($byKey, rand())) // Сохраняем новое уникальное значение для тэга $byKey
return $this->set($key, $value);
return false;
}

/**
* Сохраняет в кэше зависимую сущность, привязав ее к текущему значению тэга
* @param str $byKey тэг
* @param str $key ключ
* @param any $value значение
*/
public function set_by_key($byKey, $key, $value){
return $this->set($this->get($byKey). $key, $value); // Ложим в кэш с ключем привязанным
// к текущему значению тэга
}

/**
* Получает из кэша зависимую сущность
* @param str $byKey
* @param str $key ключ
*/
public function get_by_key($byKey, $key){
return $this->get($this->get($byKey). $key);
}
}

$mc = new MyMemcached;
$mc->addServer('localhost', 11211);

$masterValue = 1;
$mc->set_and_key('byKey', 'masterValue', $masterValue);

$slaveValue = 'value'; // Значение, которое мы привяжем к $byKey

$mc->set_by_key('byKey', 'slaveValue', $slaveValue); // Сохраняем в кэше
//$slaveValue, привязав его к текущему значению byKey
var_dump($mc->get_by_key('byKey', 'slaveValue')); // Проверяем, что лежит в кэше;
//string(5) «value» — то что положили

$masterValue = 2; // Меняем значение тэга, от которого зависит $slaveValue
$mc->set_and_key('byKey', 'masterValue', $masterValue); // обновляем его значение в кэше

var_dump($mc->get_by_key('byKey', 'slaveValue')); // Пытаемся получить из кэша $slaveValue;
//bool(false) — то что и нужно, тэг изменился
Давно мучал вопрос зависимостей кеша.
Очень, интересная статья, спасибо.

Реализван ли у вас фронтэнд кеша в файловой системе? и каким образом хранятся там сами тэги?
Каждый слот имеет список тэгов, которыми он помечен, вместе с версиями этих тэгов. При инвалидации тэга его версия изменяется, поэтому библиотека при извлечении очередного слота понимает, что он «протух». Ну а для ускорения операции используется «групповой фетч» в memcached, который поддерживается в новых версиях PHP (т.е. за один запрос к memcached получаются сразу все тэги некоторого слота).
Хмм, с memcached все более менее ясно.
Но я имел ввиду насколько оптимально ваша библиотека работает с Zend_Cache_Backend_File?
Так как цена извлечения ключа, т.е. чтения файла, в разы больше чем в memcached.
А наличие тегов только уменьшает скрость чтения/записи/очистки/сборки мусора…

о какой высоконагруженности вы говорите с зенд фреймворком? да он положит сервер раньше чем вы успеете придумать что-то об использовании мемкеша (не поймите неправильно, я очень люблю ЗФ и сам его юзаю, но на 7000-10000 запросов в секунду он ляжет как минимум временем на подключение своих классов)

хотел бы вам порекомендовать посмотреть видео с конференции HighLoad о мемкеше (http://smotri.com/video/view/?id=v649374d232) — там как раз описывается как можно обновлять кеши на высокой нагрузке чтобы при этом ничего не падало — а вы говорите практически невозможно ;)
мы все молимся на smira
после цикла его статей нагрузка на серверы упала почти в 6 раз

и (ИМХО) он тэги описал понятнее
Насколько я понимаю, высоконагруженность порядка проекта МойКруг ;).
Точно 7000-10000? у меня он почему-то клал сервер на 6999-9999 запросах в секунду… Наверное, я не с теми параметрами PHP компилировал… :-)
Joka, покажите пожалуйста нам всем решение на PHP, которое на одном сервере будет вам генерировать 10000 страниц в секунду, ну или хотя бы ссылку дайте, ну или хотя бы расскажите о каких запросах вы говорите.
$slot->addTag(new Cache_Tag_User($loggedUser);
$slot->addTag(new Cache_Tag_Language($currentLanguage);
не хватает скобок
Плюсанул из уважения, но вообще «обертка к обертке» это не очень круто.
Дано:
а) СУБД
б) Memcashe

Задача:
Снизить нагрузку на БД.

Хочется: некий слой с) МЕМСУБД — который решает эту задачу

Не знаю есть же некие интерфейсы для работы с базами данных. Ну например в кодеигнитере я пишу на активрекорд:
$this->db->select('id');
$this->db->from('table')
, а эта фигня сама решает — взять данные из бд -> положить в кеш и вынуть из него или взять из кеша. Протух кеш или нет, сама генерит ключи для запросов и т.п. Как то так хочется. И желательно без апи над апи зенда над апи мемкеша.
UFO just landed and posted this here
На больших таблицах к сожалению это уже не так.
Даже если весь индекс лежит в раме.

И на них использование memcached оправданно.
Да и при быстрых запросах, если их много, выигрыш очень серьезный есть. А это снижение нагрузки на базу.

Другой вопрос — обертка может быть сама настолько тяжелой, что съест выигрыш от кэширования.
Ну это уже вопрос используемого API. Стандартное данговское не выдерживает никакой критики по ущербности функционала.

В принципе, libmemcached подкупает тем, что большинство функционала из статьи, в ней реализовано. И если и делать обертку, то предельно простую и соответственно быструю.

А mget там — это вообще ракета.
И мало того, MySQL и Oracle сохранять результаты этих запросов в памяти, т.е. сами их кэшируют. Другое дело, что они их могут выкинуть, по своему усмотрению, а в memcache мы сами определяем время хранения.
Кстати да, решение со стороны СУБД кажется наиболее логичным. Т.е. если бы например мускул можно было так «оттюнить» чтобы он максимум информации держал в горячем кеше и сбрасывала инфу на диск только периодически — было бы супер, и мемкеш бы не понадобился.
Кстати пока писал, вспомнил чито в СУБД firebird есть такая опция (правда не тестил), но фаербёрд с других концов для веба не очень удачная СУБД. С другой стороны уже более года как Майэскюэль купил отца основателя interbase|firebird — Джима Старки, и где то в недрах мускула вызревает субд falcon… Так что надеюсь в ближайшее время мы увидим решение позволяющее избежать костылей с мемкешем.
Хаха, я как в воду глядел!
The main goals of Falcon are to exploit large memory for more than just a bigger cache
Ещё 2 раза ха.Falcon уже вышел:
dev.mysql.com/doc/refman/6.0/en/se-falcon.html
С активным кэшем записей, версиями записей прямо в оперативке, оптимизацией под 64 бита и многими другими штуками. Традиционный вопрос — взлетит?)
В MySQL, в некоторых случаях помогает использование временных таблиц в памяти: dev.mysql.com/doc/refman/5.1/en/memory-storage-engine.html
Для индексов можно использовать хеш, как в memcache или b-tree.

Можно извратиться, и, например хранить базу на RAM-диске и бекапить ее по крону, при старте mysqld, восстанавливать :) Только все равно при падении сервака, часть инфы потеряем, Ну разве что журналы на обычный хард писать :)
мускл можно так оттюнить ;)

если использовать иннодб, гиг 16 памяти и адекватные настройки сервера мускл — то он выборки из 4-5 миллионых таблиц делает просто в момент потому что все висит в памяти

само собой не нужно забывать о правильной архитектуре базы, использовании индексов на каждом запросе и тд и тп

почитайте блог www.mysqlperformanceblog.com/ — там оочень доходчиво народ пишет как нужно работать с базой данных
оттуда:
Preload table / index into buffer_pool. You can use custom queries by primary / secondary key to «warm up» part of table, but this solution is ugly and may be slow due to random logical I/O.
рекомендуют целиком пихать, но это не для моего сервака с 526 метрами)
у меня сервера разогреваются в процессе работы, там по 16 гиг памяти — поэтому все влазит :)

после часа разогрева основная серверная нагрузка идет с fast-cgi процессов… базу даже не особо то и заметно…
Конечно же в memcached реализовано магическое сжатие, позволяющее сжать всю вселенскую информацию, скажем, в один гиг и извлекать из нее крохотные кусочки со скоростью света +))))
В memcached зашит обычный RCU: данные, которые реже запрашиваются — первые кандидаты на убийство, когда заканчивается память, выделенная memcached'у, а это на нагруженных проектах случается, хоть и нечасто с учетом стоимости памяти и объема хранимых в нем данных (у нас сейчас, например, забито всего 1.3 гига и 173 метра свободно).

В базах тот же самый RCU + инвалидация при изменении таблиц, входящих в запрос. С одной стороны это конечно хорошо — база гарантировано очистит кэш, если данные поменяются, но с другой стороны — это просто отвратительно, поскольку база грохнет кэш, независимо от того, затронули ли изменения возвращаемые строки и/или их количество или нет.
UFO just landed and posted this here
Можно посмотреть на это решение?
UFO just landed and posted this here
Ух, заинтересовала прежде всего не статья, а её автор. Очень приятно видеть Вас здесь. Будем читать…
не понятно про теги! если вот без единого кода на уровне чисто работы с данными — какую проблему решаем? где что хранится?

у меня сомнения по поводу верности предпосылок — я про вот этот кусок:

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

тут вероятно идут игры вокруг отношений юзер-юзер. так может пример рассмотрим? на примере и будет понятно что и зачем.
а то вот я с мемкешом уже лет пять, да всё как-то без тегов прекрасно обходился ;)
UFO just landed and posted this here
Радостно юзаю наработки автора. Экономия кучи времени. Все работает без напильников и с толковой документацией.
Дмитрий,

>> К сожалению, memcached-tag все еще очень далек от стабильной версии: нетрудно написать скрипт, приводящий к зависанию пропатченного memcached-сервера.
>> Похоже, на момент написания данной статьи не существует ни одного надежного решения проблемы тэгирования на уровне самого memcached-сервера.


да действительно в memcached-tag есть несколько багов, приводящих в итоге к утечке памяти.
но прошу заметить что memcached-tags это совершенно другой проект, в котором кроме всего прочего эти баги были исправлены. Более того проект обновлен до memcached 1.2.6 а на днях выйдет обновление до 1.2.8.

>> На тот момент, когда я ее проверял (несколько месяцев назад) она сегфолтилась, а иногда висла и начинала бесконечно отжирать память. Возможно, сейчас ее уже починили.

Опять же, это memcached_tag, попробуйте проверить memcached-tagS.
Мы используем tags на 16ти серверах при нагрузке в среднем в 4Mбайта/сек обмена данными между вэб-приложением и мэмкэшд вот уже полгода, перезагружать не приходилось ни разу.

Пара вопросов по вашей библиотеке:
1. где именно хранится связка tag+key(/slot)?
2. можно ли удалить ячейки по нескольким тагам (например если я хочу удалить всё для $loggedUser но только для $currentLanguage)?
Спасибо, попробую. Собственно, Dklab_Cache_Backend_MemcachedTag, входящий в дистрибутив, поддерживает memcached-tagS тоже.

1. Где хранится: habrahabr.ru/blogs/php/57142/#comment_1534035
2. Нет, пока этой поддержки нет, но см. habrahabr.ru/blogs/php/57142/#comment_1538275 — кстати, как называется соответствующая PHP-функция для tagS_delete? и есть ли ссылка на PECL-расширение?
сорри, но всё равно не совсем понятно где физически хранятся тэги (на диске/в памяти/в memcached)?
т.е. если у меня 2(или больше) параллельных Apache+PHP серверa на один сайт (за load-balancer'ом) + 1 memcached за всем этим. И вот один из них PHP серверов решает удалить всё по тэгу $currentLanguage, удалит ли он всё из самого memcached, или все другие web-сервера продолжат видеть якобы «удаленные» ячейки? Sorry, я не в курсе как работает Zend_Cache_Backend_File.

насчет PHP-функции и PECL-расширения — врятли они существуют, т.к. memcached-tags это патч к самому memcached серверу и мы используем клиента только на С и Perl.
Какое расширение использовалось для memcached-tag? Я помотрел здесь ничего такого нет. Вероятно использовался пропаченый 2.2.x pecl memcache модуль отсюда?

Тэги физически хранятся там же, где и ключи. Подробности алгоритма достаточно сложны, но вы можете: а) посмотреть в коде, как оно работает; б) посмотреть в phpt-тестах все варианты использования. Также алгоритм разработан так, чтобы случайная потеря ключа с тэгом (например, в результате вытеснения из памяти) автоматом вела к инвалидации всех зависимых от него ключей. Т.е. алгоритм консистентный.

Кстати, если memcached-серверов несколько, то можно использовать входящий в библиотеку модуль Dklab_Cache_Backend_ReplicationWrapper. Он обеспечивает репликацию операций удаления (а при желании — и записи) ключей. Вообще, в последних версиях php-модуля memcache репликация поддерживается на встроенном уровне, но с ними все та же проблема: они нестабильны, сегфолтятся и подвисают (видимо, потому до сих пор и бета). Поэтому пришлось сделать простейшую репликацию самостоятельно (благо оно несложно).

Про PECL для memcached-tag — да, совершенно верно. Там есть поддержка tag_add и tag_delete. А вот в Вашей версии memcached-tagS, вернее, в PHP-модуле для нее, — есть ли поддержка функции tagS_delete?
До сегодняшнего дня у нас небыло своего PHP-модуля, т.к. я уже говорил — мы используем клиента только на С и Perl. (я честно говоря вообще не думал что в ПХП для этого нужен целое расширение)
но пропатчить pecl php memcache на основе tag_delete оказалось не так сложно:
надеюсь кому-нибудь пригодиться
Там в дистрибутиве куча файлов, начинающихся с точки. Видимо, бэкапы?

Кстати говоря, для PECL-расширения ИМХО лучше распространять не полный дистрибутив, а патч. Там могут новые фичи добавиться (всякие там поддержки консистентности и т.д.), и Ваша версия быстро устареет, если она будет не в виде патча идти.
С использованием слотов для хранения в кеше одного объекта все понятно.
А что если я хочу хранить коллекцию объектов?
Получается вот так?

class Communities
{
    public function findCommunities()
    {
        $community = new stdClass();
        $community->id = 1;

        return array($community);
    }
}
class Cache_Tag_Communtity extends Geometria_Cache_Frontend_Tag 
{
    public function __construct($community)
    {
        parent::__construct("community_{$community->id}");
    }
}
class Cache_Slot_Communities extends Geometria_Cache_Frontend_Slot 
{
    public function __construct()
    {
        parent::__construct('communities', 60 * 60);
    }
    
    public function save($communities)
    {
        foreach ($communities as $community) {
        	$this->addTag(new Cache_Tag_Communtity($community));
        }

        return parent::save($communities);
    }
}

$slot = new Cache_Slot_Communities();
$communities = $slot->thru(new Communities)->findCommunities();
Sign up to leave a comment.

Articles