0,2
рейтинг
13 апреля 2009 в 19:48

Разработка → Dklab_Cache: тэги в memcached, namespaces, статистика

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

Библиотека Dklab_Cache


Dklab_Cache — это (в основном) библиотека поддержки тэгирования ключей для memcached, использующая интерфейсы Zend Framework. Сама библиотека написана на чистом PHP. Вот полный список возможностей библиотеки:
  • Backend_TagEmuWrapper: тэги для memcached и любых других backend-систем кэширования Zend Framework;
  • Backend_NamespaceWrapper: поддержка пространств имен для memcached и др.;
  • Backend_Profiler: подсчет статистики по использованию memcached и др. backend-ов;
  • Frontend_Slot, Frontent_Tag: каркас для высокоуровневого построения систем кэшиирования в сложных проектах.
Собственно, для поддержки тэгов имеется класс TagEmuWrapper. Он представляет собой декоратор («обертку») для backend-классов кэширования Zend Framework. Другими словами, вы можете с его помощью «прозрачно» добавить поддержку тэгов в любую подсистему кэширования Zend Framework. Мы будем рассматривать backend для работы с memcached: Zend_Cache_Backend_Memcached, но, если в вашем проекте используется какой-то другой backend-класс, вы можете подключить тэгирование и к нему без каких-либо особенностей.

TagEmuWrapper реализует стандартный backend-интерфейс Zend_Cache_Backend_Interface, поэтому с точки зрения вызывающей системы он сам является кэш-backend'ом. Вообще, Zend Framework хорош тем, что на уровне интерфейса он поддерживает тэги с самого начала! Например, в методе save() уже имеется параметр, позволяющий снабдить ключ тэгами. Однако ни один из backend-ов в составе Zend Framework тэги не поддерживает: попытка добавить тэг к некоторому ключу вызывает исключение (в частности, для Zend_Cache_Backend_Memcached).

Технические подробности, документацию, а также примеры использования можно посмотреть тут: dklab.ru/lib/Dklab_Cache

Что такое тэги?


Работа с типичной кэширующей системой (в том числе с memcached) заключается в выполнении трех основных операций:
  • save($data, $id, $lifetime): сохранить данные $data в ячейке кэша с ключом $id. Можно указать «время жизни» ключа $lifetime; спустя это время данные в кэше «протухнут» и удалятся.
  • load($id): загрузить данные из ячейки с ключом $id. Если данные недоступны, возвращается false.
  • remove($id): очистить ячейку кэша с ключом $id.
Предположим, мы хотим эакэшировать долгий SQL-запрос для быстрого отображения части страницы. В этом случае мы проверяем: имеется ли запись в ячейке кэша, соответствующей этому запросу. Если ячейка пуста, данные загружаются из СУБД и сохраняются в кэш для возможных будущих извлечений.

if (false === ($data = $cache->load("key"))) {
    $data = executeHeavyQuery();
    $cache->save($data, "key");
}
display($data);

К сожалению, в чистом виде этот подход удается применять не так часто. Дело в том, что данные в БД могут измениться, и мы должны каким-то образом очистить ячейку кэша, чтобы пользователь увидел результаты этих изменений немедленно. Можно использовать метод remove() с указанием ключа, однако во многих случаях в момент обновления данных мы просто не знаем, в каких именно ячейках они кэшируются.

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

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

Давайте модифицируем предыдущий пример с использованием тэгов. Предположим, что SQL-запрос существенно зависит от ID текущего пользователя $loggerUserId, поэтому каждому такому пользователю выделяется отдельная ячейка с именем «key_{$loggedUserId}». Однако данные зависят и от ID другого человека $ownerUserId, чей профиль просматривает текущий пользователь. В этом случае мы можем пометить ячейку тэгом, связанным с пользователем $ownerUserId:

if (false === ($data = $cache->load("key_{$loggedUserId}"))) {
    $data = loadProfileFor($loggedUserId, $ownerUserId);
    $cache->save($data, "key_{$loggedUserId}", array("profile_{$ownerUserId}");
}
display($data);

Теперь, если меняются данные в профиле пользователя $ownerUserId (например, человек поменял свое имя), нам достаточно дать команду на очистку тэга, связанного с этим профилем:

$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array("profile_{$ownerUserId}");

Обратите внимание, что кэш-ячейки всех остальных пользователей при этом не пострадают: очистятся только те, которые зависели от $ownerUserId.

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

Небольшое отступление: о зависимостях в коде


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

if (false === ($data = $cache->load("profile_{$userId}"))) {
    $data = loadProfileOf($userId);
    $cache->save($data, "profile_{$userId}", array(), 3600 * 24); // кэширование на 24 часа
}
display($data);

и потом еще в совершенно другой части программы:

$cache->remove("profile_{$userId}");

Как видите, фразу «profile_{$userId}» приходится повторять аж три раза. И если в первом случае мы можем убрать повтор ценой введения новой переменной:

$cacheKey = "profile_{$userId}";
$cacheTime = Config::getInstance()->cacheTime->profile;
if (false === ($data = $cache->load($cacheKey))) {
    $data = loadProfileFor($userId);
    $cache->save($data, $cacheKey, array(), $cacheTime);
}
display($data);

… то во второй части программы нам «в лоб» не избавиться от знания, как именно строится ключ кэширования, и от каких параметров он зависит.

Важное замечание
Строчка «profile_{$userId}» — это именно знание, и не следует недооценивать вред о распространении этого знания по излишне большому числу независимых мест. В нашем примере знание очень просто, но на практике ключ кэша может зависеть от десятков различных параметров, часть из которых нужно даже загружать из БД по первому требованию.

Ситуация в действительности даже хуже, чем может показаться.
  • Кто может дать гарантию, что в переменной $userId хранится именно ID текущего пользователя, а не какой-нибудь мусор? А что, если кто-то попробует подставить туда неверные данные? Очевидно, что ключ кэша в действительности зависит не от ID пользователя, а от самого этого пользователя. Попытка использовать для генерации ключа что-либо, кроме объекта-пользователя, заведомо ошибочна, но в программе это ограничение явно не выражено.
  • Время кэширования мы должны хранить не прямо в коде, а где-то в конфигурации системы (см. предыдущий пример), чтобы его можно было менять, не трогая код. Это — еще одна зависимость от роли кэш-ячейки и строчки «profile».

Как это работает в Dklab_Cache


Вместо долгих разъяснений я сразу приведу пример использования Slot-класса, построенного в соответствии с идеологией Dklab_Cache_Frontend.

$slot = new Cache_Slot_UserProfile($user);
if (false === ($data = $slot->load())) {
    $data = $user->loadProfile();
    $slot->save($data);
}
display($data);

Для очистки кэша:

$slot = new Cache_Slot_UserProfile($user);
$slot->remove();

Чем же это лучше?
  • Знание об алгоритме построения ключа кэша заключено в едином месте — в классе Cache_Slot_UserProfile.
  • Там же заключено знание о времени жизни кэша. В нашем случае мы задали его явно, однако никто не мешает брать время жизни из параметра конфигурации, имя которого совпадает с именем слот-класса.
  • Параметр $user конструктора класса Cache_Slot_UserProfile — типизированный. Это означает, что мы не сможем «подсунуть» слот-классу что-либо, кроме корректного объекта-польователя. Естественно, зависимость может быть от нескольких объектов; все это определяется параметрами конструктора.
Вы должны написать столько собственных слот-классов, сколько видов кэш-хранилищ существует у вас в программе. Это дисциплинирует: заглянув в директорию Cache/Slot, вы сразу сможете увидеть, сколько именно различных кэшей используется в программе, а также — от чего они зависят.

Ну а теперь, собственно, о тэгах


Слоты, помимо прочего, поддерживают тэгирование. Вот пример использования тэгов для сквозного кэширования (естественно, можно применять и «несквозное»).

$slot = new Cache_Slot_UserProfile($user);
$slot->addTag(new Cache_Tag_User($loggedUser);
$slot->addTag(new Cache_Tag_Language($currentLanguage);
$data = $slot->thru($user)->loadProfile();
display($data);

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

$tag = new Cache_Tag_Language($currentLanguage);
$tag->clean();

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

Заключение


В этой статье говорится сразу обо всем: и о тэгировании кэша, и о кэш-зависимостях в коде, и о методе абстракции от кэш-хранилища Slot и Tag, реализованном в библиотеке.

Скачать исходники библиотеки и примеры можно здесь: dklab.ru/lib/Dklab_Cache
Дмитрий Котеров @DmitryKoterov
карма
385,2
рейтинг 0,2
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (57)

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

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

    А как же немножко уличной магии?
  • –2
    DmitryKoterov, извиняюсь за оффтоп. эта библиотека у вас уже с незапамятных времён, почему вы только сейчас разактивничались на хабре? кризис коснулся вас или предреклама нового проекта?
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        я глюк, не стоило обращать внимания, вопрос не к тебе.
    • 0
      Причины две:
      1) получить фидбэк от профессионального сообщества, представителей которого здесь больше, чем где-либо;
      2) узнать альтернативные (возможно, более эффективные) методы реализации того, что делают предложенные библиотеки.
  • +3
    мы у себя в проектах используем модифицированную версия memcache:
    code.google.com/p/memcached-tag/
    code.google.com/p/memcached-tags/
    • 0
      На тот момент, когда я ее проверял (несколько месяцев назад) она сегфолтилась, а иногда висла и начинала бесконечно отжирать память. Возможно, сейчас ее уже починили. Кстати, в дистрибутиве Dklab_Cache есть Dklab_Cache_Backend_MemcachedTag, который именно для memcached-tags предназначен (адаптер для Zend_Cache_Backend).
      • 0
        memcached-tag и memcached-tags — разные вещи (см мой коммент ниже), я так понимаю вы тестировали только memcached-tag и поддержка в Dklab_Cache_Backend_MemcachedTag только для ...tag (надеюсь только пока?).
        • 0
          Memcached-tags — это, насколько я понимаю, форк от memcached-tag, совместимый по интерфейсу. Поэтому для нее Dklab_Cache_Backend_MemcachedTag подхоит (правда, Dklab_Cache_Backend_MemcachedTag не поддерживает tags_delete, но такую поддержку нетрудно реализовать, позволив методу clean() принимать список, каждый элемент которого может быть именем тэга, а может — массивом имен).
          • 0
            Посути это fork, только с заново переписанным core, но протокол — тот же.

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

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

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

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

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

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

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

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

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

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

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

            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) — то что и нужно, тэг изменился
  • 0
    Давно мучал вопрос зависимостей кеша.
    Очень, интересная статья, спасибо.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                почитайте блог www.mysqlperformanceblog.com/ — там оочень доходчиво народ пишет как нужно работать с базой данных
                • 0
                  оттуда:
                  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 метрами)
                  • 0
                    у меня сервера разогреваются в процессе работы, там по 16 гиг памяти — поэтому все влазит :)

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

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

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

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

    тут вероятно идут игры вокруг отношений юзер-юзер. так может пример рассмотрим? на примере и будет понятно что и зачем.
    а то вот я с мемкешом уже лет пять, да всё как-то без тегов прекрасно обходился ;)
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Радостно юзаю наработки автора. Экономия кучи времени. Все работает без напильников и с толковой документацией.
  • 0
    Дмитрий,

    >> К сожалению, 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)?
    • 0
      Спасибо, попробую. Собственно, 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-расширение?
  • 0
    сорри, но всё равно не совсем понятно где физически хранятся тэги (на диске/в памяти/в 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 модуль отсюда?

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

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

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

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

    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();
    

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