Компонентный кеш и TaggedCache в 1С-Битриксе
Кеширование компонентов в Битриксе — тема, где легко получить работающий сайт, но легко же и нарваться на баг, когда кеш отдаёт старые данные. Разберём два подхода: обычный startResultCache с ручным ключом и TaggedCache с привязкой к инфоблоку.
Что такое обычный кеш компонента
Любой компонент в Битриксе можно кешировать через startResultCache:
if ($this->startResultCache(3600, 'page-' . $page)) {
// запрос к БД — выполняется только при кеш-МИССЕ
$rows = ElementTable::query()->exec()->fetchAll();
$this->arResult['ITEMS'] = $rows;
$this->endResultCache();
}
Ключ 'page-' . $page определяет файл кеша. При запросе с тем же ключом и в пределах TTL (здесь 3600 секунд) компонент берёт данные из файла, не обращаясь к БД.
Это работает, пока данные не меняются. Но как только кто-то меняет название записи в админке — кеш всё ещё отдаёт старое. Автоинвалидации нет: файл лежит и будет лежать, пока не истечёт TTL или пока вы руками не удалите кеш.
Что такое TaggedCache
TaggedCache — это дополнительный слой поверх обычного кеша. Он привязывает файлы кеша к «тегам» — системным событиям, которые ядро Битрикса генерирует при сохранении/удалении данных.
$taggedCache = Application::getInstance()->getTaggedCache();
$taggedCache->startTagCache($this->getCachePath());
$taggedCache->registerTag('iblock_id_' . $iblockId);
$taggedCache->endTagCache();
Когда в админке сохраняют элемент инфоблока, ядро автоматически ищет все файлы кеша, привязанные к соответствующему тегу iblock_id_, и удаляет их. При следующем запросе компонент попадёт в кеш-мисс и пересоздаст данные из БД.
Ключевой момент: TaggedCache не заменяет обычный кеш — он его дополняет. Оба вызова работают вместе: startResultCache создаёт файл кеша, а startTagCache/registerTag привязывают его к тегу.
Как это работает вместе: три примера
Небольшой блок на главной: слайдер новостей
Самый короткий и наглядный случай. Слайдер — это инфоблок, элементы которого меняются редко, но когда меняются (новая запись, правка текста), сайт должен сразу это показать.
if ($this->startResultCache((int)$this->arParams['CACHE_TIME'], 'slider')) {
$taggedCache = Application::getInstance()->getTaggedCache();
$taggedCache->startTagCache($this->getCachePath());
$taggedCache->registerTag('iblock_id_' . $iblockId);
$taggedCache->endTagCache();
$this->arResult['ITEMS'] = $this->getItems($iblockId);
$this->endResultCache();
}
Ключ 'slider' статический — все новости в одном кеше. Тег привязан к инфоблоку. Правка любой записи в админке сбрасывает кеш автоматически.
Постраничный список: компонент списка
Кеш привязан к номеру страницы — разные страницы списка живут в разных файлах:
if ($this->startResultCache($this->arParams['CACHE_TIME'], 'page-' . $page)) {
if ($iblockId > 0) {
$taggedCache = Application::getInstance()->getTaggedCache();
$taggedCache->startTagCache($this->getCachePath());
$taggedCache->registerTag('iblock_id_' . $iblockId);
$taggedCache->endTagCache();
}
// запрос к БД...
$this->endResultCache();
}
Ключ 'page-' . $page даёт отдельный файл для каждой страницы пагинации. Тег iblock_id_ привязан к инфоблоку: добавление, удаление или переименование записи сбрасывает кеш всех страниц списка сразу.
Детальная запись с защитой от кеш-ловушки
Здесь ключ — символьный код записи. Плюс важный нюанс: если записи с таким кодом нет, пустой кеш сохранять нельзя — иначе 404 «зависнет» до окончания TTL.
if ($this->startResultCache($this->arParams['CACHE_TIME'], 'code-' . $code)) {
if ($iblockId > 0) {
$taggedCache = Application::getInstance()->getTaggedCache();
$taggedCache->startTagCache($this->getCachePath());
$taggedCache->registerTag('iblock_id_' . $iblockId);
$taggedCache->endTagCache();
}
$row = ElementTable::query()->setFilter([...])->fetch();
if (!$row) {
$this->abortResultCache(); // не сохраняем пустой результат
$this->set404();
return;
}
$this->arResult['ITEM'] = $row;
$this->endResultCache();
}
abortResultCache() отменяет запись кеша. Без него пустой результат сохранится до истечения TTL: запись может уже появиться в БД, а посетители ещё час будут видеть 404.
Штатные компоненты инфоблоков: тег регистрируется самим ядром
У штатных компонентов — news.list, catalog.section, catalog и их вариаций — связка с тегами работает из коробки, без единой строчки кода:
if ($this->startResultCache($arParams['CACHE_TIME'], $this->getCacheId())) {
// Fetch() класса результата сам регистрирует тег при выборке
$rs = \CIBlockElement::GetList($arOrder, $arFilter, false, false, $arSelect);
while ($row = $rs->Fetch()) {
...
}
$this->endResultCache();
}
Сам по себе GetList() тег не регистрирует — это делает класс результата запроса при чтении строк. Внутри CIBlockResult::Fetch() для первого элемента инфоблока вызывается CIBlock::registerWithTagCache($iblockId), а та — CACHE_MANAGER->RegisterTag('iblock_id_'.$iblockId). Если выборка пустая, тег регистрируется по ID инфоблоков, переданных в фильтр. При сохранении элемента в админке ядро вызывает CIBlock::clearIblockTagCache() → ClearByTag('iblock_id_, который удаляет все файлы кеша с этим тегом. Механизм ровно тот же, что мы пишем руками, — просто для штатных компонентов его уже написали за нас.
Работает это только при включённом «управляемом кеше». При загрузке ядро (в bitrix/modules/main/include.php) определяет константу BX_COMP_MANAGED_CACHE, если её ещё никто не задал и опция component_managed_cache_on модуля «Главный модуль» не равна 'N' — по умолчанию она включена. Переключается она на странице «Настройки → Настройки продукта → Автокеширование», а при необходимости константу задают принудительно в bitrix/php_interface/dbconn.php — тогда ядро её не переопределит. Выключили управляемый кеш — штатные компоненты больше не инвалидируются, всё живёт только по TTL.
Обратная сторона этой магии — причина, по которой на многих сайтах кеш приходится сбрасывать целиком:
- Правки мимо админки теги не трогают. Прямой SQL, кастомные скрипты импорта, развёрнутая копия БД — ядро не узнает об изменении и не вызовет
clearIblockTagCache. Кеш продолжит отдавать старые данные до истечения TTL. - Правка шаблона сама по себе кеш компонента не ломает. Ключ результата строится из имени компонента, имени шаблона и параметров — время изменения файлов в него не входит (
getCacheID()). Но кеш результата хранит только данные (arResult), а шаблон исполняется при каждом обращении к странице. Старая «вёрстка» после правки дизайна — это верхние слои: композитный кеш отдаёт готовый HTML страницы по ключу-URL и правку шаблона просто не замечает. Такой кеш после правки вёрстки сбрасывают вручную либо ждут его автообновления. - Когда непонятно, что именно закешировалось, проще нажать в админке «Настройки → Настройки продукта → Автокеширование» → «Очистить файлы кеша» и выбрать вариант «все». Это снесёт и компонентные кеши, и
managed_cache, и html-кеш композита. Работает всегда, но заставляет сайт регенерировать всё подряд.
Инвалидация по тегу закрывает правки контента штатными средствами админки. Правки кода и оформления требуют сброса верхних кешей (готовый HTML страницы) — и это нормальная операция, просто делайте её прицельно: сбросить композит или managed cache, а не «все файлы» в автокешировании.
Главный подводный камень: тег пишется только на кеш-мисс
Это то, на чём горели не раз. Вызов startTagCache/registerTag находится внутри блока if (startResultCache(...) === true). Этот блок выполняется только при кеш-миссе — когда файла кеша ещё нет или он устарел.
Если файл кеша уже существует (кеш-хит), блок с тегом не выполняется — тег просто не записывается в таблицу b_cache_tag. А значит, при сохранении элемента в админке ядро не найдёт этот файл по тегу и не удалит его.
Конкретный кейс: добавили тег к компоненту списка, но кеш-файл был создан до правки кода (ещё без тега). Все последующие запросы попадали в кеш-хит → тег не регистрировался → админка сбрасывала тег iblock_id_, а файл кеша оставался на месте → список отдавал старые названия.
Решение: удалить кеш-файл один раз. Следующий запрос будет кеш-миссом, тег зарегистрируется, и дальше всё будет работать автоматически.
Убедиться, что тег привязался, можно так:
SELECT * FROM b_cache_tag WHERE TAG LIKE 'iblock_id\_%';
Подчёркивание в LIKE — это спецсимвол «один любой символ», поэтому в шаблоне его экранируют обратным слэшем (\_). Можно и проще — проверить конкретный инфоблок строгим равенством: TAG = 'iblock_id_5'.
Если в результате строки с RELATIVE_PATH, содержащим путь к компоненту — тег привязан корректно.
Когда тег НЕ нужен
Тег избыточен, когда данные не меняются через инфоблок:
- Статичные страницы — контент зашит в шаблон или компонент, инфоблока нет. Кеш живёт по TTL, а при редактировании шаблона вы и так почистите кеш вручную.
- Внешнее API — данные закешированы на час из-за ограничений стороннего сервиса. TTL = единственный механизм инвалидации.
- Один раз читающиеся настройки — опции модуля, конфигурация. Меняются в админке дважды в год, TTL 24 часа.
Когда тег ОБЯЗАТЕЛЕН
- Любой инфоблочный контент — новости, блог, каталог товаров.
- Списки с отображением NAME/CODE — переименование записи в админке должно мгновенно отражаться на сайте.
- Детальные страницы, где запись может появиться/исчезнуть — в паре с
abortResultCache. - Страница собирается из нескольких инфоблоков — зарегистрируйте несколько тегов:
$taggedCache->registerTag('iblock_id_' . $newsIblockId);
$taggedCache->registerTag('iblock_id_' . $catalogIblockId);
Тогда правка в любом из инфоблоков сбросит кеш.
Чек-лист
- Данные из инфоблока —
startResultCache+ тегiblock_id_. - Добавил тег к существующему компоненту — удали кеш-файл один раз, чтобы сработал регистр тега.
- Запись может отсутствовать —
abortResultCache()передset404(). - Статичный контент или внешнее API — только
startResultCache, тег не нужен. - Несколько инфоблоков на одной странице — зарегистрируй несколько
registerTag.