Руководство для новичков по кэшированию данных в Drupal 7

Создавать сложный динамический контент в Drupal - это легко, но всему есть цена. Многие вещи, делающие сайт привлекательным, могут создать "кошмар производительности". Они грузят базу данных сложными запросами и тяжелыми вычислениями каждый раз когда пользователь загружает страницу нашего сайта.

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

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

Основы

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

function my_module_function() {
  $my_data = &drupal_static(__FUNCTION__);
  if (!isset($my_data)) {
    // Здесь делаем сложные вычисления и записываем результат в переменную $my_data
    // остальные действия с результатом..
  }
  return $my_data;
}

Самая важная вещь, на которую нужно обратить внимание в этом примере, это переменная $my_data. Первым делом мы пытаемся инициализировать ее вызовом функции drupal_static(). Функция drupal_static() появилась только в Drupal 7, она предоставляет функциям "временное хранилище" для данных, которые должны быть доступны даже после выполнения функции. При первом вызове drupal_static() вернет пустоту, но любые изменения в этой переменной будут сохранены для следующего вызова этой функции. Таким образом мы можем проверить была ли уже установлена переменная и получить ее немедленно не выполняя при этом никакой дополнительной работы.

Этот шаблон можно встретить во многих местах в Drupal, включая такие важные функции как node_load(). Вызов node_load() для определенного ID материала делает запрос к базе данных только первый раз, далее результат сохраняется в статической переменной на протяжении всей загрузки страницы. Таким образом, чтобы отобразить нод, первый раз в списке, второй раз в блоке и, например, в списке ссылок похожих материалов не нужно обращаться к базе данных все три раза.

В Drupal 6, эти статические переменные создаются с помощью ключевого слова 'static' языка PHP вместо функции drupal_static(). Кроме того каждая функция использующая этот шаблон предоставляет переменную $reset, чтобы дать возможность модулям, при необходимости, обойти кэшированную информацию. В Drupal 7 этот подход тоже работает, но drupal_static() позволяет централизовать этот процесс. Если в модуле нужно получить свежую информацию можно вызвать функцию drupal_static_reset(), чтобы удалить все кэшированные данные.

Функции кэширования в Drupal

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

function my_module_function() {
  $my_data = &drupal_static(__FUNCTION__);
  if (!isset($my_data)) {
    if ($cache = cache_get('my_module_data')) {
      $my_data = $cache->data;
    }
    else {
      // Здесь делаем сложные вычисления и записываем результат в переменную $my_data
      // остальные действия с результатом..
      cache_set('my_module_data', $my_data, 'cache');
    }
  }
  return $my_data;
}

Этот вариант реализации функции также использует кэширование с помощью статических переменных, но кроме того тут добавлен еще один уровень кэширования - каэширование в базе данных. Drupal API предоставляет три ключевых функции с которыми вы должны быть знакомы: cache_get(), cache_set() и cache_clear_all(). Давайте разберемся как они используются.

После проверки статической переменной функция проверяет установлен ли Drupal кэш с соответствующим ключом. Если он установлен, мы записываем в переменную $my_data значение содержащееся в $cache->data. В связке со статическими переменными, при следующем вызове этой функции, на протяжении загрузки страницы не надо будет вызывать cache_get() второй раз.

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

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

Обновляем информацию вовремя

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

cache_clear_all('my_module', 'cache', TRUE);

Этот вызов удалит все кэшированные данные, ключи которых начинаются с 'my_module'.

Если вы хотите закэшировать данные, но в тоже время они должны быть относительно свежие, вы можете установить время истечения срока действия кэша в функции cache_set(). Например так:

cache_set('my_module_data', $my_data, 'cache', time() + 360);

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

Определяем место хранения кэшированных данных

Вы могли заметить, что в качестве третьего параметра в функции cache_set() мы передаем строку 'cache' - это имя таблицы в которую мы сохраняем кэшированные данные. Если вам нужно сохранять в кэш большие объемы данных вы можете создать для этого собственную таблицу и передавать ее имя в качестве третьего параметра. Это позволит вам работать с вашими кэшированными данными быстрее. Модуль Viwes использует такой подход чтобы полностью контролировать свои кэшированные данные.

Проще всего создать свою таблицу для кэширования в install файле вашего модуля используя hook_schema(). Здесь должны быть объявлены все таблицы создаваемые вашим модулем и для упрощения этого процесса можно использовать вспомогательные функции.

function mymodule_schema() {
  $schema['cache_mymodule'] = drupal_get_schema_unprocessed('system', 'cache');
  return $schema;
}

Используя функцию drupal_get_schema_unprocessed() в вышеприведенном коде мы получаем описание стандартной таблицы 'cache' и создаем ее клон с именем 'cache_mymodule'. Добавление приставки 'cache' к имени таблицы предназначенной для кэшированных данных - это стандартная практика, которая помогает держать таблицы для кэширования организованными.

Если вы хотите разгрузить свой сервер по максимуму, Drupal поддерживает использование альтернативных систем кэширования. Изменив одну строчку в вашем файле настроек settings.php вы можете переопределить реализацию стандартных функций cache_set(), cache_get() и cache_clear_all(). Очень распространена интеграция с системой кэширования memcached, но возможны и другие варианты (такие как файловое кэширование или PHP APC). При использовании альтернативных систем кэширования код ваших модулей не нуждается в изменениях.

Кэширование для генерируемого содержимого

В Drupal 7 широко используются массивы для генерации содержимого (renderable arrays). Модули могут объявлять элементы страницы такие как: блоки, таблицы, формы, и даже ноды, в виде структурированных массивов. Во время генерации содержимого страницы Drupal автоматически использует функцию drupal_render() для обработки таких массивов, также она автоматически вызывает функции темизации и другие вспомогательные функции. Генерация некоторых сложных элементов страницы может занимать много времени. С помощью добавления специального свойства #cache в массив вы можете сообщить функции drupal_render() о том что этот элемент нужно закэшировать и в дальнейшем получать из кэша.

$content['my_content'] = array(
  '#cache' => array(
    'cid' => 'my_module_data',
    'bin' => 'cache',
    'expire' => time() + 360,
  ),
  // Другие элементы страницы...
);

Свойство #cache содержит список параметров соответствующих параметрам передаваемым в функции cache_get() и cache_set(). Более подробно о том как работают массивы для генерации содержимого смотрите детальную документацию здесь the drupal_render() function on api.drupal.org.

Следует обратить внимание

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

Иногда стандартная система кэширования не совсем подходит для сохранения ваших данных, например, если вам надо объединять данные с помощью SQL запросов. В этом случае вам нужно использовать специфическое для вашего модуля решение. Например модуль VotingAPI использует одну таблицу для сохранения голосов и одну для подсчета результатов (среднего значения, суммы и т.д.) для быстрого доступа к этим данным.

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

Напутствие

Поздравляем, теперь вы знаете все о мощных инструментах кэширования, которые позволят ускорить время выполнения вашего кода!

Оригинальная статья: http://www.lullabot.com/blog/articles/beginners-guide-caching-data-drupal-7

Автор перевода: Денис Захаров

Поделись с друзьями: