Рефакторинг Битрикса без остановки продаж: разбор сбоя
Магазин на Битриксе после рефакторинга оформления заказа не упал: заказы шли, но с неверной суммой и без писем. Разбираем, как такие сбои проходят мимо мониторинга и как их ловить.

Самые дорогие сбои при рефакторинге магазина на Битриксе не похожи на сбои. Сайт отвечает 200, корзина открывается, заказы появляются в базе. Но сумма в заказе расходится с корзиной, письмо клиенту уходит дважды или не уходит вовсе, а выгрузка в 1С встаёт на первом нестандартном заказе. Ниже обобщённый сценарий из типовых отказов такого рода и порядок работ, который помогает поймать их раньше клиента. Это не отчёт об одном проекте: замеров «до и после» здесь нет, и мы не будем их изображать.
Материал про «1С-Битрикс: Управление сайтом» с модулем «Интернет-магазин», ветки 23.x-24.x, PHP 8.x. Точную версию своей сборки проверьте в разделе обновлений административной панели: механизмы ниже старые, но детали API от версии к версии отличаются.
Сценарий: что переносили и что пошло не так
Магазину около семи лет. Кастомная логика оформления заказа разбросана по трём местам: обработчики событий в init.php, правки в шаблоне компонента заказа (result_modifier.php и template.php) и несколько агентов. Задача: собрать всё в собственный модуль в /local/modules, а шаблоны вернуть к штатному виду. Окна простоя нет, продажи идут круглосуточно.
Команда перенесла обработчики в модуль и зарегистрировала их там. Старые копии в init.php оставили «до проверки». Тестовые заказы прошли чисто: двойной вызов обработчика смены статуса даёт тот же результат, что одинарный. Такая операция называется идемпотентной [1]. А вот у заказов с купоном повторный пересчёт во второй раз сработал иначе, и итоговая сумма разошлась с корзиной. Обычные заказы выглядели нормально, поэтому ни один алерт не сработал.
Почему Битрикс ломается молча
Обработчики живут в двух местах
Событие можно подписать в коде: тогда регистрация происходит при каждом хите, обычно из init.php. А можно в базе, через RegisterModuleDependences: запись хранится в таблице b_module_to_module и переживает деплой. Отсюда две беды. Если подписку оставили и в коде, и в модуле, обработчик выполнится дважды. Если откатили код, а запись в базе осталась, она указывает на класс, которого в старой ветке нет. Что будет дальше, зависит от способа регистрации и версии: от молчаливого пропуска до фатальной ошибки. Проверяйте на копии, а не на проде.
Кеш помнит старый формат
Кеш компонентов и управляемый кеш лежат в /bitrix/cache и /bitrix/managed_cache, а при настроенных Redis или Memcached - там. После рефакторинга структура закешированных данных поменялась, и старый код, подхвативший чужую запись, читает несуществующие ключи. Там, где вы сами задаёте идентификатор кеша, добавьте в него номер релиза. И сбрасывайте кеш как часть деплоя, а не «когда вспомнят».
Агенты и обмен с 1С
Агент в Битриксе - это строка с вызовом, которая хранится в таблице b_agent. Переименовали метод, а запись осталась: агент зовёт то, чего нет. Ошибку вы увидите, только если включено журналирование исключений. Обмен с 1С по CommerceML так же молчалив: если вы изменили статусы или поля заказа, выгрузка не падает с ошибкой, а просто копит невыгруженные заказы.
| Что трогали | Как ломается | Где смотреть |
|---|---|---|
| Обработчики событий | Двойной вызов или вызов исчезнувшего класса | init.php и записи в b_module_to_module, сравнить до и после |
| Шаблон компонента заказа | JS не находит поля формы, шаг оформления обрывается | Воронка по шагам оформления в веб-аналитике |
| Кеш | Старый код читает новый формат | Ошибки в логах сразу после деплоя, сброс кеша |
| Агенты | Вызов несуществующего метода | b_agent: даты последнего и следующего запуска |
| Обмен с 1С | Заказы копятся без выгрузки, ошибок нет | Журнал обмена, число невыгруженных заказов |
Мониторинг, который проверяет код ответа, не видит сбоя, при котором магазин отвечает 200 и считает деньги неправильно.
Порядок работ, при котором продажи не встают
Общая схема - перехват функций новым кодом по частям, пока старый не станет ненужным [2]. В Битриксе у неё есть своя специфика.
- Зафиксируйте поведение до изменений. Соберите контрольные заказы: обычный, с купоном, с самовывозом, с онлайн-оплатой, с нестандартной доставкой. Для каждого запишите итоговую сумму, число писем, статус и факт выгрузки в 1С. Это ваш эталон.
- Проведите инвентаризацию. Найдите все места с кастомной логикой: init.php (в /bitrix/php_interface/ или /local/php_interface/), шаблоны, агенты, записи в b_module_to_module. Перенести то, что не нашли, нельзя.
- Сначала переносите, потом улучшайте. Один коммит - одно перемещение без смены поведения. Если в одном изменении и перенос, и правка логики, причину расхождения вы уже не найдёте.
- Прячьте новый путь за флагом. Храните его в настройках модуля и читайте при каждом вызове, а не в константе при старте. Выключили флаг - сработал старый код.
- Включайте поэтапно. Сначала для сотрудников с тестовыми заказами, потом для части трафика. Долю трафика проще всего отделить на уровне nginx по cookie. Отдельную копию сайта с общей базой не используйте: два кода будут писать в одни таблицы.
- Деплойте атомарно. Релиз в отдельный каталог, переключение символической ссылкой, затем сброс кеша. Если в PHP отключена проверка времени изменения файлов (opcache.validate_timestamps), без перезагрузки php-fpm новый код не подхватится.
- Отрепетируйте откат. Заранее выпишите, что откатывается кодом, а что нет: записи в базе, свойства инфоблоков, содержимое кеша.
Что мерить вместо выдуманных цифр
Нормы у каждого магазина свои, поэтому любые чужие «было 2%, стало 0,3%» вам ничего не скажут. Мерьте своё и сравнивайте с тем же днём недели и часом, с поправкой на акции:
- расхождение суммы заказа с суммой позиций корзины на выборке свежих заказов из b_sale_order;
- число писем «новый заказ» на один заказ по таблице b_event: должно быть ровно одно;
- заказы без выгрузки в 1С старше выбранного вами порога;
- конверсия шага «оформление - заказ» по часам;
- время ответа страницы оформления, 95-й процентиль. Для него в формат лога nginx нужно добавить переменную $request_time.
Где подход ломается
- Изменения схемы данных. Свойства инфоблоков и таблицы нельзя переименовать в один шаг: сначала добавить новое, писать в оба места, потом переключить чтение. Миграции схемы - отдельная тема, здесь не разбираем.
- Общий кеш в Redis или Memcached. Старый и новый код делят его в момент деплоя, и без номера релиза в ключах они читают чужие данные.
- Клиент оформляет заказ во время деплоя. Корзина и сессия живут дольше релиза, поэтому новый код должен читать данные, созданные старым.
- Сторонние модули на тех же событиях. Порядок задаётся сортировкой при регистрации, и полагаться на него можно, только если вы зафиксировали его явно.
- Маленький магазин с ночным затишьем. Два контура и флаги могут стоить дороже, чем согласованное окно в четыре часа ночи.
Что делать дальше
Начните с двух вещей, которые не требуют рефакторинга. Соберите таблицу контрольных заказов и повесьте оповещение на два показателя: заказ без письма и заказ без выгрузки в 1С дольше порога. Потом выпишите все места с кастомной логикой и прогоните откат на копии с обезличенной базой. Копию отключите от боевых платёжных систем, почты и обмена с 1С.
[1] Идемпотентность. Свойство операции: повторный вызов не меняет результат первого.
[2] Душитель (strangler fig). Подход к миграции: новый код постепенно берёт на себя функции старого, пока старый можно не удалить без последствий.
Оформление по ГОСТ: Поля, шрифты, нумерация, сноски, титульный лист
Спросить за 10 ₽Источники: Документация для разработчиков 1С-Битрикс, PHP: настройки OPcache
Частые вопросы
Что делать, если у меня другая версия Битрикса или PHP?
Как понять, что обработчики событий выполняются дважды?
Можно ли обойтись без тестовой копии сайта?
Какие подводные камни в проде чаще всего?
Если новая логика уже приняла заказы, а я откатился, что с ними?
Сколько держать старый код после переключения?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.