УрокСистема кэширования Drupal 7. Часть третья: ускоряемся за счёт изменения места хранения кэша.

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

Меняем хранилище кэша

Чтобы несколько разгрузить базу данных, хорошей практикой считается вынос кэша за её пределы. Например, в оперативную память. Логика здесь простая - зачем нам обращаться к жёсткому диску, если можно получить данные быстрее из оперативной памяти, которая, собственно, для этого и предназначена? Одим из путей, позволяющий это реализовать, является использование демона Memcached. На его примере я и объясню как это работает.

Memcached устанавливается на сервер достаточно просто, мануал по установке вы всегда найдёте в поисковике (ну или тут :)). Если вы не умеете устанавливать дополнительные пакеты на сервер - попросите своего хостера :)

В общем, будем считать, что Memcached установлен на сервере. Теперь надо переместить кэшированные данные из базы данных в оперативную память. Для этого первым делом надо скачать модуль Memcache Storage, который позволяет интегрировать Друпал с демоном мемкэша. В общем случае это делается добавлением следующих строк в settings.php:

$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'MemcacheStorage';

Первой строкой мы указали путь к файлу, где хранится класс, который использует функции для работы с оперативной памятью и реализует интерфейс DrupalCacheInterface. Следующей строкой указано название этого класса.

Этими строками мы указали Друпалу какой класс отвечает за работу с кэшем. После этого можно включить модуль Memcache Storage и, возможно, насладиться ускоренной работой сайта.

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

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

2. Очистка кэша. У мемкэша есть некоторые проблемы в очистке кэша. К сожалению, решаются они опять же индивидуально. Т.е. если раньше при нажатии на "очистить кэш" на сайте у вас из БД физически удалялся весь кэш, то в мемкэше этого не происходит. Это связано с тем, что в его основыву закладывался метод "чем проще - тем лучше". В результате было получено достаточно быстродейственное хранилище кэша, однако со своими нюансами.
upd (13.03.2013): теперь проблема с временем хранения кэша выглядит решённой.

3. Кэш форм. В статье про сегменты кэша я указывал проблему быстрорастущего сегмента с кэшем форм. При генерации формы каждый раз в сегмент кэша добавляется 2 записи с достаточно большим количеством данных. Соответственно на среднем сайте с 20к уников в сутки за неделю может скопиться порядка 4-7гб кэша форм. Это те данные, которые просто не разумно хранить в оперативной памяти ввиду её большого размера и бесполезности в аспекте производительности. Поэтому делается небольшой финт ушами:

$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'MemcacheStorage';
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

Как видите, кэш форм мы вернули обратно в базу данных. Это довольно распространённая практика при распределении кэша между различными сегментами. Более подробно о настройке Memcache Storage в Друпале можно почитать в README.txt модуля.

Выносить кэш не обязательно в memcached, есть ещё масса других расширений, которые позволяют хранить данные вне БД. Например - APC, Boost, Varnish, XCache и так далее.

Загрузка страницы без обращения к базе данных

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

$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'MemcacheStorage';
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
 
// Настройка, позволяющая избежать подключения к БД.
$conf['page_cache_without_database'] = TRUE;

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

$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'DrupalDatabaseCache';
$conf['cache_class_cache_page'] = 'MemcacheStorage';
 
// Настройка, позволяющая избежать подключения к БД.
$conf['page_cache_without_database'] = TRUE;

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

Однако, как показала практика, такие настройки не всегда будут иметь необходимый эффект. Ведь в бутстрапе всё равно вызываются хуки hook_boot() и hook_exit(). А в этих хуках могут быть обращения к базе. Например, модуль ядра Statistics содержит функцию statistics_exit(), которая начинается следующим образом:

function statistics_exit() {
  global $user;
 
  // When serving cached pages with the 'page_cache_without_database'
  // configuration, system variables need to be loaded. This is a major
  // performance decrease for non-database page caches, but with Statistics
  // module, it is likely to also have 'statistics_enable_access_log' enabled,
  // in which case we need to bootstrap to the session phase anyway.
  drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
 
  ...

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

$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'DrupalDatabaseCache';
$conf['cache_class_cache_page'] = 'MemcacheStorage';
 
// Настройка, позволяющая избежать подключения к БД.
$conf['page_cache_without_database'] = TRUE;
 
// Настройка, позволяющая избежать вызова hook_exit() и hook_boostrap().
$conf['page_cache_invoke_hooks'] = FALSE;

Таким образом мы просим Друпал не только не дёргать БД, но и не вызывать хуки бутстрапа. Но даже в такой реализации есть небольшой нюанс :) По официальной документации всё должно работать без проблем. Однако на практике возникла проблема с сохранением кэша страниц. В ядре Друпала есть баг, который независимо от настроек принудительно архивирует кэш страницы, в результате чего он не всегда может её потом правильно отдать пользователю. Любителям почитать прикладываю ссылку на issue с проблемой, остальным - патч, который это решает. Ещё хотел бы обратить внимание на то, что оно всё работает только если в настройках производительности на сайте включена опция "Сжатие кэшированных страниц".

Множественные хранилища кэша

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

// Выносим кэш страниц в Varnish.
$conf['cache_backends'][] = 'sites/all/modules/varnish/varnish.cache.inc';
$conf['cache_class_cache_page'] = 'VarnishCache';
 
// Выносим общий кэш и кэш бутстрапа в APC.
$conf['cache_backends'][] = 'sites/all/modules/apc/drupal_apc_cache.inc';
$conf['cache_class_cache'] = 'DrupalAPCCache';
$conf['cache_class_cache_bootstrap'] = 'DrupalAPCCache';
 
// Выносим кэш форм в базу данных, чтобы не засорять оперативную память.
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
 
// Остальной кэш будет храниться в мемкэше.
$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'MemcacheStorage';
 
// Просим Друпал загружать кэш из Varnish'a без бутстрапа базы данных.
$conf['page_cache_without_database'] = TRUE;
$conf['page_cache_invoke_hooks'] = FALSE;

Комментарии

Аватар пользователя kalabro
kalabro написала:

Спасибо за отличный материал!

А nginx ты до кучи используешь или вместо варниша (спалила заголовки Drupalace.ru) ? Я использую микрокеш nginx (https://github.com/perusio/drupal-with-nginx/blob/D7/apps/drupal/microcache_proxy.conf) и пока довольна. Вопрос естественно остаётся открытым для авторизованных граждан.

PS.

GET http://drupalace.ru/sites/all/modules/bueditor/icons/page_white_code.png 404 (Not Found) 
13.02.2013 16:43
Аватар пользователя Spleshka
Spleshka написал:

kalabro, да пожалуйста) nginx используется на друпаласе только потому, что он стоит на it-patrol'e с минимальным тарифом, а там связка apache+nginx используется на всех серверах. Так бы, пожалуй, заменил его на варниш.

13.02.2013 21:42
Аватар пользователя AmiGator
AmiGator написал:

а чем потестить результативность? тест ab подойдет? я у себя добился лучшего результата с filecache, но может быть дело в самом сервере и он быстрее не может…

25.02.2013 17:17
Аватар пользователя AmiGator
AmiGator написал:

А какая версия Varnish у вас стоит? поставил себе 3 версию и вместо увеличения скорости получил тормоза. нагрузка на сервер растет, скорость отдачи в 20 раз медленее…

12.03.2013 18:56
Аватар пользователя Spleshka
Spleshka написал:

Версия не сильно важна. Важнны настройки сервера + конфигурация варниша. Конфиги варниша можно, например, у Ромки посмотреть http://romka.eu/blog/primer-rabochego-konfiga-varnish.

13.03.2013 13:28
Аватар пользователя Astral
Astral написал:

Подскажите, если конфигурация немного другая: Boost, APC, Filecache, то какие должны быть настройки в settings.php, точнее как изменить данную настройку:
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

чтобы сразу в Filecache записывался кэш форм, при условии что стандартные строчки для filecache, которые добавляются в settings.php:
$conf['cache_backends'] = array('sites/all/modules/filecache/filecache.inc');
$conf['cache_default_class'] = 'DrupalFileCache';

Но если их добавить вместе со строчками APC, сайт перестаёт грузится:
PHP Fatal error: Class 'DrupalAPCCache' not found in /home/user_name/public_html/includes/cache.inc on line 31

Как их вместе заставить корректно работать APC и filecache ?

19.03.2013 10:03
Аватар пользователя Spleshka
Spleshka написал:

Я полагаю, вы не правильно добавляете файлы в бэкенд. Вы добавляете так:

$conf['cache_backends'] = array('sites/all/modules/apc/drupal_apc_cache.inc');
$conf['cache_backends'] = array('sites/all/modules/filecache/filecache.inc');

А надо так (не перекрывать их друг другом):

$conf['cache_backends'][] = 'sites/all/modules/apc/drupal_apc_cache.inc';
$conf['cache_backends'][] = 'sites/all/modules/filecache/filecache.inc';

После чего уже настраивать. Полный пример конфига может выглядеть так:

// Выносим общий кэш и кэш бутстрапа в APC.
$conf['cache_backends'][] = 'sites/all/modules/apc/drupal_apc_cache.inc';
$conf['cache_class_cache'] = 'DrupalAPCCache';
$conf['cache_class_cache_bootstrap'] = 'DrupalAPCCache';
 
// Всё остальное храним в файловом кэше.
$conf['cache_backends'][] = 'sites/all/modules/filecache/filecache.inc';
$conf['cache_default_class'] = 'DrupalFileCache';

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

19.03.2013 12:44
Аватар пользователя FORTIS
FORTIS написал:

хм, такая же ошибка только не находит класс мемкеша и $conf['cache_backends'][] прописывал, помогло только include_once

07.08.2013 09:52
Аватар пользователя Astral
Astral написал:

Спасибо, действительно заработало.

Подскажите, а нужно ли использовать дополнительные настройки (последние 4 строчки), будут ли они иметь эффект (для Drupal 7.20, 1024 мб ОЗУ, 600 mhz, для APC 100 мб, filecache temp папка около 400 мб = 10 000 файлов, запуск крон каждый 1 час, сброс кэша Boost каждые 6 часов):

$conf['cache_backends'][] = 'sites/all/modules/apc/drupal_apc_cache.inc';
$conf['cache_class_cache'] = 'DrupalAPCCache';
$conf['cache_class_cache_bootstrap'] = 'DrupalAPCCache';

$conf['cache_backends'][] = 'sites/all/modules/filecache/filecache.inc';
$conf['cache_class_cache_form'] = 'DrupalFileCache';
$conf['cache_default_class'] = 'DrupalAPCCache';

$conf['cache_class_cache_page'] = 'DrupalAPCCache';
$conf['filecache_fast_pagecache'] = TRUE;
$conf['page_cache_without_database'] = TRUE;
$conf['page_cache_invoke_hooks'] = FALSE;

20.03.2013 11:08
Аватар пользователя Spleshka
Spleshka написал:

Если вы используете Boost, то в последних четырёх строчках вообще смысла нет. Кэш и так отдаётся мимо друпала, затрагивается только .htaccess.

20.03.2013 22:55
Аватар пользователя Валентин Будкин
Валентин Будкин написал:

Подскажите, а зачем использовать одновременно APC и Memcache в вашей любимой связке модулей? Чем плох только Memcache?

16.06.2013 15:21
Аватар пользователя AmiGator
AmiGator написал:

А какие варианты тестирования производительности вы бы посоветовали? Вот пробовал ab с сервера из той же страны. С быстрым каналом. И никакого результата. Что с обычным кешированием, что с filecache с хранилищем в оперативной памяти и там и там 500 запросов в секунду выдает. Что я делаю не так?

28.01.2015 15:05
Аватар пользователя Toopee
Toopee написал:

При включении $conf['page_cache_without_database'] = TRUE; у меня фатал: PHP Fatal error: Call to undefined function db_query() in /var/www/faprus/data/www/faprus.ru/includes/cache.inc Что я мог сделать не так?

26.11.2015 10:01

Комментировать