Как стабилизировали сайт на 1С-Битрикс и обмены с 1С: кейс Pasionaria HoReCa
В оптовом HoReCa-направлении Pasionaria тяжёлые обмены с 1С занимали те же веб-процессы, которые обслуживали покупателей. Мы перенесли импорты в отдельные консольные задания, ограничили одновременную обработку и разделили получение данных, изменение базы и подтверждение файла на FTP. Заказы и контрагенты получили собственные расписания и статусы.
Работа шла несколькими этапами в июле–сентябре 2026 года. Разберём, какие причины нашли за похожими симптомами, зачем понадобились две разные очереди и как проверяли их на реальных файлах. Результаты отдельных прогонов показывают поведение обмена; измерений скорости пользовательских страниц до и после в этом кейсе нет.
Как отделили нехватку памяти от других причин
На первом этапе было важно не угадать причину, а разделить симптомы. Поэтому начали с состояния операционной системы: память, swap, процессы, диск, журнал ядра, текущие cron-задачи, состояние MySQL и Apache. Такой подход показал повторные завершения MySQL и Apache из-за нехватки памяти. Эти записи подтверждали дефицит памяти, но сами по себе не определяли единственный процесс, который его вызвал.
В одном июльском снимке сервер имел 7,8 ГБ RAM, а swap был занят целиком: 4 ГБ из 4. Load average оставался около 1. По журналу ядра видно, что OOM killer завершал MySQL и процессы Apache. Поэтому в проверку включили память и swap, RSS и длительность процессов, свободное место, cron и журналы ядра — одного показателя нагрузки было недостаточно.
Одновременно сделали инвентаризацию фоновых задач. Выяснилось, что значительная часть старых обменов запускается через HTTP: cron вызывает URL сайта, запрос проходит через nginx и Apache, поднимает Bitrix и только после этого запускает импорт. Среди таких задач были обновления номенклатуры, экспорт заказов, HoReCa-обмены и другие регулярные процессы.
Главный недостаток такой схемы — тяжёлая фоновая работа конкурирует с реальными пользователями за один и тот же пул веб-процессов. Кроме того, период запуска некоторых заданий был меньше или сопоставим с длительностью самого задания. Это означало риск пересечения разных тяжёлых операций, даже если каждый отдельный cron формально работал правильно. Перенос в CLI освобождает веб-процессы, но память и процессор сервера остаются общими: число одновременных фоновых заданий всё равно нужно контролировать.
Как вынесли фоновые операции из веб-запросов
Первым архитектурным изменением стал перенос тяжёлых импортов из HTTP в отдельные консольные команды. Вместо цепочки «cron → URL сайта → Apache/PHP → импорт» появились специальные сценарии командной строки, которые загружали окружение Bitrix и выполняли ту же бизнес-операцию напрямую.
Отдельные запуски номенклатуры измеряли через /usr/bin/time -v: они занимали примерно от 2 минут 51 секунды до 5 минут 9 секунд. Максимальный RSS составлял около 575–576 тысяч КБ, то есть примерно 0,55 ГиБ. Это замеры отдельных импортов, которые помогали планировать параллельность, а не скорость работы сайта для посетителя.
На первый взгляд разница небольшая, но для эксплуатации она принципиальна. Фоновая работа перестала занимать процессы, предназначенные для посетителей сайта. Появились нормальные коды завершения, ограничения по времени, отдельные журналы, измерение длительности и потребления памяти, а также возможность централизованно управлять очередностью запусков.
Параллельно ввели общую блокировку для тяжёлых бизнес-операций. До этого защита от параллельного запуска существовала местами и в основном предотвращала два одинаковых cron. Новая схема решала более важную задачу: не позволяла одновременно запустить две разные тяжёлые операции, которые обе меняют одни и те же данные.
Если источник изменился при загрузке: импорт откладывается, задача сохраняется до новой попытки.
В одном наблюдении шесть сигналов об одном разделе оставались одной ожидающей задачей. Это объединение уведомлений об актуальных данных; поток отдельных заказов требует собственной обработки.
Следующий шаг оказался ещё важнее. Раньше сигнал «данные изменились» мог сразу запускать полную обработку. Но для большого обмена это неэффективно: если 1С за короткое время несколько раз обновила одну и ту же группу данных, сайт получал несколько поводов заново выполнить одну и ту же тяжёлую работу.
Мы изменили модель. Событие от 1С стало только фиксировать, что определённый раздел номенклатуры требует обновления. Тяжёлая обработка выполнялась позже отдельным обработчиком очереди. Несколько одинаковых сигналов объединялись в одну задачу: в одном реальном примере одно и то же обновление было запрошено шесть раз, но в очереди оставалась одна ожидающая задача.
Номенклатуру также разделили по приоритетам. Более критичные разделы проверялись чаще, фоновые — реже. При этом частый запуск обработчика не означал частый полный пересчёт: если изменений нет, он просто завершался.
Как проверяют выгрузку до изменения базы
После ограничения параллельности обнаружилась проблема другого уровня. FTP-каталог, куда 1С выкладывает данные, нельзя было считать неподвижным набором файлов. Пока сайт скачивал большой набор XML, 1С могла заменить файл, дописать новый или добавить очередную директорию. Для импорта, который идёт несколько минут, это означает риск собрать один набор данных из разных версий выгрузки.
Поэтому перед изменением базы сайт стал сначала формировать локальный снимок нужных данных. Проверялись размеры и время изменения файлов, состав каталогов и корректность XML. Только после того, как набор полностью скачан и прошёл предусмотренные проверки, начиналась бизнес-обработка. Локальный набор после загрузки используется без изменений. Проверки размеров, времени изменения, структуры и XML выявляют признаки повреждения или смены источника; это не транзакционный снимок, предоставленный самой 1С.
Это не была перестраховка «на будущее»: система реально ловила ситуацию, когда источник изменился во время копирования. В таком случае импорт не запускался по частично обновлённым данным, а задача оставалась в очереди до следующей попытки.
После появления локальных снимков начали проявляться более тонкие случаи. Например, остатки могли ссылаться на вариант товара, которого текущая модель сайта не знала. Отдельные проверки стали останавливать потенциально опасную синхронизацию вместо безусловного применения всего, что пришло из 1С.
Другой показательный случай возник с проверкой структуры выгрузки. Защита была специально строгой: если ожидаемая директория пропала, набор считался подозрительным. Но затем 1С совершенно легитимно добавила новый раздел goods/14. Формально структура изменилась, и защита сама стала блокировать нормальное обновление.
Проверку уточнили: добавление корректного числового раздела goods/N разрешается, а исчезновение уже известных разделов остаётся причиной остановки. Так удалось отличить расширение каталога от неполной выгрузки и возобновить обновление.
Зачем очередям нужны правила хранения
Локальные снимки, история запусков и кэш решили проблему согласованности, но закономерно начали занимать место. В один из периодов история выросла примерно до 15 ГБ, кэш — до 16 ГБ, а активные и неуспешные наборы добавляли ещё несколько гигабайт.
Поэтому добавили очистку по возрасту и ограничение числа сохраняемых версий. В отдельных контрольных снимках кэш занимал около 2,4 ГБ, активные данные — 195 МБ, история — 1,1 МБ. Эти замеры относятся к отдельным моментам эксплуатации, а не к единому сравнению до и после. Правила хранения пришлось дорабатывать по мере накопления новых данных.
Почему следующая ошибка 502 оказалась другой проблемой
Позже сайт снова начал зависать и отдавать 502. Было бы легко решить, что новая очередь импорта снова перегружает сервер. Но проверка показала обратное: по данным этой диагностики процессор был почти свободен, тяжёлых запросов MySQL не было, блокировки новой системы импорта были свободны.
Диагностика Apache показала другую цепочку. Один пользовательский запрос держал блокировку PHP-сессии и одновременно ждал ответа внешней системы. Следующие запросы той же сессии останавливались на ожидании этой блокировки. Постепенно занялись все доступные процессы Apache, очередь к backend переполнилась, и nginx начал возвращать 502.
Дальше проблему проследили до конкретного AJAX-запроса доставки и старого модуля B2C. Временная трассировка показала 99 обращений к legacy-обработчику из одного и того же сценария, а прямой тест старого внешнего endpoint доходил до тайм-аута около 20 секунд.
Перед отключением старых обработчиков сохранили их регистрации и подготовили обратный SQL-сценарий. После этого 12 старых обработчиков событий отключили мягко, без удаления самого модуля и с возможностью отката. Для этой истории важно не само число 12, а подход: внешне одинаковый симптом «сайт тормозит» оказался другой неисправностью, и её не стали лечить предыдущим решением наугад.
Как разделили обработку документа и подтверждение FTP
Обработка успешна, но FTP не подтвердился: повторяем подтверждение, сохраняя результат бизнес-операции.
Процесс остановился, результат записи неизвестен: выделяем элемент для разбора; автоматический повтор бизнес-операции запрещён.
Обычная итерация обрабатывает не более одного файла. Накопившийся объём разбирается отдельным контролируемым проходом.
К сентябрю похожий подход понадобился для файлов обмена — документов и заказов. Для них была сделана отдельная очередь с понятными состояниями: файл сначала загружается, затем передаётся в обработку, после успешной бизнес-операции помечается обработанным и только потом подтверждается на стороне FTP.
Обычная итерация специально ограничена: она не пытается проглотить весь накопившийся объём, а берёт небольшую порцию. Состояние обхода FTP сохраняется, поэтому при каждом запуске не требуется сканировать всё дерево заново.
Особенно важным оказалось разделение «бизнес-операция выполнена» и «удалённый файл подтверждён». Если обработка заказа уже прошла, но переименование файла на FTP временно не удалось, нельзя просто выполнить заказ повторно. Поэтому состояние каждого файла стало храниться отдельно, а повторные попытки подтверждения не запускают бизнес-импорт заново.
Первая версия новой очереди обнаружила неприятный дефект живучести: один файл остался в состоянии «обрабатывается» ещё с августа и блокировал дальнейшую работу. Сам файл был корректным, но обработчик не мог понять, начиналась ли бизнес-операция, поэтому каждый новый запуск останавливался на нём.
После разбора появились правила повторных попыток и «карантин» для неоднозначных случаев. Если видно, что бизнес-обработка точно не начиналась, файл можно безопасно вернуть в очередь. Если признаки начала уже есть, автоматический повтор запрещён: такой элемент выводится отдельно для ручного разбора. Ошибка подтверждения FTP также не должна запускать бизнес-операцию второй раз.
Для неоднозначного состояния выбрали отдельный разбор. Признаки начавшейся обработки запрещают автоматический повтор, пока не выяснено, что уже изменилось в базе. Такая очередь сохраняет этапы выполнения, но сама по себе не доказывает однократное применение бизнес-операции при любом возможном сбое.
Один из самых необычных production-кейсов возник уже после того, как очередь в целом заработала. В имени удалённой директории оказался символ неразрывного пробела, представленный в этом пути байтом 0xA0. На старой версии PHP с настройкой mbstring.func_overload разные строковые функции по-разному считали позицию символов и байтов. В результате путь для подтверждения на FTP собирался неправильно.
Исправление было небольшим, но показательно: операции со строкой для FTP-пути перевели в явный байтовый режим. После этого тот же каталог начал подтверждаться нормально. Такие случаи невозможно предусмотреть только по ТЗ — они появляются уже при работе с историческими данными и старой средой.
Как проверили очередь и восстановили обмен контрагентами
После восстановления работы документов накопившийся объём прогнали отдельным контролируемым проходом. За три часа система выполнила 2516 итераций и перевела в подтверждённое состояние ещё 2015 документов. Ошибок, отправленных в failed, в этом прогоне не было, а очередь к концу опустела.
Когда документы уже работали через новую очередь, обнаружился ещё один класс проблем: при переходе старый общий обработчик был выключен, документы получили новый запуск, а контрагенты — нет. Система при этом не падала, поэтому ошибка могла долго оставаться незаметной. В интерфейсе продолжала отображаться старая дата общего обмена.
Для контрагентов сделали отдельный обработчик в той же безопасной схеме и развели расписание документов и контрагентов по времени. Статус в интерфейсе тоже разделили: теперь отдельно видно последнее успешное обновление заказов и отдельно — контрагентов.
Реальная проверка на production-файле размером около 1,1 МБ содержала 257 записей. Предварительная проверка разобрала существующих пользователей, конфликты и отсутствующие обязательные данные, а бизнес-проход выполнил 190 обновлений без ошибок и корректно подтвердил файл на FTP. Это хороший пример того, что обмен контрагентами — не просто массовый INSERT, а отдельный процесс сопоставления и обновления существующей базы.
Что получил бизнес и как принимать подобную работу
Для бизнеса результат — управляемое обновление данных: видно, какой поток работает, что ожидает обработки и где нужен разбор. Номенклатура объединяет повторные сигналы, документы отдельно хранят результат обработки и подтверждение FTP, а контрагенты имеют собственный запуск. На стороне эксплуатации появились контроль состояний, журналы и очистка временных наборов.
При приёмке похожей переработки попросите команду показать четыре сценария: повторный сигнал об одной выгрузке; изменение FTP-файла во время загрузки; успешный импорт с ошибкой подтверждения; остановку процесса после возможного начала записи. Для каждого должно быть понятно, что система повторит, что сохранит и какой случай передаст специалисту. Затем проверьте полный перечень потоков старого расписания: товары, заказы, контрагенты и экспорты должны получить новый способ запуска или обоснованное решение об отключении.
Другие этапы проекта Pasionaria: интеграция программы лояльности и автоматическая сегментация клиентов в CRM.