В Битрикс не один вид кэша, а пять разных механизмов, которые решают разные задачи и почти не пересекаются друг с другом. Путаница обычно начинается именно здесь: разработчик включает автокэш компонента, а тормозит совсем другой запрос, который автокэш не касается. Разбираю все пять по порядку — что кэширует, как включается, как сбрасывается.
Какие виды кэша есть в Битрикс
| Механизм | Что кэширует | Уровень |
|---|---|---|
| Кэш данных (CPHPCache / D7 Cache) | Результат произвольного PHP-кода — массив, объект, HTML-фрагмент | Код разработчика |
| Автокэш компонента | Результат и шаблон компонента целиком | Компонент |
| Тегированный кэш | То же самое, что кэш данных, но с точечным сбросом по тегу вместо TTL | Код разработчика / компонент |
| Композитный сайт | Готовая HTML-страница целиком | Страница |
| Хранилище (файлы / memcached / redis) | Не вид кэша, а то, где физически лежат данные всех кэшей выше | Инфраструктура |
Путь одного запроса — если на каком-то уровне кэш есть, ответ отдаётся сразу и ниже запрос не идёт:
Запрос
│
▼
Страница закэширована композитным сайтом? ──да──▶ отдаём готовую страницу
│ нет
▼
Компонент закэширован автокэшем (CACHE_TYPE)? ──да──▶ отдаём HTML компонента
│ нет
▼
Данные есть в кэше данных / тегированном кэше? ──да──▶ отдаём данные, рендерим шаблон
│ нет
▼
База данных
Дальше — по каждому пункту.
Кэш данных — CPHPCache и его D7-аналог
Базовый механизм: положить в файл результат тяжёлой операции и не пересчитывать его заново, пока не истечёт время жизни. Класс CPHPCache — рабочий, никуда не убран, но это API старого ядра. В D7 у него есть прямой аналог с тем же порядком вызовов — \Bitrix\Main\Data\Cache.
Пример на CPHPCache и разбор параметров ($sCacheID, $sCacheTime, BXClearCache()) — в старом посте про кэш. Здесь — как то же самое выглядит в D7:
// получаем объект кэша D7 — прямой аналог CPHPCache
$cache = \Bitrix\Main\Data\Cache::createInstance();
// идентификатор должен включать все параметры, влияющие на результат выборки
$cacheId = "news_list_" . $iblockId . "_" . $pageNum;
// папка кэша относительно /bitrix/cache/
$cachePath = "/news_list/";
// пробуем открыть кэш на 3600 секунд по этому ID и пути
if ($cache->initCache(3600, $cacheId, $cachePath)) {
// кэш валиден — забираем сохранённые ранее данные, в базу не ходим
$items = $cache->getVars();
} elseif ($cache->startDataCache()) {
// кэша нет или устарел — включаем запись нового кэша
$items = getNewsFromDb($iblockId, $pageNum);
// сохраняем результат в файл кэша под тем же ID
$cache->endDataCache($items);
}Методы initCache() / startDataCache() / endDataCache() / getVars() — тёзки методов CPHPCache, логика идентична. Разница только в пространстве имён (неймспейсе) и стиле вызова: методы вызываются у объекта ($cache->initCache(...)), а не статически через new CPHPCache.
Важное про $cacheId не меняется от версии к версии: в идентификатор должны попадать все параметры, от которых зависит результат выборки — но только подготовленные, типизированные значения, а не сырые данные из $_GET. Если в идентификатор подставить необработанный параметр запроса, кто угодно сможет «раздуть» кэш, гоняя мусорные значения — на диске накопится множество бесполезных файлов.
Автокэширование компонентов
У каждого компонента есть встроенный кэш, который включается не в коде, а в .parameters.php:
$arComponentParameters = [
'PARAMETERS' => [
// A — кэшировать, если глобально включено «Автокэширование компонентов»
'CACHE_TYPE' => ['DEFAULT' => 'A'],
// время жизни кэша в секундах
'CACHE_TIME' => ['DEFAULT' => 3600],
],
];CACHE_TIME — секунды. CACHE_TYPE — три значения, и здесь чаще всего путают A с Y:
N— компонент не кэшируется никогда.A(«авто») — компонент кэшируется, если в Настройки → Настройки продукта → Автокеширование (вкладка «Настройка кеширования компонентов») включена галочка глобально. Выключена галочка — компонент сCACHE_TYPE=Aработает так, как будто у негоN, независимо отCACHE_TIME.Y— компонент кэшируется всегда, глобальную галочку игнорирует.
Внутри компонента управлять кэшем можно двумя методами:
class MyComponent extends CBitrixComponent
{
public function executeComponent()
{
// true — кэша нет/устарел, нужно собрать $arResult заново
// false — кэш уже отдан, весь блок ниже пропускается
if ($this->startResultCache()) {
$this->arResult['ITEMS'] = $this->getItems();
if (empty($this->arResult['ITEMS'])) {
// данных нет — не сохраняем «пустой» результат в кэш
$this->AbortResultCache();
} else {
// в кэш идёт только ключ ITEMS, а не весь $arResult
$this->SetResultCacheKeys(['ITEMS']);
}
// рендер шаблона — при попадании в кэш эта строка не выполнится
$this->includeComponentTemplate();
}
}
}SetResultCacheKeys() — задаёт, какие именно ключи $arResult сериализовать в кэш; без вызова сериализуется весь массив. AbortResultCache() — отменяет кэширование по факту выборки, например когда искомый элемент не найден: без этого метода компонент закэшировал бы «пусто» и отдавал бы пустой результат все CACHE_TIME секунд, даже если элемент появится.
Кэшируется здесь не «данные» и «шаблон» раздельно — это один слой: при попадании в кэш шаблон компонента и result_modifier.php вообще не выполняются, из файла кэша достаётся готовый HTML.
Тегированный кэш D7
Кэш данных из первого раздела живёт по TTL — истекло время, кэш пересчитался. Тегированный кэш добавляет к этому точечный сброс: кэш можно инвалидировать раньше срока, если пометить его тегом и сбросить именно этот тег, не трогая остальной кэш.
$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
$taggedCache->startTagCache('/my/path/');
$taggedCache->registerTag('iblock_id_' . $iblockId);
$taggedCache->endTagCache();
// сброс — где угодно в другом месте кода, при изменении данных
\Bitrix\Main\Application::getInstance()->getTaggedCache()->clearByTag('iblock_id_' . $iblockId);Методы класса \Bitrix\Main\Data\TaggedCache:
startTagCache($path)— открывает блок регистрации тегов для указанного пути кэша.registerTag($tag)— помечает: текущий кэш зависит от этого тега.endTagCache()— закрывает блок, теги записываются в базу.clearByTag($tag)— сбрасывает все кэши, помеченные этим тегом.
Управляемый кэш — именно так называется эта обвязка в самой админке, и для компонентов она включается сама. Статус — в Настройки → Настройки продукта → Автокеширование, вкладка «Настройка управляемого кеширования»; в коде за это отвечает константа BX_COMP_MANAGED_CACHE. Когда управляемый кэш активен, startResultCache() из прошлого раздела автоматически оборачивает компонент в startTagCache()/endTagCache(). Не активен — компонент кэшируется по TTL, но без тегов, точечный сброс не сработает.
Дальше остаётся вопрос, кто регистрирует теги во время выборки данных — и здесь разные модули ведут себя по-разному, что подводит к следующему разделу.
Highload-блоки (HL-блоки) и кэш — почему не сбрасывается «само»
У инфоблоков регистрация тегов встроена в саму выборку: CIBlockResult::Fetch() вызывает CIBlock::registerWithTagCache($iblockId), а тот — $CACHE_MANAGER->RegisterTag("iblock_id_" . $iblockId). Поэтому кэш компонента, читающего элементы инфоблока, сбрасывается сам при изменении элемента — тег регистрируется прозрачно, без единой строчки со стороны разработчика.
У highload-блоков этого нет: HighloadBlockTable наследуется от базового ORM-класса DataManager, а он тегов не регистрирует — это функциональность именно модуля iblock. Значит выборка из HL-блока в тегированном кэше сама не инвалидируется при изменении записи — тег приходится сбрасывать вручную.
Проще всего — сразу после записи:
use Bitrix\Main\Application;
// добавляем запись в HL-блок через стандартный ORM D7
$result = MyHlBlockTable::add($fields);
if ($result->isSuccess()) {
// ORM highload-блоков теги сама не сбрасывает — делаем это вручную,
// тег должен совпадать со строкой, которую регистрировали при чтении
Application::getInstance()->getTaggedCache()->clearByTag('hlblock_' . MyHlBlockTable::getHighloadBlock()['ID']);
}А при чтении регистрировать тот же тег через registerTag() внутри startTagCache() / endTagCache(), как в разделе выше. Строку с тегом нужно продублировать и на чтении, и на записи — иначе сброс просто не найдёт, что чистить.
Композитный сайт — кэш страницы целиком
Композитный кэш — не про данные, а про готовую страницу целиком. Включается в Настройки → Настройки продукта → Композитный сайт, режимы — «Автокомпозит» или «Композит». Собранный HTML сохраняется в /bitrix/html_pages/, отдельно по доменам; хранить можно на диске или в оперативной памяти через memcached (в памяти кэш пропадает при перезапуске сервера).
Страница целиком статична не значит, что на ней не может быть личных данных — для этого есть динамические зоны, помечаемые не тегом в разметке, а вызовом API прямо в шаблоне компонента (template.php):
// в шаблоне компонента — создаём динамическую зону с уникальным ID
$frame = $this->createFrame('user-greeting')->begin();
echo 'Здравствуйте, ' . $USER->GetFullName();
// закрываем зону — дальше снова обычный статический контент
$frame->end();Весь вывод между begin() и end() не попадает в статический HTML и подгружается отдельным AJAX-запросом после отдачи страницы из кэша.
Сброс — кнопкой «Сбросить кэш» в настройках или программно: \Bitrix\Main\Composite\Page::getInstance()->deleteAll(). Отдельно есть cron_html_pages.php — но это не сброс по требованию, а фоновая уборка: он удаляет с диска файлы старше заданного времени, не разбирая, актуальны они ещё или нет. Диагностика — раздел «Композитный сайт → Страницы» показывает по каждому URL число просмотров и перезаписей: если перезаписей много относительно просмотров, это сигнал, что кэш сбрасывается слишком часто и толком не работает.
Как это выглядит на реальной нагрузке — в разборе 15k+ заказов в месяц без падения: там же про инвалидацию тегов на уровне конкретного элемента, а не всего инфоблока разом.
Где хранится кэш: файлы, memcached, redis
Всё, что описано выше — кэш данных, автокэш компонентов, тегированный кэш — физически пишется через один и тот же слой хранения, который настраивается в bitrix/.settings.php, секция cache. По умолчанию это файлы (CacheEngineFiles). Переключение на memcached:
'cache' => [
'value' => [
// какой класс движка кэша использовать вместо файлового
'type' => [
'class_name' => '\Bitrix\Main\Data\CacheEngineMemcache',
// PHP-расширение, которое должно быть установлено на сервере
'extension' => 'memcache',
],
// адрес и порт демона memcached
'memcache' => ['host' => '127.0.0.1', 'port' => '11211'],
// соль — чтобы кэши разных сайтов на одном memcached не пересекались
'sid' => $_SERVER['DOCUMENT_ROOT'] . '#01',
],
],Для redis — та же структура, другой класс:
'cache' => [
'value' => [
// то же самое, но движок — redis
'type' => [
'class_name' => '\Bitrix\Main\Data\CacheEngineRedis',
'extension' => 'redis',
],
// адрес и порт redis-сервера
'redis' => ['host' => '127.0.0.1', 'port' => '6379'],
'sid' => $_SERVER['DOCUMENT_ROOT'] . '#01',
],
],Для redis нужно расширение PHP redis (phpredis). Оба класса лежат в ядре, bitrix/modules/main/lib/data/.
Файловый кэш работает нормально на одном сервере. Проблема начинается при нескольких веб-серверах за балансировщиком: файл кэша, записанный на сервере A, физически не существует на сервере B — каждый сервер будет заново пересчитывать один и тот же кэш, и выигрыш от кэша почти пропадает. memcached или redis в этой схеме — общее хранилище, доступное всем серверам одинаково, поэтому кэш, посчитанный один раз, виден сразу везде.
Кэш не сработал — куда смотреть
Порядок проверки, когда компонент явно должен кэшироваться, а каждый раз идёт в базу заново:
- Глобальный автокэш выключен. Настройки → Настройки продукта → Автокеширование, вкладка «Настройка кеширования компонентов» — если выключено, все компоненты с
CACHE_TYPE=Aне кэшируются вообще (см. раздел про автокэш). $sCacheIDменяется на каждый запрос. Обычно из-за необработанного параметра —microtime(), необработанный GET, session ID там, где его быть не должно.- Хранилище недоступно на запись. Права на
/bitrix/cache/, либо в.settings.phpпо ошибке стоитCacheEngineNone(полное отключение кэша, используется для отладки, но иногда забывают вернуть обратно). CACHE_TIME=0— компонент технически «кэшируемый», но с нулевым временем жизни, что равносильно отсутствию кэша.- Композитный кэш сброшен глобальным событием. Ищите в разделе «Страницы» частые перезаписи — если весь сайт перезаписывается на каждое изменение одного элемента, вероятно теги стоят не на элемент, а на весь инфоблок или раздел целиком.
- HL-блок без ручной инвалидации — если данные меняются, а тегированный кэш вокруг HL-блока не чистится ни при записи, ни при чтении (см. раздел про highload-блоки).
Как измерить эффект кэша — панель производительности
Прежде чем тюнить кэш, стоит увидеть разницу в цифрах, а не на глаз. Встроенная Панель производительности (модуль perfmon) показывает время генерации страницы и число SQL-запросов — поэтому разницу между «включённым CACHE_TYPE=A» и «принудительным N на одном компоненте» сразу видно по числу запросов к базе. Для более глубокого профилирования — Xdebug/Blackfire, если нужно понять, что именно съедает время внутри незакэшированного участка.
Практический способ проверки: временно выставить CACHE_TYPE=N на подозрительном компоненте и сравнить время ответа и число запросов до/после — так сразу видно, действительно ли кэш на этом участке что-то экономит, или тормозит что-то другое.
Частые ошибки кэширования
- Кэшируют персональные данные без ID пользователя в
$cacheId. Один пользователь видит в кэше данные другого — самая неприятная ошибка кэширования из всех. - В
$cacheIdпопадает время. Обычно этоtime(),microtime()или другое постоянно меняющееся значение. Кэш формально «работает», но каждый запрос создаёт новый файл — по сути кэша нет. rand()/mt_rand()внутри закэшированного блока. На кэш-хите отдаётся всегда один и тот же — «случайный» — результат первого запроса, пока кэш не истечёт.- Забыли
AbortResultCache(). Пустой результат (например «элемент не найден») кэшируется как валидный — элемент появится в базе, а компонент ещёCACHE_TIMEсекунд будет показывать пустоту. - Изменили данные HL-блока и не сбросили тег вручную — см. раздел про HL-блоки выше, это не баг, а ожидаемое поведение, которое легко упустить.
Итог — что когда использовать
| Задача | Инструмент |
|---|---|
| Закэшировать выборку в своём коде | Кэш данных (CPHPCache / \Bitrix\Main\Data\Cache) |
| Закэшировать компонент целиком | Автокэш компонента (CACHE_TYPE в .parameters.php) |
| Сбрасывать кэш точечно, не по TTL | Тегированный кэш (TaggedCache) |
| Кэшировать выборку из HL-блока | Тегированный кэш + ручной сброс на add/update/delete |
| Закэшировать страницу целиком | Композитный сайт |
| Кэш общий для нескольких серверов | memcached/redis вместо файлового хранилища |
Ни один из этих механизмов не заменяет остальные — они закрывают разные уровни одной и той же задачи, и обычно на боевом проекте работают все сразу.
