Сегменти кешу, програмна робота з кешем в Drupal 7

23.09.2016
Сегменти кешу, програмна робота з кешем в Drupal 7
Автор:

Сьогодні ми знову пропонуємо вам блог одного з наших Drupal-розробників, а це означає багато важливих деталей та практичних порад — цього разу щодо кешування в Drupal 7. Сьома версія Друпала зберігає свої позиції, тож багато розробників обирають її для побудови сайтів, та й для написання блогів ;) У нас є статті про створеннія CTools попапів, роботу з модулем Book, побудову додатків з PhoneGap, важливі інструменти для розробника в Drupal 7, налаштування пошуку з ApacheSolr і багато іншого в рубриці програмування. Але просто зараз час зануритися в тонкощі кешування! ;)

Кеш — це інструмент веб-розробника, що дозволяє користувачеві отримати запрошені дані з максимальною швидкістю і з найменшими системними затратами. По суті це проміжний буфер, в якому зберігаються дані, до яких часто звертаються. Як на практиці реалізується цей буфер? Зазвичай він являє собою таблицю в базі даних з уже готовими даними, або замість таблиці часто використовується збереження даних у файлі.

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

Виходить, що будь-який раз, коли користувач заходить на сторінку, він чекає 30 секунд, аби отримати бажаний результат. Проте, скажете ви, чому б після першого виконання функції, результат її обробки не зберегти і всім наступним просто показувати його, оминаючи довге виконання функції? Себто дані 30 секундного виконання ми просто запишемо у вище описаний "проміжний буфер" і щоразу будемо миттєво їх віддавати користувачу — це і є загальна філософія кешування.

В Друпал існує ряд уже готових інструментів, що дозволяють маніпулювати даними та кешувати їх за певними алгоритмами. Саме ці інструменти та способи їх застосування ми і розглянемо нижче.

Як говорилось, кеш Друпала зберігається в таблиці бази даних. Проте наша система керування контентом розподіляє кеш і ділить її на певні сегменти, кожен з яких представляє в базі свою окрему таблицю з індивідуальною назвою. Чому відбулась ця сегментизація, та які переваги ми отримуємо від такої системи реалізації? Розглянемо ці сегменти та наведемо приклади практичного їх застосування.

Сегменти кешу

Стандартно кожен сегмент складається з 5 полів:

$cid — кеш ідентифікатор (ID) даних в сховищі. Можна використовувати будь-яку послідовність символів довжиною не більше 255. Рекомендовано починати ідентифікатор з назви модуля.

$data — з назви зрозуміло, що тут якраз і будуть зберігатись наші дані. Складні типи даних будуть автоматично серіалізуватись.

$expire — (optional) Час життя кешу. Може бути наступних значень:

  • CACHE_PERMANENT: вказує, що дані ніколи не повинні бути видалені. Крім випадку коли явно вказане ID того елемента при виклику функції cache_clear_all().
  • CACHE_TEMPORARY: вказує, що елемент повинен бути видалений при наступному загальному очищенні кешу.
  • UNIX — мітка часу: вказує час життя елементу в UNIX форматі. Після чого він веде себе як CACHE_TEMPORARY.

$created — час збереження кешу в Unix форматі.

$serialized — прапорець (0 або 1), що вказує, були(1) чи ні(0) кешовані дані серіалізовані. Існують такі функції для роботи з кешем.

cache_clear_all() — очищує дані з кешу.

cache_get() — повертає дані з кешу.

cache_get_multiple() — повертає дані з кешу, з масиву ідентифікаторів кешу.

cache_is_empty() — перевіряє, чи є кеш порожній.

cache_set() — зберігає дані в кеші.

_cache_get_object() — отримує об'єкт кешу по сегменту($bin).

Наприклад:

 function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
     	return _cache_get_object($bin)->set($cid, $data, $expire);
}

Вертаючись до сегментів:

1. {cache} — (default) сегмент для зберігання загального кешу. Зазвичай використовується для реєстру теми, локальних дат, списків SimpleTest тестів і т.д. Або для даних, класифікація яких неможлива, чи створення для них окремого сегменту не має сенсу. Часто використовується для кешування даних з кастомних модулів.

2. {cache_block} — додається при включенні модуля Block. Працює наступним чином — коли відбувається завантаження регіону теми, проводиться завантаження даних по всіх блоках і перевірка на предмет побудови його з кешу, само собою хук hook_block_view() виконуватись не буде, якщо кеш для блоку заданий. Запит до {cache_block} вертає уже відрендерений блок. Не варто забувати і про параметри кешування при створення блоку через hook_block_info(), де опційно можна задати один з наступних параметрів:

  • DRUPAL_CACHE_PER_ROLE (за замовчуванням): Блок може змінюватися в залежності від ролі користувача і наданим йому прав.
  • DRUPAL_CACHE_PER_USER: Блок може змінюватися в залежності від користувача при перегляді сторінки. Проте цей параметр може бути ресурсомістким для сайтів з великою кількістю користувачів, і його слід використовувати тільки тоді, коли DRUPAL_CACHE_PER_ROLE недостатньо.
  • DRUPAL_CACHE_PER_PAGE: Блок може змінюватися в залежності від сторінки що переглядається.
  • DRUPAL_CACHE_GLOBAL: Блок є однаковим для кожного користувача на кожній сторінці, де його видно.
  • DRUPAL_CACHE_CUSTOM: Модуль реалізує свою власну систему кешування.
  • DRUPAL_NO_CACHE: Блок не повинен кешуватися. Ну і при створенні блоку йому виставляється прапорець CACHE_TEMPORARY, тобто дані будуть видалені при наступному загальному очищенні кешу.

3. {cache_bootstrap} — даний сегмент кешу відповідає за дані ініціалізації Drupal (списки модулів, змінних, хуків включених в системі і тд.) Для даного сегмента існують наступні ідентифікатори ($cid) даних в сховищі:

  • bootstrap_modules — список модулів, які імплементують хуки початкового завантаження (завантажуються раніше інших модулів).
  • hook_info — список всіх хуків, що доступні для імплементації.
  • lookup_cache — список класів, що динамічно завантажуються, та файлів, в яких вони зберігаються. module_implements — список хуків разом з модулями, в яких є реалізація цих хуків.
  • system_list — список увімкнених модулів і тем разом з залежностями.
  • variables — кеш змінних (таблиця "variables").

4. {cache_field} — в даному сегменті зберігаються дані по всіх полях. ID ($cid) формується за правилом field:тип_сутності: id_сутності.

Наприклад, для полів таксономії з tid рівним 14 : field:taxonomy_term:14

Тут зберігається набір всіх полів та їх значень, що відносяться до даної сутності.

5. {cache_filter} — сегмент, створений модулем Filter(ядро), що відповідає за фільтрацію текстових форматів. Тобто для текстів, можна створити уже готовий кеш по його MD5 хешу. Наприклад, функція check_markup() по SHA-2 хешу тексту перевіряє, чи є в кеші сегменту {cache_filter} уже відфільтрований текст, і при позитивному результаті вертає його. Якщо відфільтрованого тексту в кеші немає, то завантажується список включених фільтрів поточного формату введення і текст буде оброблятись згідно налаштувань фільтру, що веде за собою певну затрату часу. Тому по можливості викликаючи check_markup(), варто користуватись кешуванням.

6. {cache_form} — сегмент кешування побудови форм. За своєю логікою відрізняється від вищеописаних сегментів і застосовується не для збільшення продуктивності, а швидше для безпеки і уникнення виникнення вразливостей при побудові форм з допомогою FORM API. Ця таблиця має властивість стрімко рости (час життя 6 год вказано в ядрі includes/form.inc), тому якщо на сайті чимало форм плюс велика відвідуваність, варто подумати над налаштуванням крону (чи інших кастомних рішень) для періодичного очищення даного сегменту.

7. {cache_image} — сегмент зарезервований модулем Image. Як такого кешу зображень в ньому нема і в основному таблиця служить для зберігання даних про маніпуляції з картинками.

8. {cache_menu} — сегмент, зарезервований модулем Menu, що служить для кешування і зберігання інформації про посилання зі всіх меню, створених через інтерфейс Drupal. Сам ідентифікатор кешу формується наступним чином:

 $cid = 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($parameters));

Наприклад:

 links:main-menu:page:admin/reports/status:ru:1:1

9.{cache_page} — один з найважливіших сегментів кешування. Служить для зберігання кешованих сторінок для анонімних користувачів. У випадку, коли при побудові сторінки для такого користувача був знайдений кеш, виконається лише 2 хуки hook_boot() и hook_exit(), всі решта буду пропущені. Для включення цього кешу достатньо перейти admin/config/development/performance і включити відповідне кешування.

10. {cache_path} — сегмент, що відповідає за збереження відповідностей між системним шляхом та його аліасом.

11. {cache_update} — сегмент, резервований модулем Update manager. В даних кешу зберігається вся інформація по релізах для увімкнених модулів. Проте, сегмент виступає більше як таблиця збереження даних і на продуктивність не впливає. Обновлення таблиці відбувається лише у випадку отримання Drupal нових даних щодо релізів модулів.

Також додаткові сегменти створюють сторонні модулі (hacked, l10n_update, token, views). Серед них окремо варто згадати {ctools_object_cache} — сегмент кешу, що служить для зберігання великих об’єктів, що редагуються в даний момент. Саме тому замість поля $cid в цій таблиці знаходиться $sid(Session ID) — ідентифікатор поточної сесії користувача. Так само таблиця не має $expire, тому не стирається після очищення кешу через інтерфейс, проте чиститься раз в добу по крону. Прикладом може бути кеш сконфігуреного, та ще не збереженого views.

Узагальнюючи вище викладене, можна сказати, що сегмент кешування не завжди являє собою саме той кеш в розумінні буфера, про що говорилось з самого початку. Тому, знаючи кожен сегмент і його призначення, можна вміло володіти цим інструментом та вдало застосовувати на практиці.

Створюємо кастомні сегменти

А як щодо створення власного сегменту для індивідуальних потреб, наприклад, кастомного модуля? В цій ситуації нам треба буде просто клонувати стандартний сегмент {cache} і на його основі створити свій власний. Наприклад, створимо модуль “My module”, для якого “cache_my_module” буде нашим новим сегментом кешування.

Щоб клонувати таблицю cache, в my_module.install імплементуємо hook_schema() і додаємо свою таблицю.

 /**
 * Implements hook_schema().
 */
function my_module_schema() {
 
  $schema['cache_my_module'] = drupal_get_schema_unprocessed('system', 'cache');
  $schema['cache_my_module']['description'] = 'Cache table for My module.';
 
  return $schema;
}

Все — таблиця створена. Далі важливим кроком буде передбачити очищення таблиці при системному очищенні кешу. Для цього в Drupal існує ось такий хук — hook_flush_caches(). Застосуємо його. Для цього в файлі my_module.module пишемо:

 /**
 * Implements hook_flush_caches().
 */
function my_module_flush_caches() {
  return array('cache_my_module');
}

На прикладі функції нижче показано використання кешу. Спеціально створено ресурсомісткий цикл, який вираховує суму за певним правилом. При першому запуску ця функція виконалась за 5.85 с., іншим же разом і всіма наступними разами — за приблизно 0.001с.

 /**
* Example function.
*/
function my_module_cache_page() {
  timer_start('cache_module');
 
  $cache_id = 'cache_example_files_count';
  $sum = 0;
 
  if ($cache = cache_get($cache_id, 'cache_my_module')) {
	//якщо кеш по ідентифікатору є, тоді присвоюємо йому дані
	$sum = $cache->data;
  }
  else {
	//проводимо деякі ресурсомісткі обрахунки
	for ($i = 0; $i < 30; $i++){
      $files_count = count(file_scan_directory('.', '/.*/'));
  	$sum += $files_count * $i;
	}
   //записуємо наші обрахунки в кеш
    cache_set($cache_id, $sum, 'cache_my_module', REQUEST_TIME + 15 * 60);
  }
  //виводимо затрачений час
  $timer = timer_read('cache_module') / 1000;
  print $sum . ' We got a sum in = ' . $timer . 'seconds.';
}

Як видно, різниця досить значна. Однак, при значних навантаженнях на сайт варто зосередити увагу на мінімізації звернень до бази даних. А тому постає питання про перенесення кешу за межі БД.

Винесення сегментів за межі БД

Зробимо це з допомогою, наприклад, Memcached. Memcached в першу чергу має бути встановлений на сервері. Встановити треба або самому, якщо це виділений сервер, або попросити в хостера. Далі нам необхідно буде перенести всі сегменти кешування. Для цього нам допоможе уже готовий модуль Memcache Storage. Перед встановленням треба зв’язати функціонал модуля з демоном Memcached на сервері. Робиться це додавання наступних рядків коду у settings.php:

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

Тепер можна увімкнути модуль. Та слід зауважити, що Memcached працює, використовуючи оперативну пам’ять. А ми пам’ятаємо про сегмент кешу {cache_form}, що може бути дуже значний в розмірах, тим паче при великій кількості користувачів. Виходить, ці дані зберігати в оперативній пам’яті буде не зовсім коректно. Тому наступними рядками скажемо Друпалу, щоб кеш даного сегменту повернув нам назад в базу:

 $conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

Існує ще кілька розповсюджених практик ефективного використання Memcached. Оскільки ми і так перенесли кеш в оперативну пам’ять, сенсу звернення Друпала до бази при побудові сторінки на фазі бустрапа нема, тому:

// відправляємо кеш сторінки в оперативну пам’ять
$conf['cache_class_cache_page'] = 'MemcacheStorage';
// деактивуємо підключення до БД
$conf['page_cache_without_database'] = TRUE;

Крім того, існують функціонали, що напряму зачіпають БД з кешем, так як на стадії бустрапу все одно викликаються хуки hook_boot() і hook_exit(). Поправимо і це:

 $conf['page_cache_invoke_hooks'] = FALSE;

Крім Memcached можна використовувати інші рішення. Наприклад APC, Boost, Varnish, XСache, Redis. І всі ці системи можна використовувати одночасно, виносячи певні сегменти кешу в те чи інше рішення.

Уcпіху в роботі з сегментами кешу та кешуванням у Drupal 7!

2 votes, Рейтинг: 5

Також по темі

1

Згідно з найкращими традиціями респонсивного дизайну, сайт ідеально підлаштовується під екран будь-якого...

2

Зображення лазурних морських берегів, екзотичних островів і таємничих гір в інтернеті… Наче справжній магніт, вони приковують до себе погляди. Туристичні сайти бувають просто неймовірними! Тож ми...

3

Більшість з нас звикли максимально оптимізувати будь-який робочий процес, в чому нам допомагає велика кількість наявних технологій. Деяким з них (Drupal Composer шаблону і Phing) присвячений...

4

Давайте дізнаємося, завдяки яким рисам Друпал безпечний. Вам буде цікаво почитати про ці риси і дізнатися поради щодо їх вірного застосування, якщо ви лише розглядаєте можливість створення сайту...

5

Обирати хостинг-провайдера для Drupal-сайту може бути непростою задачею, адже інтернет переповнений пропозиціями на хостинг-послуги. Ваш Drupal-сайт — наче корабель, який шукає найбезпечнішу...

Subscribe to our blog updates