Почему Битрикс медленно работает: где искать узкое место
К нам регулярно приходят с одной и той же формулировкой: «обширный каталог с большим количеством торговых предложений работает медленно, страницы загружаются с заметной задержкой, админка тормозит, менеджеры жалуются». Сайт живой, заказы идут, но каждая правка товара превращается в пытку, а поисковики потихоньку просаживают позиции. По нашему опыту, в 90% таких обращений тормозит не «Битрикс вообще», а неоптимизированные выборки или настройки компонентов, которые можно найти за пару часов и починить за день.
Практика показывает: причина почти всегда лежит в одной из четырёх плоскостей.
- Тяжёлые запросы к MySQL — неиндексированные выборки по свойствам, N+1 в циклах компонентов, JOIN'ы по таблицам без ключей.
- Отсутствие кеша компонентов — компонент вызывается с
CACHE_TYPE => "N"или кеш сбрасывается на каждом хите из-за неверногоCACHE_GROUPS. - Не включён композит — статика отдаётся через полный прогон PHP, хотя могла бы уходить из
html_pages. - Слабые настройки PHP и MySQL — маленький
memory_limit, дефолтныйinnodb_buffer_pool_size, отсутствие opcache.
Соблазн «оптимизировать вслепую» здесь очень велик: нагуглить чек-лист, включить всё подряд и надеяться. Так делать нельзя — вы не узнаете, что именно помогло, а что сломало корзину или фильтр. Сначала замер, потом правка.
Порядок диагностики, к которому мы пришли на десятках проектов:
- Снять замер в панели производительности — зафиксировать время генерации, число запросов, память.
- Посмотреть список медленных запросов MySQL — включить
slow_query_logи отсортировать поTime. - Проверить, кешируются ли ключевые компоненты —
catalog.section,catalog.element,news.list. - Проверить, включён ли композитный режим для страниц каталога и карточек.
- Проверить настройки PHP и MySQL — opcache,
memory_limit,innodb_buffer_pool_size,thread_pool_size.
Панель производительности (Настройки → Производительность → Панель производительности) — это не просто «сколько миллисекунд». Смотрите на три числа: время генерации страницы, количество запросов к БД и пиковое потребление памяти. Первое говорит о пользовательском опыте, второе — о нагрузке на MySQL, третье — о близости к лимиту PHP.
Абсолютные цифры обманчивы: 800 мс на dev-машине и на боевом сервере под нагрузкой — это два разных мира. Панель поэтому и предлагает сравнение с эталонной системой. Важна не сама цифра, а отклонение от нормы. Если ваш каталог считает страницу заметно дольше эталона, узкое место уже локализовано.
Отдельный тревожный сигнал — 200+ запросов на страницу каталога. Это почти всегда означает одно: компонент крутит цикл по элементам и внутри дёргает свойства, цены, скидки отдельными запросами. Лечится не «оптимизацией MySQL», а переписыванием выборки через getList с select нужных полей и включённым управляемым кешем — об этом в разделе про CPHPCache.
Композитный сайт Битрикс: как включить и настроить
Самый частый запрос на эту тему звучит так: «каталог отдаётся две секунды, что ещё можно сделать без переписывания шаблона». Композитный сайт в такой ситуации даёт самый заметный прирост на единицу усилий. Технология делит страницу на две части: статическую оболочку и отложенные динамические фрагменты. Оболочка один раз рендерится в готовый HTML и отдаётся посетителю мгновенно, а всё, что зависит от пользователя, подтягивается отдельными AJAX-запросами уже после загрузки.
Включать композит имеет смысл там, где контент одинаков для всех: каталог, страницы разделов, статьи, карточки товаров без персонализации. Это ровно те URL, которые чаще всего попадают в индекс и на которые идёт основной трафик. А вот корзину, личный кабинет, страницы с формами и региональными подстановками в композит «как есть» пускать нельзя — их сначала нужно вынести в отложенные фрагменты, иначе посетитель увидит чужие данные. Об этом ниже отдельным предупреждением.
Включается всё через админку, без правки кода:
- Открыть Настройки → Настройки продукта → Композитный сайт.
- Выбрать режим работы — автокомпозит (система сама решает, что кешировать) или ручной (управляете вручную через API).
- Указать список доменных имён, для которых композит активен — если сайт мультидоменный, перечислить все.
- Задать маску включения, например
*.html, чтобы композит не лез на служебные адреса. - Сохранить настройки и очистить кеш — без этого старые страницы продолжат отдаваться из обычного кеша.
Файлы композитного кеша лежат в /bitrix/html_pages/. Если перед сайтом стоит Nginx, имеет смысл отдавать эти файлы напрямую, минуя PHP:
# Структура композитного кеша
/bitrix/html_pages/<домен>/<путь>/index.html
# Пример: /bitrix/html_pages/example.ru/catalog/iphone/index.html
# Фрагмент конфигурации Nginx
set $composite_file /bitrix/html_pages/$http_host$request_uri/index.html;
if (-f $document_root$composite_file) {
rewrite ^(.*)$ $composite_file last;
}Для управления динамическими областями в технологии «Композитный сайт» используется класс Bitrix\Main\Page\Frame. Он позволяет помечать блоки страницы как «отложенные», сохраняя статический HTML в кеш и внедряя JS-код для их последующей загрузки через аякс. Ключевым элементом является заглушка — временный контент, отображаемый до получения данных. Начиная с версии main 14.5.2, содержимое заглушки учитывается в хеш-сумме кеша. Это обеспечило корректную инвалидацию страниц при изменении статики внутри динамических зон (например, кодов счетчиков), предотвращая отображение устаревших данных.
Если у вас кастомные динамические блоки — не полагайтесь на автокомпозит, оборачивайте их в Frame вручную. Так вы явно контролируете, что попадёт в кеш, а что уйдёт в аякс. Для проектов с региональностью или персонализированными подстановками ручной режим обычно предпочтительнее.
Если не вынести корзину и формы в отложенные фрагменты, посетитель увидит чужую корзину или закешированное состояние формы. Перед включением композита пройдитесь по всем динамическим блокам: корзина, избранное, формы обратной связи, счётчики с пользовательскими данными, региональные подстановки. Каждый такой блок должен быть либо отложенным фрагментом, либо исключён из композита маской.
Кэширование компонентов и CPHPCache: что и как кешировать
Композитный сайт, о котором мы говорили выше, спасает только публичную часть: анонимный посетитель получает готовый HTML из статического кеша. Но как только пользователь авторизовался, положил товар в корзину или зашёл в админку — композит отключается, и вся нагрузка ложится на штатное кеширование компонентов. Именно здесь чаще всего и прячется та самая «вторая секунда» отдачи каталога. В проектах с большим каталогом мы почти всегда видим одну картину: композит включён, а внутри компонентов кеш либо выключен, либо настроен так, что не работает вообще.
Есть два уровня кеширования, и путать их нельзя. Первый — штатный кеш компонента: параметры CACHE_TYPE, CACHE_TIME, CACHE_GROUPS, которые передаются прямо в вызове $APPLICATION->IncludeComponent(). Битрикс сам решает, что кешировать, сам пишет в /bitrix/cache/ и сам сбрасывает при изменении данных инфоблока. Второй — ручное кеширование через CPhpCache: вы сами оборачиваете тяжёлую выборку в InitCache() и StartDataCache() и сами управляете тем, когда кеш инвалидируется. Первого хватает в 80% случаев: типовые компоненты bitrix:news.list, bitrix:catalog.section уже умеют кешироваться. Второй нужен, когда вы пишете свою выборку напрямую через CIBlockElement::GetList или агрегируете данные из нескольких инфоблоков, и штатный механизм компонента тут ни при чём.
Как включить кеш у штатного компонента: параметры CACHE_TYPE, CACHE_TIME, CACHE_GROUPS
У любого штатного компонента есть три ключевых параметра кеша. CACHE_TYPE принимает три значения: N (компонент не кэшируется никогда), A (автокеширование — компонент кэшируется, если в разделе «Настройки > Настройки продукта > Автокеширование» глобально включена соответствующая галочка) и Y (принудительное включение кеша). CACHE_TIME — время жизни кеша в секундах: 3600 для каталога, который меняется раз в час, 86400 для статичных справочников. CACHE_GROUPS — тонкий момент: если поставить Y, кеш будет отдельным для каждой группы пользователей, что критично, когда в шаблоне выводятся цены по группам или скрытые для гостя блоки. Если такой логики нет — ставьте N, иначе плодятся лишние копии кеша.
В наших проектах мы обычно держим CACHE_TYPE=A и CACHE_GROUPS=N для листингов каталога и Y — только там, где реально показываем разный контент разным группам. Главное правило: включённый кеш компонента сбрасывается автоматически при изменении элементов инфоблока — об этом позаботилось ядро.
<?php
$APPLICATION->IncludeComponent(
'bitrix:news.list',
'catalog_list',
[
'IBLOCK_ID' => 12, // замените на ваш IBLOCK_ID
'NEWS_COUNT' => 30,
'SORT_BY1' => 'SORT',
'SORT_ORDER1' => 'ASC',
'CACHE_TYPE' => 'A', // автокеширование
'CACHE_TIME' => 3600, // час
'CACHE_GROUPS' => 'N', // кеш общий для всех групп
'CACHE_FILTER' => 'Y', // учитывать фильтр в ключе кеша
],
false
);CACHE_GROUPS=N здесь означает, что один и тот же закешированный HTML отдаётся и гостю, и авторизованному — это безопасно, только если в шаблоне нет вывода, зависящего от пользователя.
Ручное кеширование через CPhpCache: InitCache и StartDataCache
Когда выборка идёт напрямую, компонент вам не помощник. Классический случай — блок «Топ-10 товаров раздела» или агрегат по нескольким инфоблокам: считать это на каждый хит дорого, а штатный кеш компонента не применим. Здесь на сцену выходит CPhpCache. Схема простая: вызываете InitCache(), проверяете результат, и если кеша нет — выполняете тяжёлую работу внутри StartDataCache() и закрываете её EndDataCache().
Ключевой момент — второй аргумент StartDataCache(): строка uniq_str. Это «отпечаток» или уникальный ID, от которого зависит кеш. Четвёртый аргумент (vars) служит для сохранения массива переменных в кеш-файл, чтобы они были доступны после его извлечения. Если не включить идентификаторы данных в uniq_str, кеш не будет уникальным. Для управления актуальностью кеша при изменении данных в инфоблоках обычно используют дополнительные механизмы очистки или теги.
<?php
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
$sectionId = (int)($_REQUEST['SECTION_ID'] ?? 0);
$cacheId = 'catalog_top_' . $sectionId;
$cache = new CPHPCache();
$cacheTime = 3600;
if ($cache->InitCache($cacheTime, $cacheId, '/catalog_top/')) {
$arResult = $cache->GetVars();
} else {
$arCacheVars = [
'IBLOCK_ID' => 12, // замените на ваш IBLOCK_ID
'SECTION_ID' => $sectionId,
];
$cache->StartDataCache($cacheTime, $cacheId, '/catalog_top/', $arCacheVars);
$arResult = [];
$res = CIBlockElement::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => 12, 'SECTION_ID' => $sectionId, 'ACTIVE' => 'Y'],
false,
['nTopCount' => 10],
['ID', 'NAME', 'DETAIL_PAGE_URL', 'PROPERTY_PRICE']
);
while ($row = $res->Fetch()) {
$arResult[] = $row;
}
$cache->EndDataCache($arResult);
}
// дальше $arResult используется в шаблонеЭтот код можно копировать в result_modifier.php компонента или в собственный обработчик — он самодостаточен.
Разберём сигнатуры, чтобы вы понимали, что именно крутить под свой проект. InitCache(int $TTL, string $uniq_str, mixed $initdir = false, string $basedir = 'cache') — первые два аргумента это время жизни и уникальная строка-идентификатор. $initdir задаёт подкаталог внутри /bitrix/cache/, куда лягут файлы; если оставить false, кеш уедет в общую свалку, и разбирать её будет больно. Мы всегда задаём осмысленный $initdir вроде '/catalog_top/' — так проще чистить точечно. $basedir по умолчанию 'cache', трогать его почти никогда не нужно.
Метод StartDataCache() принимает те же TTL и $uniq_str, а четвёртым аргументом — массив vars. Этот массив задаёт PHP-переменные, которые сохраняются в файле кеша и затем возвращаются методом GetVars(). Автоматическую инвалидацию кеша при изменении данных инфоблока обеспечивает не vars, а тегированный (управляемый) кеш: при правках элементов ядро сбрасывает кеши, привязанные к соответствующим тегам. Если вы ведёте данные не через инфоблок (например, тянете из внешнего API), автоматического сброса не будет — придётся вешать свой обработчик на событие обновления и вызывать $cache->Clean($uniq_str, $initdir) вручную.
Осторожно с CACHE_SELECTED_ITEMS. У компонента меню этот параметр влияет на привязку кеша к адресу страницы. При некорректной настройке это может привести к созданию множества копий кеша для разных разделов сайта. Проверьте вызов bitrix:menu: правильное использование параметра помогает избежать раздувания кеша и оптимизировать время отклика сервера.
Как ускорить Битрикс через настройки PHP и MySQL
Композит включён, компоненты кешируются, а страница каталога всё равно отдаётся медленно. Значит, кеш уже не при чём — упёрлись в сервер. На этом этапе в игру вступают параметры PHP и MySQL, и именно здесь часто прячется самый дешёвый прирост производительности: без переписывания шаблонов и без покупки нового железа.
Что реально влияет на скорость Битрикса со стороны окружения. Лимит памяти PHP — если он занижен, тяжёлые страницы с выборками падают или уходят в своп. Настройки mbstring — при неверной кодировке функции строк работают медленнее и иногда возвращают мусор. Со стороны MySQL — performance_schema, который на слабых машинах только жрёт память, и thread_pool_size, влияющий на обработку параллельных запросов. Отдельная тема — размещение временных файлов в RAM-диск: MySQL активно пишет tmp-таблицы, и перенос tmpdir в память убирает лишний I/O.
На BitrixVM часть этих параметров уже настроена автоматически через сервис bvat: он подбирает значения Apache, PHP, MySQL и nginx под ресурсы сервера. Но под конкретную нагрузку — большой каталог, активный импорт — дефолтов не всегда хватает, и часть параметров приходится править руками. Ниже — что именно и где менять.
| Параметр | Где задаётся | Рекомендуемое значение | Зачем |
|---|---|---|---|
memory_limit |
php.ini / z_bx_custom.ini |
256M (для дефолтных конфигураций) | Тяжёлые выборки и импорт не уходят в своп |
php.ini |
2 | Корректная и быстрая работа строковых функций в UTF-8 | |
mbstring.internal_encoding |
php.ini |
UTF-8 | Кодировка по умолчанию для mbstring |
performance_schema |
[mysqld] в my.cnf |
OFF на слабых серверах | Освобождает память на машинах с малым объёмом RAM |
thread_pool_size |
[mysqld] |
16 (дефолт MySQL 8) | Число параллельных потоков обработки запросов |
tmpdir |
[mysqld] |
путь в RAM-диск | Временные таблицы пишутся в память, а не на диск |
Пользовательские конфиги складываем в отдельные файлы, которые не перезаписываются BitrixEnv — иначе bvat или обновление затрёт правки:
# PHP: /etc/php.d/z_bx_custom.ini
memory_limit = 256M
mbstring.func_overload = 0
default_charset = UTF-8
# MySQL: /etc/mysql/conf.d/z_bx_custom.cnf
[mysqld]
performance_schema = OFF
thread_pool_size = 16
tmpdir = /dev/shm/mysql-tmpПочему нельзя править файлы прямо в /bitrix/modules и дефолтные конфиги BitrixVM — их перезапишет либо обновление продукта, либо тот же bvat при очередной реконфигурации. Всё, что мы меняем руками, должно жить в отдельных файлах, которые никто не трогает. Это правило действует и для PHP, и для MySQL: /etc/php.d/z_bx_custom.ini и /etc/mysql/conf.d/z_bx_custom.cnf подключаются после дефолтных и не перезаписываются.
Проверить, что настройки применились, просто. Для PHP — php -i | grep memory_limit и аналогично по mbstring-параметрам. Для MySQL — SHOW VARIABLES LIKE 'performance_schema'; и SHOW VARIABLES LIKE 'thread_pool_size'; прямо в консоли. Если значения не совпадают с ожидаемыми, значит конфиг не подхватился: проверьте путь, права и порядок подключения в my.cnf.
Перенос tmpdir в RAM-диск имеет смысл, когда MySQL активно строит временные таблицы — например, при сортировках в большом каталоге или при импорте. На сервере с запасом памяти это даёт заметный выигрыш, но при дефиците RAM лучше сначала разобраться с лимитами, а не отдавать память под tmp.
Осторожно на слабых серверах. На машинах с 512 МБ RAM стандартные настройки MySQL в BitrixVM могут привести к дефициту памяти и падению сервера под нагрузкой. При оптимизации конфигурации в секции [mysqld] важно учитывать лимиты ресурсов и отключать лишние опции для экономии ОЗУ. Проверяйте фактическое потребление памяти после каждого изменения, а не только по документации.
Redis и Memcache в Битрикс: когда они реально помогают
Типичный сценарий: композит включён, компоненты кешируются, PHP и MySQL подкручены, а MySQL всё равно упирается в потолок по количеству одновременных соединений. Причина в том, что кеш по умолчанию живёт в файлах, а сессии — в базе. Каждый анонимный посетитель, каждый авторизованный пользователь, каждый запрос к CPHPCache — всё это уходит в MySQL или на диск, который под нагрузкой становится узким местом не хуже самой базы.
В ядре Bitrix Framework есть штатные подключения к Redis и Memcache — они выносят кеш и сессии из MySQL в оперативную память. Это не «серебряная пуля», а конкретный инструмент под конкретную нагрузку. Тратить время на настройку имеет смысл, когда выполнено хотя бы одно условие: каталог от 20 000 SKU, стабильная посещаемость выше нескольких тысяч визитов в сутки, много авторизованных пользователей (личный кабинет, B2B-портал, история заказов). Если у вас витрина на 2 000 товаров и пара сотен визитов — Redis не даст заметного выигрыша, а лишний демон на сервере добавит точку отказа.
Мы обычно подключаем Redis, когда видим в панели производительности, что основное время уходит не на конкретный тяжёлый SQL, а на множество мелких запросов к кешу и сессиям.
<?php
// bitrix/.settings.php
return [
'cache' => [
'value' => [
'type' => [
'class_name' => '\Bitrix\Main\Data\CacheEngineRedis',
'extension' => 'redis',
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 2.0,
],
'sid' => 'bitrix-cache',
],
],
'session' => [
'value' => [
'mode' => 'default',
'handlers' => [
'general' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
],
],
],
],
];В Redis уходит три категории данных: кеш компонентов и managed cache (то, что раньше писалось в /bitrix/cache/), сессии и мелкие служебные выборки. В MySQL при этом остаётся всё, что касается транзакционных данных: заказы, корзины, остатки, свойства товаров. Их кешировать в Redis нельзя — данные должны быть консистентны, а Redis для этого не предназначен. Именно поэтому Redis не отменяет нормальный кеш на уровне компонентов: он лишь меняет место хранения, а не логику.
Проверить эффект проще всего через панель производительности (Настройки > Производительность > Панель производительности): замеряем до подключения Redis и после, сравниваем число запросов к MySQL и общее время генерации страницы. Если разница в пределах погрешности — Redis вам не нужен, откатите настройку.
Что касается выбора между Memcache и Redis: Memcache проще — это чистый key-value для кеша без персистентности. Redis гибче: поддерживает разные типы данных, репликацию и сохранение на диск. В Bitrix Framework Redis доступен как один из штатных вариантов для хранения сессий наряду с файлами и Memcache. Если у вас уже настроен memcached под кеш — не обязательно мигрировать. Но новые проекты мы обычно поднимаем на Redis.
Панель производительности Битрикс: как замерить результат
Многие думают, что оптимизация — это про «включить композит и Redis», но без замера любая правка — это вера, а не инженерия. Мы регулярно видим проекты, где админ неделю правит настройки PHP, а потом выясняется, что узкое место было в одном некешируемом компоненте. Чтобы такого не было, в Bitrix есть встроенный инструмент — Панель производительности. Она находится по пути Настройки > Производительность > Панель производительности и умеет тестировать конкретную страницу: прогоняет её через сценарий нагрузки и сравнивает результат с эталонной системой.
Эталон — это не ваш сервер и не ваш проект. Это усреднённые показатели «здоровой» конфигурации Bitrix на типовом железе, которые 1С-Битрикс закладывает как ориентир. Сравнивать с ним полезно, потому что абсолютные цифры в отрыве от контекста ничего не значат: страница может генерироваться 0.8 секунды на мощном выделенном сервере и 3 секунды на VPS — и обе цифры «нормальны» для своей среды. А вот расхождение с эталоном по числу запросов и по времени генерации показывает, где именно вы отстаёте от типовой картины. Об этом мы подробнее скажем ниже.
Пошаговый сценарий, которым пользуемся мы сами:
- Открыть
Настройки > Производительность > Панель производительностии убедиться, что модуль «Производительность» установлен. - Запустить тест на типовой странице каталога — не на главной и не на карточке товара, а именно на листинге раздела, потому что там основной вес выборки и пагинации.
- Зафиксировать три ключевых числа: время генерации страницы, количество SQL-запросов, объём потреблённой памяти.
- Внести одну правку — например, включить кеш конкретного компонента или поменять маску композита.
- Повторить тест на той же странице и сравнить с предыдущим замером.
Почему сравнивать с эталоном важнее абсолютных цифр? Потому что эталон показывает структуру нагрузки, а не скорость железа. Если у вас 400 запросов на страницу каталога против эталонных 80 — это сигнал, что где-то не работает кеш выборок или отключён управляемый кеш ORM. Если запросов столько же, а время генерации втрое выше — проблема, скорее всего, в самих запросах: тяжёлые JOIN'ы, отсутствие индексов, выборка всех свойств разом. Это уже другой класс задач, и лечится он не настройками, а профилированием SQL.
Отдельный сюжет — когда после включения композита цифры не улучшились. Часто страница просто не попадает под условия работы технологии или маски включения, заданные в системе. Проверьте конфигурацию в Настройки > Настройки продукта > Композитный сайт и убедитесь, что тестовый URL, включая все параметры запроса, соответствует правилам и не исключен из процесса кеширования.
Самые частые ошибки при оптимизации Битрикс
За годы работы с Битрикс-проектами у нас сложился свой «топ» ошибок — те, что встречаются в чужом коде и в аудитах почти всегда. Общая черта у них одна: оптимизацию делают без замера и без учёта динамики проекта. Включают композит «на всякий случай», правят конфиги по гайдам из интернета, лезут в ядро, потому что так быстрее, чем разбираться с событиями. Через месяц каталог снова тормозит, а причина уже в другом месте.
Ниже шесть ошибок, которые мы встречаем чаще всего. По каждой: в чём суть, почему так делать нельзя и что делать вместо. Часть напрямую вытекает из предыдущих секций — композита, кеширования, PHP-настроек, — но здесь мы собрали их в один чек-лист, чтобы было проще пройтись по своему проекту.
| Ошибка | Причина | Решение |
|---|---|---|
| Композит включён без выноса динамики | Динамические блоки (корзина, авторизация, счётчики) попадают в статический HTML и кешируются для всех | Обернуть динамику в Frame или вынести в отдельные ajax-компоненты до включения композита |
CACHE_SELECTED_ITEMS=Y на фильтрах |
Кеш привязывается к адресу страницы — на каждый URL свой вариант, кеш не переиспользуется | Для страниц с фильтрами ставить N и кешировать выборку отдельно по параметрам фильтра |
Правка ядра вместо /local |
Файлы в /bitrix/modules перезаписываются при обновлении — правки исчезают |
Свой модуль или обработчики событий в /local/php_interface |
performance_schema на слабом сервере |
Сборщик статистики MySQL съедает память и CPU, которых и так мало | Для машин с 512 МБ RAM — performance_schema = off в [mysqld] |
| Redis подключён без предварительного кеша | Redis ускоряет доступ к данным, но не создаёт их — при промахе всё равно идёт запрос в БД | Сначала настроить кеш компонентов и CPHPCache, потом подключать Redis |
Отключение mbstring.func_overload |
Ломает работу строковых функций ядра и кодировку UTF-8 на сайте | Оставить mbstring.func_overload=2 и mbstring.internal_encoding=UTF-8 |
Отдельно остановимся на двух пунктах, которые чаще всего приводят к необратимым последствиям — правке ядра и настройке mbstring.
Правки в /bitrix/modules — это классика. Разработчик открывает модуль, добавляет пару строк в компонент, всё работает. При ближайшем обновлении ядра эти строки исчезнут: Bitrix перезапишет файлы модуля, и никто не вспомнит, что там было. Вместо правки ядра есть два пути. Собственный модуль, если логика нетривиальная и переиспользуется. Или обработчики в /local/php_interface/init.php, если нужно перехватить событие и подменить данные. Второй вариант быстрее и подходит для 80% задач. Если нужна подмена шаблона компонента — копируем его в /local/templates/, а не правим исходник.
С mbstring.func_overload история коварнее. В старых гайдах встречается совет «отключить, чтобы не было конфликтов». И это ломает UTF-8 на сайте: strlen, substr и другие функции начинают считать байты вместо символов, кириллица при обрезке превращается в кракозябры. Для Bitrix корректная настройка — mbstring.func_overload=2 и mbstring.internal_encoding=UTF-8. Проверять это нужно после каждого обновления PHP, потому что дефолты в дистрибутивах меняются.
Перед каждым релизом у нас в чек-листе три пункта: диф по /bitrix/modules (не появилось ли правок ядра), актуальность mbstring и отсутствие хардкода доступов в конфигах и шаблонах. Первые два ловят технический долг, третий — потенциальную утечку.
Подробнее об услуге: поддержка и сопровождение сайтов на Битрикс.
Частые вопросы
Можно ли включить композит на страницах с авторизованными пользователями?
Да, но только в режиме динамического композита: в настройках модуля «Композитный сайт» включите опцию «Динамический композит» и оберните персональные блоки в компонент bitrix:main.include с параметром DYNAMIC или используйте $APPLICATION->ShowProperty. Статический композит для авторизованных отдаёт закешированную страницу и покажет чужие данные.
Чем отличается CPHPCache от managed cache (Bitrix\Main\Data\Cache) и что выбрать?
CPHPCache — старый API с ручным управлением через InitCache/StartDataCache/EndDataCache и обязательным указанием TTL и baseDir. Managed cache ($cache = Bitrix\Main\Data\Cache::createInstance()) сам инвалидируется по тегам и работает поверх того же механизма, поэтому для нового кода берите managed cache, а CPHPCache оставляйте в легаси-компонентах.
Что делать, если после включения Redis сайт стал работать медленнее?
Проверьте, что в .settings.php для секции 'cache' указан 'type' => 'redis' с корректными 'host' и 'port', а не оставлен файловый кэш при подключённом расширении. Также уберите кэширование сессий в Redis (параметр 'session' в 'redis' => ['session' => false]), если сессии пишутся часто — на них Redis часто становится узким местом.
А если у меня shared-хостинг без root — что реально можно ускорить?
Включите композит, OPcache (через .user.ini: opcache.enable=1, opcache.memory_consumption=128), HTML-кэш и кэш компонентов с большим TTL, а также переведите кэш на Memcached, если провайдер даёт сокет. Настройки MySQL и php-fpm без root недоступны — их меняет только хостер по тикету.
Как в панели производительности отличить проблему в PHP от проблемы в MySQL?
На вкладке «Время выполнения» смотрите колонку «Время» по компонентам и «Запросы» — если суммарное время SQL-запросов превышает 50% от общего, узкое место в БД (индексы, JOIN, кэш запросов). Если время уходит в компоненты при малом числе запросов — проблема в PHP-коде и отсутствии кэша.
Можно ли кэшировать результат компонента, который зависит от $_GET, не плодя сотни кэш-файлов?
Да, передайте в $arParams['CACHE_GROUPS'] => 'N' и сформируйте $cacheId только из значимых параметров, например md5(serialize([$arParams['IBLOCK_ID'], $_GET['SECTION']])). Не включайте в $cacheId весь $_GET — иначе получите отдельный кэш на каждый UTM-метку.