Композит изнутри - статическая копия, зоны, пересборка
Разбираем композит по слоям: из чего складывается статическая копия страницы и чем она отличается от кэша компонента. Дальше смотрим, как догружаются динамические зоны и почему копия пересобирается чаще ожидаемого.
Механика
Композит ускоряет ровно одну величину - время ответа сервера на запрос страницы. Разрешение имени, установка соединения и загрузка картинок остаются точно такими же, какими были. Поэтому вывод «страница стала вдвое быстрее» после включения почти всегда завышен.
Статическая копия - это готовый HTML-файл страницы в каталоге
/bitrix/html_pages/<домен>/. Кэш компонента хранит результат одного вызова, а
страницу вокруг него всё равно собирает платформа. Копия же уходит посетителю
целиком и раньше, чем ядро успевает стартовать.
Первое обращение идёт обычным путём: платформа собирает страницу от начала до
конца. На событии OnEndBufferContent динамические зоны заменяются заглушками,
добавляется загрузчик и считается контрольная сумма. Готовый HTML пишется на
диск, и только с этого момента копия существует.
Второе обращение уже получает её целиком, и порядок величин тут примерно пятнадцать миллисекунд против семисот пятидесяти на первом. Ответ «не изменилось» появляется лишь с третьего обращения: второе отдаёт дату изменения, третье присылает её обратно.
Страница делится надвое границами динамических зон. Всё между
$this->createFrame()->begin() и $frame->end() выполняется на каждом
обращении, а разметка вокруг попадает в общую копию. Вкладывать зоны друг в
друга платформа не разрешает.
Догрузка идёт фоновым запросом уже из браузера: загрузчик забирает свежее содержимое всех зон одним ответом. Сервер попутно пересчитывает контрольную сумму страницы и переписывает копию, если она разошлась с текущей сборкой.
«Моргание» зоны - прямое следствие такого порядка. Сначала посетитель видит
заглушку из копии, например счётчик корзины с нулём, и лишь потом приходят
настоящие данные. Сглаживают это заглушкой той же структуры и плавным
появлением, а мгновенный показ личного делают через BX.localStorage.
Каждый компонент страницы голосует за композит или против него. Компонент,
который нельзя кэшировать, ставит $this->setFrameMode(false), и одного такого
голоса хватает на отключение всей страницы. По умолчанию против голосуют
bitrix:lists, bitrix:sale.personal.subscribe и bitrix:search.page.
Пересобирается копия заметно чаще, чем ожидают. Достаточно случайного
идентификатора в разметке, подстановки адреса запроса в back_url, данных
сессии или разного содержимого для гостя и вошедшего. Перед каждой перезаписью
прежняя версия сохраняется рядом файлом с меткой времени.
Внутри кэшируемой области часть вызовов ведёт себя иначе. FrameBuffered
буферизирует содержимое, и отложенные функции вроде ShowTitle там не работают.
А bitrix_sessid_post() в кэшируемом шаблоне отдаёт пустое значение, которое
подставит уже JavaScript.
Шаги
- Замерить сборку страницы без композита, добавив к её адресу отладочный параметр
ncc=1. - Сравнить этот замер со вторым обращением к той же странице, у которой копия уже готова.
- Пройти по разметке глазами гостя и вошедшего покупателя, выписывая все различающиеся места.
- Проверить голосование компонентов страницы и найти шаблоны, которые голосуют против кэширования.
- Посмотреть частоту перезаписи копии по числу соседних файлов с меткой времени в имени.
Код
Смотрим статическую копию на диске:
ls -la /home/site/public_html/bitrix/html_pages/example.org/catalog/# page.html - сама копия, которую получают посетители# page.html.delete.1509433132.4885 - прежняя версия, сохранённая перед перезаписьюls /home/site/public_html/bitrix/html_pages/example.org/catalog/ | grep -c delete# столько раз копия этой страницы переписывалась с момента создания каталогаcurl -sI 'https://example.org/catalog/?ncc=1' | grep -i x-bitrix-composite# параметр ncc=1 отменяет композит: служебного заголовка в ответе не будетЧисло файлов с меткой времени в каталоге страницы прямо показывает, сколько раз копия переписывалась. Растущая пачка означает, что в разметку попало что-то нестабильное.
Размечаем границу личной зоны в шаблоне:
$frame = $this->createFrame()->begin('0'); // заглушка попадёт в статическую копию?><span class="cart-count"><?= $cartCount ?></span><?php$frame->end();// код между begin() и end() выполняется на каждом хите, а не при сборке копии// begin() без аргумента подставит заглушкой данные предыдущего хитаЗаглушка живёт в общей копии и приходит всем посетителям одинаковой. Настоящее значение догружается фоновым запросом, поэтому заглушку подбирают так, чтобы она не смещала соседние элементы.
Обходим ограничение буферизованной зоны:
$area = new \Bitrix\Main\Page\FrameStatic('workarea');$area->setAssetMode(\Bitrix\Main\Page\AssetMode::STANDARD); // стили и скрипты минуют копию$area->setAnimation(true); // плавное появление вместо рывка$area->setStub('загрузка');$area->startDynamicArea();$APPLICATION->IncludeComponent('mycompany:mycomponent', 'template', []);$area->finishDynamicArea();Зона без буферизации расставляет метки и забирает содержимое после выполнения страницы. Отложенные функции внутри неё работают, а условное подключение стилей перестаёт переписывать копию.
Разделяем содержимое зоны и заглушку:
$frame = new \Bitrix\Main\Page\FrameBuffered('cart-line');$frame->begin(); // содержимое зоны: приходит фоновым запросом на каждом обращении$frame->beginStub(); // заглушка: именно она попадает в статическую копию страницы$frame->end();// отложенные функции внутри буферизованной зоны не работаютРазделение выручает там, где заглушка сложнее одной строки текста. Всё до переключения на заглушку в копию не попадает, а всё после отдаётся посетителям одинаковым.
Убираем чужой голос против:
$cache = \Bitrix\Main\Data\StaticHtmlCache::getInstance();$cache->disableVoting();$APPLICATION->IncludeComponent('bitrix:search.page', '', []); // голосует «против»$cache->enableVoting();// свой компонент честнее пометить в его коде вызовом setFrameMode(false)Отключение голосования снимает запрет, но не делает содержимое компонента общим для всех. Личное внутри такого вызова по-прежнему обязано жить в динамической области.
Объявляем зону прямо в шаблоне сайта:
$frame = \Bitrix\Main\Page\Frame::getInstance();$frame->startDynamicWithID('header-auth');// личный блок шапки: имя посетителя, вход, ссылка на кабинет$frame->finishDynamicWithID('header-auth', '');// setAutoUpdate(false) вместе с setAutoUpdateTTL(60) отключает фоновый запросВ шапке и подвале объекта компонента нет, поэтому зону создают через одиночный экземпляр. Режим без фонового запроса подходит лендингам, на которых личного содержимого нет вовсе.
Убираем из разметки то, что переписывает копию:
// каждое из этих значений своё у посетителя и заставляет пересобрать копиюecho $USER->GetID(); echo bitrix_sessid_post(); echo rand(0, 9999);echo $_SERVER['HTTP_USER_AGENT'];echo '<input type="hidden" name="back_url" value="' . $_SERVER['REQUEST_URI'] . '">';// стабильные замены: BX.message('USER_ID'), BX.message('bitrix_sessid'),// метод $this->randString(), а разбор браузера - в коде компонентаКонтрольная сумма считается по всей странице целиком. Одно меняющееся значение в разметке возвращает копию к состоянию «пересобирается на каждом обращении».
Возвращаем обработчики после догрузки зоны:
if (window.frameCacheVars !== undefined) { // страница пришла из копии BX.addCustomEvent('onFrameDataReceived', function (json) { initSliders(); // события на элементах зоны потеряны });} else { BX.ready(initSliders); // обычная загрузка без композита}BX.addCustomEvent('onBeforeDynamicBlockUpdate', function () { /* зона вот-вот сменится */ });BX.addCustomEvent('onFrameDataRequestFail', function () { /* фоновый запрос не дошёл */ });Замена содержимого зоны выбрасывает старые узлы вместе с их обработчиками. Скрипт, привязанный к разметке зоны при загрузке страницы, после догрузки просто перестаёт работать.
Запрещаем копию для отдельной страницы:
\Bitrix\Main\Data\StaticHtmlCache::getInstance()->markNonCacheable();// вызов из любой части страницы: в статику она уже не попадётПрограммный запрет удобен там, где страница личная по устройству, а маску исключений выписывать не на что. Решение принимает сам код страницы, а не настройки модуля.
Чистим старые файлы по расписанию:
php -f /home/site/public_html/bitrix/modules/main/tools/cron_html_pages.php 10Задание удаляет файлы кеша старше десяти часов и держит каталог в разумном
объёме. Каталог html_pages руками не чистят: после ручного удаления приходится
отдельно приводить в порядок служебные файлы.
Ограничения
Ускоряется только время ответа сервера. Разрешение имени, соединение и загрузка стилей, скриптов и картинок остаются прежними, и метрики браузера показывают это честно.
Копия заводится на каждый адрес отдельно. Каталог с умным фильтром порождает
копию на каждую комбинацию параметров, и объём каталога html_pages растёт
быстрее ожиданий.
Права доступа копия не проверяет. Раздел, закрытый от неавторизованных посетителей, композитом не защищается: готовый HTML уходит раньше проверки доступа. Такие разделы выводят из композита масками исключений.
Мимо композита идут запросы не методом GET, адреса внутри /bitrix, запросы с
cookie _NCC или параметром ncc, а также вызовы через BX.ajax. Страницы с
админ-панелью не кэшируются, и это норма, а не поломка.
Хранение зоны в браузере через setBrowserStorage(true) рабочим считать больше
нельзя. Механизм опирался на Web SQL, а его убрали из современных браузеров, и в
Firefox его не было никогда.
Типичные проблемы
Счётчик корзины сначала показывает ноль, потом правильное число.
Заглушка зоны лежит в общей копии и приходит вместе со страницей мгновенно. Настоящее значение догружается фоновым запросом только после события готовности документа.
Слайдер и меню периодически перестают работать.
Догрузка зоны заменила её разметку целиком, а обработчики висели на старых узлах страницы. Их вешают заново по событию получения данных, а не при обычной готовности документа.
Композит выключился сразу на всей странице.
Один шаблон компонента голосует против кэширования, и этого хватает для отключения всей страницы. Виновника показывает отладочная копия рядом с файлом страницы в каталоге кеша.
Дисковая квота кончилась при отладке композита.
Перед каждой перезаписью платформа сохраняет прежнюю версию копии отдельным файлом с меткой времени. При частой пересборке такие файлы накапливаются пачками и съедают место незаметно.
Копия переписывается на каждом обращении к странице.
В разметку попало значение, своё у каждого посетителя: идентификатор сессии, гостевой номер или случайная строка. Контрольная сумма расходится всякий раз, и платформа честно пересобирает копию заново.
Вёрстка развалилась сразу после включения композита.
Динамическая область объявлена внутри головы документа, и её служебная обёртка встала посреди метатегов. Условные стили и скрипты подключают зоной без буферизации в стандартном режиме ассетов.
Частые вопросы
Композитный сайт и ускорение сайта - это одно и то же?
Нет, это разные технологии. Композит хранит готовый HTML страницы на своём сервере и ускоряет только время ответа.
Чем композит отличается от кэша компонента?
Кэш компонента хранит результат одного вызова, а страницу вокруг всё равно собирает платформа. Композит отдаёт готовую страницу целиком.
Почему ответ «не изменилось» приходит только с третьего раза?
Первое обращение создаёт копию, второе отдаёт дату изменения, третье присылает её обратно. Это штатный порядок, а не сбой.
Как понять, какой компонент голосует против?
По отладочной копии страницы рядом с файлом кеша. В ней перечислены голоса, и виновник виден по имени компонента.
Защитит ли композит закрытый раздел от гостей?
Нет, копия отдаётся без проверки прав доступа. Закрытые разделы выводят из композита масками исключений в настройках модуля.
Смежное
- Композит на практике - оглавление подтемы
- Композитный сайт - устройство технологии
- Композитный сайт: включение, динамические области, сброс - порядок включения на боевом сайте
- Композит не включается на странице: причины по убыванию частоты - разбор конкретного отказа
- Кэш не срабатывает: страница собирается заново каждый раз - соседний уровень кэша
- Правка не видна на сайте: какой кэш сбросить - порядок сброса по уровням
- Компонент изнутри: вызов, кэш, шаблон, эпилог - что кэширует сам компонент
- Шаблоны и композит - как это выглядит в шаблоне
- Кэширование - остальные уровни кэша
- Стили и скрипты в шаблоне: подключение, порядок, кэш - условные ресурсы в голове документа
- Мобильная версия: адаптив, отдельный шаблон, кэш по устройству - содержимое, зависящее от браузера