Кеш в Drupal

Кеш в Drupal 
 
Drupal насчитывает множество различных сегментов кеша. Сегменты кеша похожи для версий 7.XX и 6.XX: 
 
1. {cache} — сегмент для общего хранилища кеша. Сюда попадают данные, которые невозможно классифицировать, либо же нет смысла создавать под них новый сегмент кеша.
 
2. {cache_block} — добавляется при включении модуля Block (входит в ядро). При загрузке региона темы Drupal производится загрузка данных по всем блокам этого региона и при необходимости производится построение блока или отображение его из кеша, пропуская вызов хука hook_block_view(). Стоит учесть, что кеширование для блоков отключается, если включаются модули по работе с доступами к материалу, использующие hook_node_access(). Так же необходимо знать, что при программном создании блока через hook_block_info() можно управлять параметрами кеширования для блока (подробнее — в документации).
 
3. {cache_filter} — модуль Filter создает свою таблицу для хранения кеша для обработанного фильтрами текста. Чем сложнее фильтр, тем больше процессорного времени тратится на обработку текста. Поэтому по возможности все вызовы check_markup() кешируются. Cache ID для таблицы {cache_filter} собирается по правилу название_формата: язык: хэш_текста.
 
4. {cache_form} — если остальные кешируемые данные хранятся для ускорения работы сайта, то этот кеш на производительность никак не влияет. Он необходим, чтобы формы, построенные с помощью Forms API, были абсолютно безопасными с точки зрения уязвимостей. Каждый раз при построении формы она сохраняется в сегменте {cache_form}. Если форм и посетителей много, то {cache_form} имеет свойство разрастаться до внушительных размеров, если не запускать cron для очистки кеша каждый час-два.
 
5. {cache_menu} — включается при включении модуля Menu и является хранилищем ссылок из всех меню, созданными через интерфейс. Cache ID строится по правилу links: имя_меню:tree-data: язык: хэш_параметров.
 
6. {cache_page} — хранит закешированные данные страниц для анонимных пользователей. Если найден кеш для текущей страницы, то будут вызваны только 2 хука: hook_boot() и hook_exit(). Остальные же хуки (включая hook_init() и прочие) будут пропущены. Это именно тот кеш, который включается на сайте в разделе настроек производительности (admin/config/development/performance) галочкой «Кешировать страницы для анонимных пользователей».
 
7. {cache_update} — модуль Update manager добавляет данный сегмент. Он хранит данные по всем релизам для включенных модулей.
 
Сегменты кеша, имеющиеся в Drupal версии 7.XX (нет в версии 6.XX):
 
1. {cache_path} — хранит соответствие между системным путём и его алиасами для более быстрого поиска алиаса по системному пути.
 
2. {cache_image} — зарезервирована модулем Image и может использоваться как хранение сведений о проведении различных манипуляций над изображениями.
 
3. {cache_bootstrap} — сегмент кеша, в котором хранятся данные, инициализируемые при загрузке Drupal.
 
4. {cache_field} — в данном сегменте хранятся данные по всем полям (fields). Cache ID формируется по правилу field: тип_сущности:id_сущности.
 
Так же сторонние модули могут создавать свои сегменты кеша. Например, сегменты для модулей hacked, l10n_update, token, views:
cache_hacked
cache_l10n_update
cache_token
cache_views
cache_views_data
 
Стоит упомянуть и объектный кеш Ctools’a, который не относится к ядру и создаётся модулем CTools. Объектный кеш CTools’a — это сегмент кеша, который предоставляет своё пространство под хранение больших объектов, которые редактируются в данный момент. Например, кеш изменённого представления до его сохранения в модуле Views хранится именно в объектном кеше CTools’a. В отличии от других сегментов кеша он имеет дополнительное поле sid (Session ID) — идентификатор текущей сессии пользователя. Благодаря ему изменённые данные видны только изменившему объект пользователю. Этот сегмент не имеет поля expire и не удаляется при очистке кеша через интерфейс, но очищается раз в сутки по Cron с удалением из этого сегмента кеша недельной давности.
 
Жизненный цикл страницы
 
При запросе страницы у веб-сервера браузером пользователя происходит следующее (на примере Drupal версии 7.XX):
 
Производится первичная загрузка ядра Drupal с одним из параметров:
DRUPAL_BOOTSTRAP_CONFIGURATION: Инициализирует только конфигурацию.
DRUPAL_BOOTSTRAP_PAGE_CACHE: Инициализация слоя кеширования.
DRUPAL_BOOTSTRAP_DATABASE: Инициализация слоя базы данных.
DRUPAL_BOOTSTRAP_VARIABLES: Инициализация слоя переменных.
DRUPAL_BOOTSTRAP_SESSION: Инициализация работы с сессиями.
DRUPAL_BOOTSTRAP_PAGE_HEADER: Инициализация слоя работы с заголовками.
DRUPAL_BOOTSTRAP_LANGUAGE: Инициализация слоя работы с языком страницы.
DRUPAL_BOOTSTRAP_FULL: Полностью загружает Drupal. А также добавляет функции проверки и исправления введенных данных.
 
Подробнее можно посмотреть в функции drupal_bootstrap() из файла bootstrap.inc расположенного в папке includes.
 
При выполнении drupal_bootstrap() с параметром DRUPAL_BOOTSTRAP_FULL производится:
Подключение файлов системных функций.
Инициализация всех слоёв и первичных настроек.
Подключение файлов всех включённых модулей.
Инициализация переменных и системных функций для работы с путями и их алиасами в Drupal.
Инициализация включённой темы оформления.
Производится выполнении module_invoke_all(). Реализует API для загрузки и взаимодействия с модулями Drupal, регистрируя все хуки текущих включённых модулей.
 
После этого производится вызов функции menu_execute_active_handler(), которая определяет навигационные меню и преобразует запросы страниц в вызовы функций, привязанные к путям на сайте. Также внутри данной функции производится вызов:
Функции drupal_deliver_html_page(), которая возвращает данные страницы в виде HTML в браузер пользователя. Внутри этой функции вызывается функция drupal_render(), которая не только выводит данные, но и сохраняет в один из сегментов кеша и достаёт их оттуда при их наличии вместо повторной генерации страницы с использованием шаблонизатора.
Функции drupal_page_footer(), которая устанавливает кеш страницы ('cache_path' и 'cache_bootstrap'), если это необходимо, и позволяет модулям реагировать на закрытии страницы по hook_exit (). Тут же при необходимости производится запуск Cron.
 
В общем случае:
Производится инициализация всех необходимых переменных и функций.
Производится проверка, имеется ли кеш по данному URL ({cache_page}). Если имеется, то он возвращается, иначе производятся дальнейшие действия.
Производится проверка имеется ли кеш полей ({cache_field}), контента ({cache_filter}), меню ({cache_menu}), блоков ({cache_block}), а так же изображений ({cache_image}) и алиасов ({cache_path}). Если кеш не имеется, то производится операция по получение и обработки необходимых данных с сохранением в кеш. Полученные данные передаются в функцию темизации.
Функция темизации строит страницу и кеширует её по данному URL ({cache_page}).
Данные возвращаются пользователю.
 
Программная работа с кешем в Drupal 7.X
 
Самым распространённым вариантом работы с кешем является сохранение данных разрабатываемого модуля в кеш используя функцию cache_set. А также извлечение их из него, используя функцию cache_get. 
 
<?php
  // Проверяем, имеется ли кеш с именем:  my_module_data.
  if ($cache = cache_get('my_module_data')) {
    // Возвращаем его данные, если он имеется.
    return $cache->data;
  }
  else {
    // Если кеш отсутствует, реализуем построение данных.
    $my_data = 'Тестовые данные для кеширования';
 
    // Сохраняем данные в  кеш с именем:  my_module_data сегмента: {cache}.
    cache_set('my_module_data', $my_data, 'cache');
 
    // Возвращаем данные.
    return $my_data;
  }
?>
 
Очистить данные кеша с именем my_module_data можно, вызвав одну из функций:
 
<?php
  // Очистим данные кешей с истёкшим сроком годности 
  // и времени (если кешировались на определённое время).
  cache_clear_all();
 
  // Полностью очистим сегмент {my_module_data}.
  cache_clear_all('*', 'my_module_data', TRUE);
 
  // Удалим из сегмента {my_module_data} записи, 
  // у которых Cache ID начинается c 'my_module'.
  cache_clear_all('my_module', 'my_module_data', TRUE);
?>
 
Через интерфейс администратора сайта это можно сделать на странице: example.com/admin/config/development/performance нажав кнопку Очистить кеш.
 
Если необходимо, чтобы данные были закешированы на определённое время и не зависели от нажатия кнопки очистки кеша на странице example.com/admin/config/development/performance, то достаточно добавить в функцию cache_set дополнительный параметр — на сколько секунд кешировать данные.
 
<?php
  cache_set('my_module_data', $my_data, 'cache', time() + 360);
?>
 
Drupal позволяет создавать свои сегменты кеша для хранения данных. Создадим для этого модуль: mymodule. Для этого в каталоге сайта: ./sites/default/modules создадим папку: mymodule. В ней создадим файл описания модуля mymodule.info с содержимым:
 
name =  My module
description = "Тестовый модуль для создания своего сегмента кеша."
core = 7.x
version = 7.x-1.x-dev
files[] = mymodule.module
 
Так же создадим файл mymodule.install в котором, используя hook_schema(), создадим новую таблицу для хранения данных своего сегмента кеша:
 
<?php
 
/**
 * Implements hook_schema().
 */
function mymodule_schema() {
  // Копируем схему таблицы сегмента: {cache}. 
  // Это позволяет не описывать самим поля своей таблицы
  // так как нам необходима идентичная таблица.
  $schema['mymodule'] = drupal_get_schema_unprocessed('system', 'cache');
  $schema['mymodule']['description'] = 'Cache table stores some example data.';
 
  return $schema;
}
 
Последний шаг перед тем как начать использовать функцию cache_set с указанием созданного нами сегмента — создать фал mymodule.module и позаботиться об автоматическом сбросе кеша при нажатии на кнопку очистки кеша на странице example.com/admin/config/development/performance.
 
<?php
 
/**
 * Implements hook_flush_caches().
 */
function mymodule_flush_caches() {
  // Возвращаем имя собственного сегмента для очистки данных кеша в нём.
  return array('mymodule');
}
 
Включаем модуль на странице example.com/аdmin/modules, после чего, например, сохраним в собственный сегмент кеша созданного модулем, данные:
 
<?php
  cache_set('my_module_data', 'Строка сохранённая в собственный сегмент кеша', 'mymodule');
?>
 
Существует малоизвестная функция cache_is_empty, с помощью которой можно узнать, хранятся ли в кеше с заданным именем какие-либо данные:
 
<?php
  cache_is_empty('my_module');
?>
 
Как работать с кешем в Drupal 6.X и 8.X, можно всегда посмотреть в подмодуле cache_example, модуля: examples со страницы: www.drupal.org/project/examples.
 
Вынесение кеша из базы данных
 
Кеш сегментов можно перенести, например в Memcached или Redis. 
 
У каждого хранилища имеются свои сторонники. Помогавший в написании статьи Evgeniy Maslovskiy (Spleshka, www.drupal.org/u/spleshka) является сторонником Memcached, и на его сайте подробно описана интеграция Memcached и Drupal. В описанном им модуле интеграции с Memcached имеются дополнительные плюсы перед другими решениями: это обход подключения к БД при получении кеша и обход вызовов hook_exit() и hook_boostrap() в других модулях.
 
В данной статье рассмотрим вынесение кеша в Redis, который мне нравится более простой настройкой по сравнению с Memcached. А сравнительные тесты скорости обоих хранилищ рассмотрим в другой статье.
 
Настройку Redis на сервере можно найти в интернет. Модуль интеграции с Drupal скачаем со страницы www.drupal.org/project/redis и распакуем в директорию ./sites/all/modules. После чего внесём изменения в конфигурационный файл Drupal ./sites/default/settings.php, добавив в него строки:
 
$conf['redis_client_interface'] = 'PhpRedis';
$conf['redis_client_host'] = $relationships['redis'][0]['host'];
$conf['redis_client_port'] = $relationships['redis'][0]['port'];
// Имя используемой библиотеки в PHP для соединения с Redis.
$conf['redis_client_interface'] = 'PhpRedis';
$conf['cache_backends'][]       = 'sites/all/modules/redis/redis.autoload.inc';
$conf['cache_default_class']    = 'Redis_Cache';
// Пример как сегмент кеша {cache_form} оставить для хранения в базе данных.
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
// Пример как собственный сегмент кеша созданы в модуле ранее в статье: {mymodule}
// перенести для хранения в Redis.
$conf['cache_class_mymodule'] = 'Redis_Cache';
 
Не забывайте читать файл README.txt в папке модуля, так как в нём описаны все настройки модуля.
 
Где это можно применить?
 
Один из вариантов применения описанных знаний вынесение части данных сайта в блоки загружающиеся после основной загрузки страницы, как для ускорения загрузки сайта, так и для общего ускорения работы сайта. Достаточно лишь взять за основу модуль Ajax Blocks, интегрировать его с модулем High-performance JavaScript callback handler, а кеш данных поместить в своё хранилище в Redis, используя данную статью.