Компонентный кеш и TaggedCache в 1С-Битриксе

Компонентный кеш и 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.