in Профессиональное

Doctrine + memcached: использование и тестирование

Doctrine ORM имеет встроенный кэш-менеджер, который умеет кэшировать в

  • Memcached
  • APC
  • DataBase (видимо имеется в виду некая плоская БД с быстрым доступом, типа SQLite)

Узнав сей факт решил воспользоваться встроенным механизмом и протестировать механизм кэширования.

Включение кэширования в symfony

Расширяем возможности конфигурации. В config/ProjectConfiguration.class.php добавляем метод конфигурирования Doctrine:

/**
* Конфигурация ORM
* @param Doctrine_Manager $manager
*/
public function configureDoctrine( Doctrine_Manager $manager )
{
  if ( extension_loaded( 'memcache' ) )
  {
    $servers = array(
      'host'        => 'localhost',
      'port'        => 11211,
      'persistent'  => true
    );
    $cacheDriver = new Doctrine_Cache_Memcache( array(
      'servers'     => $servers,
      'compression' => false
    ));
    $manager = Doctrine_Manager::getInstance();
    $manager->setAttribute( Doctrine::ATTR_QUERY_CACHE, $cacheDriver );
    $manager->setAttribute( Doctrine::ATTR_RESULT_CACHE, $cacheDriver );
  }
}

Итак, если загружено расширение memcached (это подпорка для того чтобы работало без проблем и без кэширования), определяем сервер (серверы), создаем инстанс драйвера кэша. Кэшировать будем запросы Doctrine::ATTR_QUERY_CACHE (разобранные Doctrine-запросы, чтобы не парсить их каждый раз заново) и результаты запросов Doctrine::ATTR_RESULT_CACHE.

Собственно для того чтобы кэшировались запросы придется использовать такую конструкцию:

Doctrine_Query::create()
  ...
  ->useQueryCache ()
  ->useResultCache()
  ...
  ->execute       ();

Вообще говоря, судя по документации Doctrine, прописав атрибуты менеджера мы обеспечиваем себя кэшированием. На самом деле у меня кэширование не заработало пока не использовал в конструкторе запросов use*Cache().

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

Собственно вот. Переходим к тестированию.

Тестирование кэширования

Для того чтобы оценить эффект от кэширования напишем простой таск, который будет в цикле выполнять однотипные запросы, но со случайными параметрами. Создаем класс lib/task/doctrineTestTask.class.php:

class doctrineTestTask extends sfDoctrineBaseTask
{
}

Конфигуратор – без неожиданностей:

protected function configure()
{
  $this->briefDescription = 'Test doctrine memcached';
  $this->detailedDescription = <<<EOF
Test doctrine memcached
EOF;
}

Тестовый метод:

protected function execute($arguments = array(), $options = array())
{
  // макс ID
  $q = Doctrine_Query::create()
    ->select  ( "MAX(l.id) as max, MIN(l.id) as min" )
    ->from    ( "Locality l" )
    ->useResultCache()
    ->execute ()
    ->toArray ();

  // тестовый цикл
  echo ( $start = time() ) . "n";
  for( $i = 0; $i < 10000; $i++ )
  {
    echo "$ir";
    // Случайная locality
   while( !( $locality ) )
   {
     $locality = $this->getRandLocality( $q[0]["min"], $q[0]["max"] );
   }

   // собственно тестовый запрос с несколькими джойнами. Таблицы от тысяч до пости сотни тысяч строк. Для теста сойдут.
  $search_query = Doctrine_Query::create()
    ->select  ( "prop.*, loc.*, haskw.*, kw.*" )
   ->from    ( "Property prop, prop.Locality loc, prop.PropertyHasKeyword haskw, haskw.Keyword kw" )
   ->where   ( "loc.lft >= ? AND loc.rgt <= ?", array( $locality->lft, $locality->rgt ) )
   ->offset  ( 10 * rand( 1, 100 ) )
   ->limit   ( 10 )
   ->useResultCache()
   ->execute ();
  }
  echo ( $end = time() ) . "n";
  echo "====================================================n" . ($end - $start);
}

getRandLocality() особого значения не имеет (но результат того запроса тоже кэшируем), поэтому с find() пришлось уйти на Doctrine_Query (пока не смотрел как подружить find* с кэшированием).

Для тестирования под win понадобятся memcached dll и MemCacheD Manager.

Результаты тестирования (последовательный запуски – чтобы прокэшировать данные):

Z:homedoctrine-test>symfony doctrine-test
1266870147
1266872222
====================================================
2075

Z:homedoctrine-test>symfony doctrine-test
1266872876
1266873507
====================================================
631

Z:homedoctrine-test>symfony doctrine-test
1266877034
1266877157
====================================================
123

Z:homedoctrine-test>symfony doctrine-test
1266877387
1266877478
====================================================
91

Z:homedoctrine-test>symfony doctrine-test
1266878158
1266878233
====================================================
75

Z:homedoctrine-test>symfony doctrine-test
1266878598
1266878658
====================================================
60

Z:homedoctrine-test>symfony doctrine-test
1266880037
1266880096
====================================================
59

Нисходящий тренд на лицо. Первый запуск занял > 2000 секунд (в кэше ничего не было). Повторный запуск оказался в 3 раза быстрее, последующий в 5 раз быстрее. После этого начали подходить к пределу возможностей кэшера и последующие 4 запуска уменьшили время лишь в два раза. Но там не менее, всего с первого запуска скорость выполнения теста уменьшилась более чем в 35 раз.

Состояние memcached:

Memcached Server 1.2.4
Uptime 04:13:26
Items count 13859
Total items 27711 11
Hits 86088
Misses 15994
Used bytes 8695086
Bytes read 4513330164
Bytes writen 4547347068
Max bytes 134217728

Итого, наш тест в памяти memcached занял примерно 8 Мб. Неплохо, неплохо. Готовим memcached на production серверах 😉

Write a Comment

Comment

*

  1. Что-то я запутался с этими мемкешами. Вроде как memcache и memcached разные вещи? И doctrine имеет встроенную поддержку memcache. Или я чего-то не понимаю?

      • Хм, думал какое-нибудь уведомление на почту должно свалиться, не дождался. =) Спасибо за ответ, я и запутался благодаря этим статьям. Как понял, у нас есть сервер memcached, а вот клиентские библиотеки для php разные: memcache и memcached. И вот читаю статью:
        if ( extension_loaded( ‘memcache’ ) ) <– это по коду
        А ниже текст "Итак, если загружено расширение memcached"… Даже если и опечатка (или опять непонимание с моей стороны?),то это условие я и вовсе убирал, доктрина мне ошибку вываливала. Собственно, на этом ступор у меня и возник моменте.

  2. Вообще говоря, судя по документации Doctrine, прописав атрибуты менеджера мы обеспечиваем себя кэшированием. На самом деле у меня кэширование не заработало пока не использовал в конструкторе запросов use*Cache().На самом деле это не есть гуд, так как при таком подходе нельзя выключить кэширование сразу и хитрое конфигурирование не убережет нас от падения продуктовой среды, если в ней нет memcached.

    Позволю себе дополнить вас, может кому еще пригодится.

    Ничто не мешает сделать проверку на мемкешед перед кешированием.

    $q = Doctrine_Query::create() ->
    select( "MAX(l.id) as max, MIN(l.id) as min" )->
    from( "Locality l" );

    if (extension_loaded( 'memcache' )) {
    $q->useResultCache();
    }

    $q->execute()
    ->toArray();

    Поправьте, если я ошибаюсь, но у себя я реализовал так.

    • Да, я тоже так делал, например когда переводили сессии на мемкеш – локально мемкеш не требовался и по наличию модуля либо включался, либо нет.