diff --git a/editorial/agent-rewrites/185.json b/editorial/agent-rewrites/185.json index 1e14bde..d8489ee 100644 --- a/editorial/agent-rewrites/185.json +++ b/editorial/agent-rewrites/185.json @@ -1,7 +1,7 @@ { "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

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

" + "title": "Производительность Bitrix-страницы: модель, ограничения и границы", + "excerpt": "Разбираем контракт встроенного кеша Bitrix: какие части ключа названы, где заканчивается документация API и почему ветка учебной модели не равна наблюдению сервера.", + "contentHtml": "

Фраза «компонент закеширован, значит страница быстрая» ломается сразу в двух местах. Она смешивает контракт компонента с итогом всего запроса и объявляет реальную производительность без наблюдения. Цена ошибки — опасные решения: в ключ не попадает условие, влияющее на HTML, шаблон перестают проверять, а следующий симптом снова объясняют кешем. Команда теряет корректность выдачи и не получает ответа, где именно возникла задержка.

\n

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

\n

Что действительно говорит API компонента

\n

В архивной документации CBitrixComponent::StartResultCache второй параметр описан как additionalCacheID. Базовая зависимость включает SITE_ID, имя компонента, имя шаблона и входные $arParams; дополнительное условие передают отдельно. В том же контракте true означает, что результат нужно сформировать, а false — что найден действительный кеш.

\n

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

\n

SetResultCacheKeys решает соседнюю задачу: определяет, какие части $arResult нужны при встроенном кешировании. Этот метод не добавляет отсутствующее условие в ключ и не доказывает, что шаблон перестал влиять на HTML. Сначала нужно назвать компонент, результат и данные, которые действительно используются при выводе.

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

Ключ — это часть публичного поведения

\n

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

\n

Практическое правило ревью: каждый фрагмент HTML либо одинаков для всех состояний ключа, либо имеет названную зависимость. Если это не доказано, ключ считаем неполным. Такой stop condition останавливает попытку «ускорить» страницу до того, как один вариант не окажется показан другому посетителю. Сначала корректность выдачи, затем экономия работы.

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

Проверяемый пример: пропущенный вход меняет решение

\n

Fixture работает не с Bitrix, а с учебным объектом createTeachingRequest. В полном варианте есть visitor-segment; в неполном он отсутствует среди объявленных частей, хотя требуется для output contract. Модель возвращает stop-incomplete-cache-contract и блокирует замену шаблона. Это не эмуляция StartResultCache, не генерация HTML и не тест платформы. Она проверяет только правило: неизвестную зависимость нельзя замаскировать оптимизацией.

\n
import { runBitrixPerformanceFixture } from './upgrade-2022-11.mjs';\nconst report = runBitrixPerformanceFixture();\nif (!Object.values(report.assertions).every(Boolean)) {\n  throw new Error('fixture contract failed');\n}\nconsole.log(report.incompleteReport.nextAction);\n// name-missing-cache-input-before-changing-cache\nconsole.log(report.planned.rollback);\n// catalog-grid
\n

Язык результата здесь намеренно строгий. declared-cache-reuse не называется hit, а declared-cache-build не называется miss: fixture знает только объявленные входы учебной модели. Первый объект позволяет запланировать замену владельца шаблона и сохраняет catalog-grid как rollback. Второй останавливает изменение и называет недостающий вход. Для отчёта о production всё равно потребуется новый источник.

\n

Маршрут: симптом → причина → проверка → действие

\n
  1. Симптом. Зафиксируйте один маршрут и один видимый признак без объяснения причины: «страница долго отвечает» или «разные состояния получают один блок».
  2. Гипотеза. Разложите её на request, component, cache и template. Одним наблюдением нельзя подтвердить сразу четыре слоя.
  3. Проверка контракта. Назовите компонент, шаблон, параметры и внешние условия, влияющие на HTML. Сверьте их с документированным API.
  4. Один артефакт. Выберите разрешённый исходник, лог, профиль или замер, который различает две гипотезы. Не создавайте trace из догадки.
  5. Малое действие. При полном перечне меняйте один владеемый параметр или шаблон и заранее назовите rollback. При пропуске остановитесь.
  6. Повтор. С теми же входами снова проверьте корректность HTML и выбранный показатель. Новый маршрут или условие — новый случай.
\n

Почему шаблон и invalidation нельзя склеивать

\n

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

\n

Курс Bitrix различает компонентное, неуправляемое и управляемое кеширование. Это не означает, что режим обновления сам определит все смысловые зависимости HTML. На ревью держите два списка: что делает результат другим и когда результат должен обновиться. Первый описывает ключ, второй — invalidation. Смешивание списков порождает ложные решения: срок жизни подменяет зависимость, а очистка кеша подменяет исправление output.

\n

Rollback должен быть частью первого diff

\n

Хороший rollback возвращает конкретное изменение: прежний параметр компонента, прежнего владельца шаблона или ранее названную часть ключа. Он не обещает откатить всю платформу. В учебной модели rollback возвращает catalog-grid и снова выдаёт только объявленный evidence без длительности и SQL. Это проверка формы решения, а не измерение продукта.

\n

Если после изменения появился другой output или новое условие, старое сравнение использовать нельзя. Оформите новый контракт явно. Не смешивайте rollback с очисткой всего кеша: она меняет контекст и может скрыть дефект вместо его объяснения. Узкий обратимый diff проще проверить, отменить и передать следующему владельцу.

\n

Ограничение и следующий шаг

\n

Эта статья не содержит production-кейса, реального пользователя, срока кеша, hit-rate или метрики. visitor-segment — учебное условие, а не свойство конкретного сайта. Источники подтверждают только описанные API и виды кеширования; они не доказывают поведение произвольного самописного компонента и не заменяют документацию его версии.

\n

Следующий шаг — выбрать один компонент и написать его output contract: какие данные меняют HTML, что формирует шаблон, что живёт в $arParams, что приходит извне и как выглядит rollback. Только после этого выбирайте инструмент наблюдения. Если зависимость не доказана, результатом проверки должна быть остановка или сбор недостающего факта, а не объявление страницы быстрой.

\n

Историческая граница ноября 2022

\n

Ниже использованы официальные страницы 1С-Битрикс в архивных снимках июня, августа и сентября 2022 года. Текущие плавающие формулировки не выдаются за свидетельство состояния ноября 2022. Учебная fixture поверх документов остаётся локальной моделью и не превращается в отчёт о настоящем сервере.

\n

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

" }