Вопрос продвинутым знатокам Cache API

Главные вкладки

Аватар пользователя marassa marassa 30 июня в 13:12

Так уж вышло, что мне нужно по одному и тому же URL генерировать немножко разные страницы в зависимости от наличия/отсутствия некоей куки. В результате пришлось отключить Internal Page Cache, так как закэшится первый запрошенный вариант страницы и будет далее выдаваться всем независимо от куки. Сайт по мере роста базы данных стал ощутимо тормозить, и очень хочется всё же найти способ задействовать страничный кэш.
Наткнулся на такую статью в которой предлагается простой способ модифицировать ядерный механизм кэширования, добавив нужную куку в Cache ID, то есть страница с кукой и страница без куки будут считаться разными страницами и соответственно кэшироваться раздельно, что мне и нужно.
Всё прочитал и понял, кроме последнего пассажа:

And the last thing is to add the cache context to your cache array

$build['#cache']['contexts' ][] = 'cookies:my_cookie_name';

Вопрос: КУДА этот код добавить-то? Наверняка ответ очевиден, но я туплю, а автор статьи не торопится отвечать.

Заранее премного.

Комментарии

Аватар пользователя OldWarrior OldWarrior 1 июля в 2:00

Ключевое слово тут $build. Т.е. этот элемент добавляется просто к любому возвращаемому из какого-то контроллера (страницы или формы) рендер-массиву.

Для контроллеров страниц возвращаемый массив обычно именуют $build, для форм - $form. В данном примере, судя по всему, контроллер страницы.

Собственно, ключ кеширования может применяться не только к верхнему уровню рендер-массива (например формы - $form), но индивидуально (и независимо от общего ключа) к отдельным её элементам, например, какому-то полю:

<?php
$form
["boxberry_address"]["#cache"] = [
      
"contexts" => [],
      
"tags" => [],
      
"max-age" => 0// Для примера отключаем кеширование только элемента "boxberry_address" в текущей форме.
  
];
?>

Т.е. тут вопрос в том, что именно хотите кешировать в выводимых данных.

Аватар пользователя marassa marassa 1 июля в 6:19

OldWarrior wrote: Т.е. тут вопрос в том, что именно хотите кешировать в выводимых данных.

Насколько я понимаю, Internal Page Cache кэширует целиком страницу. То есть в самом начале работы контроллера страницы нужно проверить нет ли уже в кэше страницы с этим адресом И ЭТОЙ КУКОЙ, и если есть, то просто отдать из кэша. А если нет, и дело дошло до рендеринга страницы, то после рендеринга нужно положить свежеотрендеренную страницу в кэш, причем с id, включающим куку.
Пока писал, осознал, что это ж у меня в БД будут храниться десятки тысяч готовых страниц. Может не зря Друпал рекомендует Internal Page Cache for small to medium-sized websites? Может всё же Varnish освоить? Там поддержка кук из коробки есть.

Аватар пользователя OldWarrior OldWarrior 1 июля в 11:05

marassa wrote: То есть в самом начале работы контроллера страницы нужно проверить нет ли уже в кэше страницы с этим адресом И ЭТОЙ КУКОЙ, и если есть, то просто отдать из кэша. А если нет, и дело дошло до рендеринга страницы, то после рендеринга нужно положить свежеотрендеренную страницу в кэш, причем с id, включающим куку.

Вот как раз для того, чтобы этим не заниматься и служит контекст кеша. Достаточно сообщить рендер-массиву зависимость контекста (в вашем случае от кук, хотя она может быть и от ID сущностей или ID термина или от ещё каких-то иных уникальных значений) и система (точнее, классы в обвязке сервиса \Drupal::renderer()) сама привяжет отображение к уникальному вхождению кеша на основании этой зависимости.

marassa wrote: Пока писал, осознал, что это ж у меня в БД будут храниться десятки тысяч готовых страниц.

На самом деле не совсем страниц, а результатов рендеринга build-массивов. Т.е., например, только отрендеренное тело ноды. Но вообще - да, каждый указанный ключ контекста (а он может быть не по одному параметру, как в вашем случае) добавляет в кеш свою группу к общему набору ключей контекста (или только их пересечениями/совокупностям). Т.е. если содержимое ноды чувствительно и к кукам и к параметрам, например, GET, и вы указали эту зависимость в кеш-контексте, то в конечном итоге будут построены вхождения для всех трёх случаев (по мере их рендеринга): для уникальных кук, для уникального параметра GET и для уникальных связок куки + GET.

Грубое объяснение, конечно, но примерно показывает картину.

Аватар пользователя OldWarrior OldWarrior 1 июля в 11:15

PS. Опять же, мы рассматриваем типичный случай, когда build-массив представляет содержимое, отдаваемое контроллером. Но всё вышенаписанное справедливо и для любых других рендер-массивов, например, блоков.

Аватар пользователя marassa marassa 1 июля в 11:46

OldWarrior wrote: Вот как раз для того, чтобы этим не заниматься и служит контекст кеша.

Есть только один нюанс: документация по cache contexts в явном виде говорит:

Note the Internal Page Cache assumes that all pages served to anonymous users will be identical, regardless of the implementation of cache contexts. If you want to use cache contexts to vary the content served to anonymous users, this module must be disabled, and the performance impact that entails incurred.

То есть Internal Page Cache (не путать с Dynamic Page Cache, который у меня включён и что-то не особо ускоряет) вообще игнорирует контексты. Тогда зачем их добавлять в билд-массив? Весь остальной код в статье модифицирует функциональность именно Internal Page Cache (page_cache).

Аватар пользователя OldWarrior OldWarrior 1 июля в 17:30

marassa wrote: То есть Internal Page Cache (не путать с Dynamic Page Cache, который у меня включён и что-то не особо ускоряет) вообще игнорирует контексты. Тогда зачем их добавлять в билд-массив?

Я сейчас смутно припоминаю: году так в 2016, когда D8 был свеж, как утренняя булочка из пекарни, мы вроде как парились на одном проекте с аналогичным вопросом. Вроде что-то кешировалось неуместным образом для гостей в форме на фронте. Не помню точно, как решилось тогда, но кажется, действительно выключили модуль Internal Page Cache.

Если вернуться к исходной причине ваших поисков:

marassa wrote: Так уж вышло, что мне нужно по одному и тому же URL генерировать немножко разные страницы в зависимости от наличия/отсутствия некоей куки. В результате пришлось отключить Internal Page Cache, так как закэшится первый запрошенный вариант страницы и будет далее выдаваться всем независимо от куки.

Для анонимов можно просто установить max-age => 0 в параметрах кеша билд-массива, это должно работать и с включенным Internal Page Cache, если не ошибаюсь. Правда, для авторизованных кеш тоже отключится. Но тогда можно попробовать сообщать разные параметры кеширования для массива в зависимости от признака: авторизован или нет. Просто как первая мысль.

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

Аватар пользователя kosskren kosskren 3 сентября в 8:02
1

Мне в свое время очень помогла статья от Niklan, вот ссылка. В ней подробно разбирается тема кеширования.

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

На одном проекте в базе была таблица с 380 млн. записей. До добавления индексов, время выполнения запросов было от 10 сек до 3 минут в зависимости от запроса. После добавления индексов, время составило от 0.009 сек до 0,23 сек

Аватар пользователя marassa marassa 3 сентября в 12:26

Спасибо, что подняли тему - я так ничего и не сделал пока, даже и не помню почему бросил. Насчёт индексов тоже думал, но это ж надо анализировать запросы и понять каких именно индексов не хватает. Есть какая-то общепринятая методика для этого?

Аватар пользователя chei1ahJoh8K chei1ahJoh8K 3 сентября в 17:47

Вопрос ускорения сайта комплексный. Я занимался этим. Вот технологии ко орые помогли: mysql tuner, mytop, nginx reverce proxy cache. Ещё отключение ненужных модулей и рефакторинг кода.

Аватар пользователя kosskren kosskren 3 сентября в 22:11

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

Ну вот скажем условная таблица "продукты", в выражении where участвуют 2 столбца - "цена" и "наличие".
Значит можно попробовать добавить составной индекс.

CREATE INDEX цена_наличие ON продукты(цена,наличие);
цена_наличие - любое название индекса

Дальше смотрите, помогло или нет. Если индекс не помог, то можно удалить его. И так по новой.
Опыты лучше проводить на копии базы.

По хорошему конечно анализировать запрос через mysql explain.

Аватар пользователя chei1ahJoh8K chei1ahJoh8K 3 сентября в 23:24

Вот настройки mysql выловят медленные не тндексируемые запросы. Их надо будет проиндексировать.

# В файле /etc/mysql/my.cnf или /etc/mysql/mysql.conf.d/mysqld.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1