Главная > Теория и практика > Memcached Multi-Get, зачем?

Memcached Multi-Get, зачем?

memcached

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

Мы рассмотрим очень полезную функциональную особенность про которую многие забывают (а некоторые даже и не знают). Это операция множественного чтения или multi-get. В чем ее суть и действительно ли ее использование оправдано?

Зачем нужен multi-get

Операция multi-get позволяет за один запрос к серверу memcached получить сразу несколько объектов. Работает она очень просто, принимая в аргумент список ключей, по которым необходимо вернуть объекты.

Зачем это нужно? В статье «Правила кеширования» были рассмотрены удобные подходы при кешировании списков данных. При этом кешируются только первичные ключи в списках, а данные кешируются отдельными объектами. Это обеспечивает отличную управляемость данными. В этом случае Вам придется доставать достаточно большое число объектов каждый раз, когда Вы отображаете список данных. Тут и нужна операция multi-get.

Например, пусть у Вас есть список новостей. Тогда сам список будет хранится в ключе под именем, например «news». Каждая новость будет храниться в ключе под именем «news_item_ «, где — первичный ключ в таблице новостей (id новости). Используя multi-get мы сможем в два обращения к memcached подготовить список для отображения:

# Функция обработки списка ключей для добавления префиксов - ее лучше в прослойке реализовывать
function make_list_keys(&$value, $key, $prefix) { $value = $prefix . $value; };
...
$m = new Memcache;
...
# Получаем список первичных ключей
$list = $m->get('news')

# генерируем массив ключей
array_walk($list, 'make_list_keys', 'news_item_');
$news_list = $m->get($list);
...

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

В принципе, с помощью метода множественного чтения мы решаем две проблемы:

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

Действительно ли результаты будут ощутимыми? Давайте тестировать:

Один Multi-get vs много get

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

connect('localhost', '11211');

for ( $i = 0; $i < $tests; $i ++ )
{
        $m->set('test' . $i, md5($i));
}

$t = microtime(true);

for ( $i = 0; $i < $tests; $i ++ )
{
        $list_get[] = $m->get('test' . $i);
        $keys[] = 'test' . $i;
}

echo "Let's see our results:\n";
echo 'Fetched ' . $tests . ' objects with standard get in ' . (microtime(true) - $t) . 's';
echo "\n";

$t = microtime(true);
# Метод Memcache::get в PHP работает в режиме multi-get,
# когда получает массив в первый аргумент
$list_mget = $m->get($keys);

echo 'Fetched ' . $tests . ' objects with multiple get in ' . (microtime(true) - $t) . 's';
echo "\n";

Результаты весьма интересные:

Fetched 15000 objects with standard get in 0.47441411018372s
Fetched 15000 objects with multiple get in 0.023789882659912s

В случае multi-get мы потратили на порядок меньше времени. И стоит учесть, что тестирование выполнялось на локальном компьютере, а значит сетевой трафик отсутствовал. Если учесть это, да еще и то, что сетевой трафик генерирует не только memcached :) (скорее не столько memcached, сколько всё остальное), разница будет еще больше.

Подводя итог, можно сказать, что multi-get очень полезная операция и использовать ее стоит. Тем более, что это потребует минимального изменения Вашего кода.

А Вы использовали этот метод?

  1. tarasov
    3 Ноябрь 2009 в 19:00 | #1

    хм, вроде как все правильно написал, но есть небольшое нюансы, которые могут немного изменить результаты теста:

    1) дело в префиксах! если ты добавляешь их для каждого элемента внутри функции, то должен отдать вы выход функции ключи без префиксов. можно решить эту проблему на уровне приложения и писать обращение в массивам тоже через функцию «make_list_keys»:

    $keys = make_list_keys(array(1,2,3,4), ‘news’);
    $result = $memcache->get($keys);
    $news1 = $result[$keys[0]];
    $news2 = $result[$keys[1]];

    правда это тоже не совсем так:
    2) мемкеш multiget отпдает только те элементы которые есть, остальные просто вытерает из списка. другими словами не жди null или false в возвращаемом массиве

    $keys = make_list_keys(array(1,2,3,4), ‘news’);
    $result = $memcache->get($keys);

    var_export($result);
    array(
    ‘news_1′ => ‘qwerty’,
    ‘news_4′ => ‘qwerty’
    )
    Так вот если перебирать два раза массив: первый раз для добавления ключей, а второй чтобы их убрать — будет использовать больше ресурсов и следовательно больше времени.

    p.s. прости но пример 1 и пример 2 у тебя в тесте неправильные. результаты get’a мемкеша отличаются

  2. tarasov
    3 Ноябрь 2009 в 19:05 | #2

    поправка,
    $list_get != $list_mget

  3. 3 Ноябрь 2009 в 19:38 | #3

    @tarasov
    Большой спасибо за комментарии!

    Вы не совсем правильно понимаете саму суть multiget и того, что написано (скорее всего, я не очень удачно описал все):
    1. Я думаю понятно, что ресурсы, которые траться на перебор массива, просто не сравнимы с теми, которые тратяться на запрос к удаленному серверу. Что лучше: перебрать 100 числовых элементов или сделать 100 сетевых запросов?

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

    3. Про отдачу ключей с префиксами и без вообще ничего не понял :)

    4. Поделитесь своими результатами, было бы здорово?

  4. tarasov
    10 Ноябрь 2009 в 16:17 | #4

    не пришло на почту уведомление о новом комментарии, только сейчас увидел(

    multi-get VS single-get: конечно же multi коллосально выигрывает!

    по поводу префиксов:
    например у вас на сайте есть статьи, пользователи и галлереи и вы кешируете строчки БД, то в результате должны использовать префиксы в кеше, как например,
    users: user_1, user_2
    articles: article_1, article_2, article_3
    galleries: gallery_1, gallery_2

    следовательно в теле програмы вы будете использовать вызов по самим id, что-то вроде
    getUsers(array(1,2,3,4))
    getArticles(array(1,2,3,4))

    сама функция внутри getUsers конвертирует в правильные ключи. я беру Ваш пример:
    # генерируем массив ключей
    array_walk($list, ‘make_list_keys’, ‘news_item_’);
    $news_list = $m->get($list);

    так вот на выход вы получите массив вида
    array(
    ‘user_1′ => array(name => Petya, group => 1)
    ‘user_4′ => array(name => Vasya, group => 1)
    )
    Но для логичной и правильно работы функция должна отдавать в таком же виде в каком и запросили
    array(
    1 => array(name => Petya, group => 1)
    4 => array(name => Vasya, group => 1)
    )

    так вот на перебор больших массивов кеша будет уходить довольно много времени.
    тут я хочу извиниться, но тестовые скрипты уже давно затерялись

    если вы используете один мемкеш для нескольких схожих проектов, то вам нужно добавлять в ключи еще и id/name сайта, для примера:
    site1_user_1, site1_user_2
    site2_user_1
    конечно же, это лучше делать сразу в самой функции getUsers(), иначе прийдется делать еще один обход массива.

    так вот, если у вас в результатах массива сравнительно большой текст, как например тексты статей, или просто множество заголовков + доп инфа о самых статьях — то обхождения массива довольно затратное дело, поскольку создается 2 одинаковых массива в памяти.

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

    $users_id = array(1,2,3,4,5);
    $users = getUsers($users_id);

    переборы массивов несколько отличаются при существующих ключах и нет:
    есть пустые ключи:
    foreach($users as $user)
    {
    if (empty($user)) echo ‘anonymous’;
    }
    нет пустых ключей, но нужно вывести инфу
    foreach($users_id as $id)
    {
    if (!isset($users[$id])) echo ‘anonymous’;
    }

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

    P.S. Простите за много букв.

  5. tarasov
    10 Ноябрь 2009 в 16:18 | #5

    похоже на то что капча експайрится по времени. поставьте немного больше времени, пожалуйста. а то неудобно оставлять комменты

  6. 10 Ноябрь 2009 в 18:04 | #6

    @tarasov
    Спасибо!

    Комментарий очень исчерпывающий.
    Дествительно в случае использования multi-get Вам нужно несколько усложнить логику с переборами списков ключей. Могу только добавить, что следует максимально использовать стандартные функции PHP по работе с массивами, т.к. они гораздо производительнее, чем обычный перебор.

    Действительно описанные Вами моменты не представлены в статье, но думаю сам Ваш комментарий уже очень хорошо дополняет ее :)

  1. Пока что нет уведомлений.
*