Redis при многосайтовости: почему кеш не сбрасывается на соседних сайтах
Одно ядро, несколько сайтов, симлинки на каталог ядра, кеш вынесен в Redis. Редактор добавляет картинку к разделу через админку одного сайта — на остальных она не появляется. Помогает только ручная очистка компонентов с публичной части. Отключаешь Redis — всё работает. Разбираем, почему так происходит и какой ровно один параметр это чинит.
Почему кеш не сбрасывается на соседних сайтах?
Потому что у каждого сайта свой префикс ключей в Redis. Сброс, запущенный из админки одного сайта, чистит ключи только в его пространстве имён — до ключей остальных сайтов он физически не дотягивается.
Задаёт этот префикс параметр sid в секции cache файла /bitrix/.settings.php. Установщик по умолчанию пишет туда такое значение:
'cache' => [
'value' => [
'type' => [
'class_name' => '\\Bitrix\\Main\\Data\\CacheEngineRedis',
'extension' => 'redis',
],
'redis' => [
'host' => '127.0.0.1',
'port' => '6379',
],
'sid' => $_SERVER["DOCUMENT_ROOT"] . "#01", // ← вот здесь
],
],Для одиночного сайта это корректно. При многосайтовости — ловушка: DOCUMENT_ROOT у каждого сайта свой, значит из одного и того же файла конфигурации получается столько разных значений sid, сколько сайтов.
Файл действительно один: /bitrix — симлинк, физически это один и тот же .settings.php на все сайты. Но строка внутри вычисляется в рантайме, на каждом запросе заново. Одинаковый файл, разные значения.
Что такое sid в настройках кеша?
Это префикс каждого ключа, который ядро кладёт в Redis, и единственный механизм разделения пространств имён. Читается он в Bitrix\Main\Data\Cache\KeyValueEngine:
// KeyValueEngine::configure()
elseif (!empty($cacheConfig['sid']))
{
$this->sid = $cacheConfig['sid'];
}
$this->sid .= '|v1.1';
// и дальше уходит во все ключи
protected function getKeyPrefix($baseDirVersion, $initDirVersion): string
{
return $this->sid . '|' . sha1($baseDirVersion . '|' . $initDirVersion);
}
protected function getBaseDirKey($baseDir): string
{
return $this->sid . '|BDV:' . sha1($baseDir);
}Обратите внимание на $baseDir: это относительный путь вида bitrix/cache, а не абсолютный. Корень документа в ключ не входит вообще. То есть при одинаковом sid все сайты честно лягут в общее пространство имён — именно так, как и должно быть при общем ядре и общей базе.
Значение по умолчанию, если sid не задан, — строка BX. Метод clean() работает строго внутри своего префикса и за его границы не выходит.
Как ядро сбрасывает кеш по тегам?
Через таблицу b_cache_tag в базе — и вот здесь разные sid превращаются в наблюдаемый баг. Смотрим Bitrix\Main\Data\TaggedCache::clearByTag():
$cache = Cache::createInstance();
$query = CacheTagTable::query()->setSelect(['ID', 'RELATIVE_PATH']);
// ...
$this->deleteTags($cache, $id, $paths);Таблица тегов лежит в базе и общая для всех сайтов, пути в ней относительные. А вот Cache::createInstance() создаётся с тем sid, который действует в текущем запросе. База говорит «почистить такой-то путь», ядро честно чистит его — но только в своём пространстве ключей.
Дальше история складывается сама:
- Редактор правит раздел в админке первого сайта — ядро чистит ключи с префиксом первого сайта
- Публичная часть второго сайта читает ключи с префиксом второго сайта — их никто не трогал
- На экране старая картинка, хотя в базе новая
- Ручная очистка компонентов с публичной части второго сайта помогает — там корень документа наконец совпал
Почему на файловом кеше этой проблемы не было?
Потому что файловый кеш лежит в /bitrix/cache/ и /bitrix/managed_cache/, а /bitrix — симлинк. Физически это один каталог на все сайты, общий по построению.
Отсюда типичная диагностическая ошибка: отключили Redis, всё заработало — «значит виноват Redis». Виноват не Redis, а то, что при переезде с файлов на ключ-значение общность каталога перестала работать сама собой, а настройку пространства имён никто не поправил.
Как правильно задать sid при многосайтовости?
Фиксированной строкой, одинаковой на всех сайтах и уникальной на инсталляцию. Никаких вычислений в рантайме.
'cache' => [
'value' => [
'type' => [
'class_name' => '\\Bitrix\\Main\\Data\\CacheEngineRedis',
'extension' => 'redis',
],
'redis' => [
'host' => '127.0.0.1',
'port' => '6379',
],
'sid' => 'proj_prod_01', // одна строка на все сайты инсталляции
],
],Правило, которое стоит запомнить целиком: один sid — одна база данных Битрикса. Сайты, живущие на общем ядре и общей базе, обязаны делить пространство ключей, потому что делят данные.
Когда sid, наоборот, должен различаться?
Когда на одном сервере Redis живут разные инсталляции с разными базами. Причина конкретная: ядро не умеет переключать номер базы Redis. В Configurator\RedisConnectionConfigurator есть только адрес, порт, пароль, постоянное соединение, таймауты и настройки отказоустойчивости. Команды выбора базы нет.
То есть развести две инсталляции можно ровно двумя способами: разными значениями sid или разными экземплярами Redis на разных портах. Третьего варианта платформа не даёт.
Как настроить сессии в Redis при многосайтовости?
У сессий отдельный префикс, к cache.sid он отношения не имеет. Задаётся ключом keyPrefix, и по умолчанию у всех сайтов он одинаковый:
// RedisSessionHandler::__construct()
$this->prefix = $options['keyPrefix'] ?? 'BX';Идентификаторы сессий случайные, так что коллизий практически не бывает. Но для порядка префикс лучше задать явно и — в отличие от кеша — разный для каждого сайта:
'session' => [
'value' => [
'mode' => 'separated',
'handlers' => [
'kernel' => 'encrypted_cookies',
'general' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6380,
'keyPrefix' => 'sess_site1', // свой на каждом сайте
],
],
],
],Если между сайтами нужен сквозной логин — префикс, наоборот, общий, но тогда отдельно разбирайтесь с доменом cookie. Кеш и сессии здесь ведут себя зеркально, и это нормально: кеш описывает общие данные, сессия — конкретного посетителя конкретного сайта.
Что сделать после смены sid?
Старые ключи со старым префиксом осиротеют и будут занимать память до истечения их времени жизни. Их нужно убрать руками:
# Чистим ТОЛЬКО экземпляр кеша
redis-cli -p 6379 FLUSHDB
# 6380 не трогаем — там сессии, иначе разлогинятся все посетители- Правим sid в /bitrix/.settings.php
- Чистим экземпляр Redis, отведённый под кеш
- Запускаем «Очистить весь кеш» в админке
- Делаем это в часы низкой нагрузки: сразу после будет пик запросов к базе
Кеш и сессии стоит разносить по разным экземплярам Redis именно ради этого шага. Когда всё в одном, любая чистка кеша превращается в массовый разлогин.
Как убедиться, что конфигурация действительно одна?
Проверить значение на каждом сайте по отдельности. Файл ищется не по фиксированному пути: Loader::getLocal() сначала смотрит в личный каталог и только потом в каталог ядра.
// Loader::getLocal()
if (file_exists($root . "/local/" . $path))
{
return $root . "/local/" . $path;
}
elseif (file_exists($root . "/bitrix/" . $path))
{
return $root . "/bitrix/" . $path;
}Если у какого-то сайта есть собственный, не симлинкнутый каталог local со своим .settings.php — он перекроет настройки ядра целиком. Плюс существует .settings_extra.php, который накладывается поверх основного файла. Поэтому проверять надо не файл, а итоговое значение:
var_dump(\Bitrix\Main\Config\Configuration::getValue('cache')['sid']);Ещё одна деталь из той же оперы: старая константа BX_CACHE_SID из dbconn.php на поведение кеша больше не влияет. Она осталась только в классе миграции настроек, который переносил старую конфигурацию в .settings.php. Если она где-то прописана — она вводит в заблуждение и ничего не делает.
Что ещё посмотреть, если не помогло
Если после выравнивания sid инвалидация заработала, но память Redis растёт — дело в параметре full_clean. По умолчанию он выключен: при команде «очистить весь кеш» ключи физически не удаляются, а ротируется версия каталога. Для корректности этого достаточно, мусор просто доживает до истечения срока.
Включить принудительную очистку можно ключом 'full_clean' => true в той же секции, но чистка станет заметно медленнее. Для большинства проектов выгоднее оставить как есть и следить за объёмом памяти.
Что забрать себе
- При многосайтовости на одном ядре sid обязан быть одинаковым на всех сайтах: один sid — одна база
- Значение по умолчанию с DOCUMENT_ROOT рассчитано на одиночный сайт и при симлинках тихо разваливает инвалидацию
- Таблица тегов общая, а чистка выполняется в пространстве текущего запроса — отсюда «почистилось, но не везде»
- Разные инсталляции на одном Redis разводятся только через sid или разные порты: номер базы ядро не переключает
- Сессии настраиваются зеркально: свой keyPrefix на каждый сайт, и отдельный экземпляр Redis от кеша
- После смены sid обязательна чистка: старые ключи не исчезнут сами
И общий вывод, ради которого стоит помнить этот случай: настройки кеша выглядят как набор безобидных строк, но ровно одна из них определяет, видят сайты работу друг друга или нет. Когда поведение кеша непонятно, быстрее всего открыть исходники ядра и посмотреть, из чего собирается ключ, чем перебирать настройки наугад.
Если кеш живёт своей жизнью
Странности с кешем редко чинятся перебором настроек: их быстрее прочитать в коде ядра. Расскажите, что происходит, — посмотрим, где именно расходятся ключи.