{ "index": 185, "slug": "editorial-2022-11-mechanism-bitrix-performance", "title": "Почему кешированный компонент Bitrix не гарантирует быструю страницу", "excerpt": "Медленная Bitrix-страница начинается не с размера JavaScript. Разберите маршрут, компонент, ключ кеша и шаблон, чтобы найти измеряемую причину и не нарушить выдачу для разных состояний пользователя.", "contentHtml": "

Каталог открывается заметно дольше обычного. В DevTools виден большой HTML, сервер возвращает ответ без явной ошибки, а команда спорит о размере JavaScript. Через час отключают кеш компонента. Время почти не меняется, зато база получает больше запросов. Пользователь по-прежнему ждёт, а команда теряет защиту от повторной работы.

\n

Цена ошибки здесь двойная. Неверная оптимизация не убирает задержку. Неполный ключ кеша может отдать одному посетителю HTML, рассчитанный для другого состояния. Поэтому фраза «компонент закеширован» ничего не говорит о полной скорости страницы и сама по себе не доказывает корректность результата.

\n

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

\n

Механизм: что делает встроенный кеш компонента

\n

CBitrixComponent::StartResultCache отвечает за ветку компонента. При действительном кеше метод возвращает false и использует сохранённый результат. При недействительном кеше он возвращает true, после чего компонент получает данные и подключает шаблон. Документация называет базовые части зависимости: SITE_ID, имя компонента, имя шаблона и входные $arParams. Дополнительное условие передают отдельно через второй параметр.

\n

Этот контракт описывает решение компонента. Он не сообщает, сколько занял PHP, какой запрос выполнила база, прочитался ли файл кеша и сколько времени занял браузер. Вызов с ожидаемой веткой повторного использования тоже не равен наблюдаемому cache hit на конкретном HTTP-запросе. Для такого вывода нужен отдельный артефакт: профиль, лог или другой разрешённый инструмент среды.

\n

SetResultCacheKeys решает соседнюю задачу. Метод определяет, какие части $arResult сохраняются при встроенном кешировании. Если его не вызвать, ядро сериализует весь результат. Это влияет на объём и состав сохранённых данных. Метод не добавляет отсутствующее условие в ключ и не доказывает, что шаблон перестал влиять на HTML.

\n
Четыре границы, которые нельзя смешивать
СлойЧто фиксируемЧего это не доказываетПервое действие
МаршрутURL и одинаковый вариант входаКакой PHP или SQL исполнилсяПовторить один вход
КомпонентИмя и фактические параметрыЧто он единственный источник задержкиНазвать владельца
КешРежим и полный список зависимостейРеальный hit-rate и время чтенияСверить ключ с output
ШаблонИмя и данные, которые он выводитЕго длительность в миллисекундахВыбрать измерение границы
\n

Как неполный ключ ломает результат

\n

Представим каталог, где блок цены зависит от сегмента посетителя. Компонент получает IBLOCK_ID, сортировку и постраничность. Эти параметры входят в $arParams. Сегмент хранится отдельно и влияет на HTML: один посетитель видит персональную цену, другой — обычную.

\n

Если сегмент не участвует в зависимости, два разных результата становятся одним кешированным результатом. Первый запрос создаёт HTML. Следующий запрос может получить его, хотя условие вывода изменилось. Очистка кеша исправит уже сохранённое значение только временно. При следующем построении ошибка повторится. Увеличение TTL делает риск дольше, а отключение кеша маскирует его ценой дополнительных запросов.

\n

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

\n
\"Схема
Схема показывает границы контракта. Она не является профилем сервера и не показывает реальный cache hit.
\n

Учебный пример на PHP

\n

Пример показывает безопасную форму проверки. Он не измеряет production, не обращается к Bitrix и не утверждает, что конкретный URL использует этот путь. В реальном компоненте состав $extraCacheId нужно получить из фактических условий, которые меняют HTML.

\n
$extraCacheId = implode(':', [\n    (string) $visitorSegment,\n    (string) $priceMode,\n]);\n\nif ($this->StartResultCache(false, $extraCacheId)) {\n    $arResult = loadCatalogItems($arParams);\n\n    if ($arResult['ITEMS'] === []) {\n        $this->AbortResultCache();\n    } else {\n        $this->SetResultCacheKeys(['ITEMS', 'SECTION_ID']);\n        $this->IncludeComponentTemplate();\n    }\n}
\n

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

\n

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

\n

Симптом → причина → проверка → действие

\n
Диагностическая матрица для одной страницы
СимптомПричинаПроверкаДействие
Отключение кеша не ускорило ответЗадержка находится в другом слоеРазделить TTFB, PHP, SQL, HTML и браузер разрешённым профилемВернуть кеш и измерить названную границу
Разные посетители видят один вариант блокаУсловие HTML не вошло в ключСравнить output contract и дополнительные параметрыДобавить зависимость или временно запретить кеширование
Кеш большой и медленно обновляетсяВ $arResult сохраняются лишние веткиПроверить вызов SetResultCacheKeys и данные шаблонаОставить только нужные ключи после проверки шаблона
После очистки проблема возвращаетсяИсправлен симптом, а не контрактПовторить сценарий после нового построенияНайти источник меняющегося HTML
Страница стала другой после замены шаблонаСравнение сделано с другим outputСопоставить входы, шаблон и сегмент посетителяОткатить один diff и начать сравнение заново
\n

Порядок проверки

\n
  1. Зафиксируйте один маршрут, параметры запроса и состояние пользователя. Не меняйте URL, сегмент и пагинацию между сравнениями.
  2. Назовите компонент и шаблон. Найдите место вызова StartResultCache, второй параметр и вызовы SetResultCacheKeys.
  3. Составьте список всех значений, которые могут менять HTML. Отдельно отметьте источник каждого значения.
  4. Сверьте список с $arParams и дополнительным идентификатором кеша. При пропуске не меняйте TTL и не очищайте весь кеш как «проверку».
  5. Выберите один наблюдаемый артефакт для одной границы: разрешённый профиль, лог, SQL trace или замер ответа. Запишите условия замера.
  6. Измените один параметр или один шаблон. До изменения назовите rollback: прежний шаблон, прежний ключ или прежняя настройка.
  7. Повторите тот же сценарий с теми же входами. Сравните корректность HTML и выбранный показатель, а не только субъективное ощущение.
  8. Если зависимость не доказана, верните изменение и оформите недостающий факт. Остановка — результат проверки, а не неудача.
\n

Ограничения

\n

Встроенный кеш компонента ускоряет только ту работу, которую он действительно может повторно использовать. Он не устраняет медленный SQL, тяжёлый PHP, большой ответ, блокирующий ресурс браузера или внешний API. Страница может иметь несколько компонентов с разными ключами и сроками жизни. Один успешный компонентный кеш не описывает весь запрос.

\n

Нельзя выводить production-результат из учебного кода. Без замера не называйте миллисекунды, процент ускорения, hit-rate и экономию ресурсов. Без проверки выдачи не объявляйте ключ полным. Без владельца значения не добавляйте его в зависимость наугад: случайный ключ ухудшит повторное использование и усложнит invalidation.

\n

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

\n

Критерий готовности

\n

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

\n

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

\n

Проверяемые источники

" }