Большой каталог сам по себе еще не делает интеграцию сложной. В нашем проекте интеграции сайта с 1С сейчас 211 761 позиция номенклатуры. Передать такой объем можно, вопрос скорее в том, сколько ресурсов и времени на это потребуется. Проблемы начинаются позже: когда данные нужно не просто выгрузить, а отобрать, обработать, регулярно обновлять и доставить в другую систему.

Проект работает на связке 1С:Управление торговлей + интернет-магазин на Bitrix. Источником основных данных остается 1С:там находятся номенклатура, склады, цены и остатки, а сайт в основном работает как витрина. В обратную сторону с сайта в 1С приходят заказы.

Изначально для этой связки использовали типовой обмен. Но проект развивался: появились дополнительные отборы и обработка данных, разные сущности стали требовать разной скорости обновления. В какой-то момент часть данных начала приходить на сайт с задержкой.

Мы не стали переписывать интеграцию целиком. Архитектура менялась постепенно, по мере того как в production проявлялось очередное ограничение. Сначала оказалось, что тяжелая полная выгрузка конкурирует с оперативными изменениями.

 Когда их развели по времени, уперлись в скорость обработки на стороне сайта. Когда начали разделять большой обмен на отдельные потоки, нашли bottleneck уже внутри 1С. Позже появились новые потребители данных, 1С:Шина и новый вопрос — как контролировать доставку при асинхронной передаче.

Ниже — этот путь в том порядке, в котором мы его проходили с командой ASAP.

Первое узкое место: полный обмен работал днем вместе с инкрементальным

Изначально полный и инкрементальный обмены могли работать в течение дня одновременно. Пока данных было меньше, это не создавало заметных проблем. С ростом базы полная выгрузка стала достаточно тяжелой, чтобы конкурировать с оперативными обновлениями.

Сейчас в базе 211 761 позиция номенклатуры, но в полный обмен попадает не весь справочник. После фильтрации помеченных элементов и групп остается 178 740 позиций. Отдельно выгружаются 15 370 действующих карт лояльности.

Как устроен полный обмен

Полный обмен строится прямым запросом к базе. План обмена здесь не используется: система собирает актуальный слепок данных.

Для номенклатуры передаются идентификатор ссылки и артикул. Цены берутся из среза последних регистра ЦеныНоменклатуры для видов цен «Розница», «Опт1» и «Опт2». Остатки из регистра РаспределениеЗапасов, в основном из поля Свободно.

Одним сообщением весь каталог не отправляется. Выборка режется на порции по 1500 позиций, между которыми выдерживается пауза около 10 секунд. Для 178 740 позиций получается примерно 120 порций. Только паузы между ними дают около 20 минут, не считая времени на формирование и передачу данных. Еще примерно 11 порций приходится на карты лояльности.

Полезная нагрузка для HTTP и шины формируется одинаково. При отправке через шину к сообщению дополнительно добавляются служебные параметры type, version и packet.

Как 178 740 позиций уходят на сайт 
Как 178 740 позиций уходят на сайт 

Тяжелый полный обмен в итоге вынесли на ночь. Его задача — периодически получить полный согласованный слепок данных. В течение дня остались небольшие инкрементальные обмены, задача которых быстро доставлять изменения. 

Как работает инкрементальный обмен

Здесь механизм уже другой. Для оперативных изменений используются планы обмена. Когда объект из состава плана записывается в 1С, платформа регистрирует его для соответствующего узла. Важно, что в плане хранится не набор изменившихся полей, а ссылка на изменившийся объект. При очередном запуске обработчик вызывает ПланыОбмена.ВыбратьИзменения, получает зарегистрированные ссылки и обычным запросом заново собирает актуальное состояние объектов.

То есть это не классическая передача дельты вида «у товара X поле Цена изменилось со 100 на 110». По сути механизм говорит: «объект X изменился», после чего перед отправкой 1С снова собирает его текущее состояние.

На прямом HTTP-пути это заодно давало простой механизм подтверждения. Если сайт успешно обработал запрос и вернул 2xx, регистрацию изменения можно снять. Если запрос завершился ошибкой, объект остается в плане и снова попадет в выборку при следующем запуске.

Отдельной очереди ретраев или счетчика попыток здесь нет. Пока регистрация существует, система будет снова пытаться отправить актуальное состояние объекта.

Для некоторых связанных сущностей подтверждение дополнительно согласовано. Например, регистрацию изменений скидок можно снять только после успешной отправки самих скидок и связанных с ними товаров.

Инкрементальный обмен через план обмена
Инкрементальный обмен через план обмена

Так тяжелый полный слепок и небольшие оперативные изменения перестали конкурировать за одно и то же дневное окно. Но задержки полностью не исчезли.

Второе узкое место: 1С успевала отправить пакет, сайт не всегда 

Одним из наиболее чувствительных потоков были цены и остатки. Для интернет-магазина это данные, которые желательно обновлять как можно быстрее: если товар уже закончился или его цена изменилась, старая информация на витрине быстро становится проблемой.

Со стороны 1С обмен можно было запускать часто. В какой-то момент рассматривался интервал порядка двух минут. Но увеличение частоты само по себе не ускоряет систему, если следующая сторона цепочки работает медленнее.

На стороне сайта пакет принимал PHP-скрипт. И при накопившемся объеме логики он не всегда успевал полностью обработать предыдущую порцию до запуска следующего обмена. Если в такой ситуации просто уменьшить интервал, получится обратный эффект: новые пакеты будут приходить быстрее, чем сайт успевает разбирать старые.

Поэтому фактический интервал онлайн-обмена остановился на пяти минутах.

Проблема здесь оказалась не в скорости формирования JSON или HTTP как таковом. Один приемник на стороне сайта со временем начал выполнять слишком много работы.

Одним из наиболее чувствительных потоков были цены и остатки. Для интернет-магазина это данные, которые желательно обновлять как можно быстрее: если товар уже закончился или его цена изменилась, старая информация на витрине быстро становится проблемой.

Со стороны 1С обмен можно было запускать часто. В какой-то момент рассматривался интервал порядка двух минут. Но увеличение частоты само по себе не ускоряет систему, если следующая сторона цепочки работает медленнее.

На стороне сайта пакет принимал PHP-скрипт. И при накопившемся объеме логики он не всегда успевал полностью обработать предыдущую порцию до запуска следующего обмена. Если в такой ситуации просто уменьшить интервал, получится обратный эффект: новые пакеты будут приходить быстрее, чем сайт успевает разбирать старые.

Поэтому фактический интервал онлайн-обмена остановился на пяти минутах. Проблема здесь оказалась не в скорости формирования JSON или HTTP как таковом. Один приемник на стороне сайта со временем начал выполнять слишком много работы.

Третье узкое место: в один обмен со временем сложили слишком много данных

Исторический обмен назывался «цены и остатки», но постепенно перестал соответствовать своему названию. Помимо цен и остатков в этот же поток добавились дисконтные карты, данные для рассрочки и другая логика. На стороне сайта все это разбирал общий скрипт-приемник.

Получилась неприятная связность: проблема в обработке одной сущности могла задержать остальные данные в том же потоке. Например, даже если цены сформировались быстро, они все равно зависели от общего времени работы приемника.

Поэтому обмен начали разделять по сущностям. Вместо одного большого процесса появились отдельные потоки для сопутствующих товаров, промоакций, промотоваров, скидок и наценок, товаров по скидкам, дополнительных реквизитов, деактивации и других данных.

У разных потоков появились и разные размеры порций: скидки по 500 скидок, промо-товары по 1000 связей промо-акции с товаром, деактивация по 1000 товаров, дополнительные реквизиты по 5000 строк. 

У товаров по скидкам жёсткий лимит не подошёл. Порция резалась ровно по 100 строк связей скидки с товаром, и скидка, на которой пакет оборвался, теряла весь хвост: следующая порция стартовала уже со следующей скидки. А поскольку сайт заменяет список товаров скидки целиком, обрезанный список затирал на витрине остальные товары этой скидки. Пришлось сделать лимит в 100 строк ориентировочным и набирать порцию скидками целиком. 

Это не только уменьшило связанность процессов. Когда обмены разделились, стало проще понимать, какой именно из них тормозит или падает.

Но при разделении обнаружилась еще одна проблема. На этот раз не между 1С и сайтом, а непосредственно внутри 1С.

Четвертое узкое место: товары по скидкам часами собирались внутри 1С

Скидка сама по себе передается достаточно просто. Сложнее получить список товаров, на которые она действует. 

Почему обычным запросом это не выбиралось 

Исторически состав товаров по скидке не хранился как обычная таблица связей «скидка → номенклатура». В справочнике СкидкиНаценки  реквизит ХранилищеНастроекКомпоновкиДанных типа ХранилищеЗначения, внутри которого лежат сохранённые настройки отбора номенклатуры. 

Чтобы получить товары одной скидки, приходилось прочитать объект скидки, распаковать хранилище, разобрать элементы отбора — «равно», «в списке», «в списке по иерархии» — и по ним выполнить отдельный запрос к справочнику номенклатуры. Затем повторить всё это для следующей скидки. 

Присоединить такое хранилище обычным запросом нельзя: это двоичное поле, а не таблица данных. Поэтому при росте количества скидок линейно росло число отдельных чтений и запросов. 

Связи вынесли в регистр 

Для нового механизма связь скидки с номенклатурой стали хранить явно — в регистре ДействиеСкидокНаценокПоНоменклатуре.

Теперь для переведенных на этот механизм скидок вместо распаковки хранилища и отдельного запроса по каждой скидке данные можно получить одним запросом к регистру. На рабочих данных время расчета сократилось с часов до минут.

Здесь есть важная оговорка. На момент технического замера перенос еще не был завершен для всех скидок. Поэтому это не история «мы поменяли хранилище и полностью закрыли проблему», а постепенная миграция: новый механизм уже проверен на production-данных, но часть скидок продолжает работать по старой схеме.

Как изменили получение товаров по скидкам 
Как изменили получение товаров по скидкам 

Сам обмен при этом тоже разделили: справочник скидок и товары, участвующие в скидках, передаются отдельными потоками.

К этому моменту интеграция уже перестала быть одной связкой «1С → сайт». Потоков становилось больше, а данные понадобились нескольким потребителям. Следующим ограничением стала уже сама схема прямых HTTP-вызовов.

Когда получателей стало больше, между 1С и потребителями появилась шина

При прямом HTTP источник знает конкретного получателя. 1С формирует данные, выполняет POST на endpoint сайта и ждет ответа.

Для одного потребителя такая схема достаточно прозрачна:

1С → HTTP → сайт → 2xx → 1С

Но при появлении нескольких потребителей возникает дополнительная связность. Если те же данные нужны еще одной системе, маршрут до нее и логику отправки приходится добавлять со стороны 1С. Поэтому часть обменов начали постепенно переводить на 1С:Шину.

Как устроен путь шины

1С:Шина работает отдельным приложением на выделенном сервере. Для доставки используется AMQP. Со стороны 1С работает интеграционное расширение, а сообщения передаются в шину, после чего маршрутизируются к нужным потребителям. В технической конфигурации один из таких маршрутов описан схемой FromUtToSite.

Разные сущности различаются параметром type. Например, related используется для сопутствующих товаров, promos — для промоакций, discounts — для скидок, discount_products — для товаров по скидкам, а prices_stocks — для цен и остатков. Для последнего дополнительно используется scope: full для полного обмена и online для оперативного.

Сейчас через шину проходит семь отдельных обменов, а также полный и онлайн-обмен цен и остатков. При этом часть справочных обменов «по изменению» продолжает работать через HTTP.

Миграцию делали не одним переключением всей интеграции, а поэтапно. Сначала через шину пошли полные выгрузки и потоки, которые нужно было рассылать нескольким потребителям. Затем туда перевели онлайн-обмен цен и остатков.

Для него в 1С предусмотрен отдельный переключатель транспорта — константа АСАП_ОнлайнОбменЦеныОстаткиЧерезШину. Если она включена, старый прямой HTTP-путь не выполняет отправку.

Как устроена шина
Как устроена шина

У шины другая семантика подтверждения

Здесь появился важный архитектурный нюанс.

При прямом HTTP ответ приходит от конечного сайта. Если 1С получила успешный 2xx, она знает, что запрос дошел до конкретного обработчика, и может принять решение о снятии регистрации изменения.

С шиной цепочка становится асинхронной:

1С → публикация → шина → потребитель

Для 1С успешная публикация означает, что сообщение принято шиной. Это уже не тот же самый 2xx от конечного PHP-обработчика сайта.

Поэтому перевод обменов на шину — не просто замена одного транспорта другим. Вместе с транспортом меняется модель контроля доставки. Если потребитель недоступен, сообщение остается в шине. Для недоставленных сообщений есть отдельный канал, а уведомления об ошибках доставки формируются уже на стороне шины.

Зато добавление нового получателя теперь не требует дописывать очередной маршрут рассылки в конфигурации 1С: подключение делается на стороне шины. В обратном направлении через шину принимаются и заказы с сайта. На этапе перехода при этом сохранен и прямой HTTP-endpoint для приема заказов.

Один зависший запуск привел к сотням пропущенных обменов

После разделения потоков обнаружился ещё один класс проблем. Интеграция может не выдавать ошибку ни на одном запуске и при этом фактически не обновлять данные.

Часть обменов по изменению защищена от параллельного запуска mutex-маркером. Это не блокировка в привычном смысле, а запись в журнале регламентных заданий: MUTEX_LOCK с ключом и временем на входе, MUTEX_UNLOCK на выходе. Новый запуск смотрит последний маркер по своему ключу: если это LOCK и ему меньше 1800 секунд, запуск выходит и пишет в журнал «Пропуск запуска».

У такой схемы есть потолок. Маркер не продлевается работающим процессом, поэтому через 30 минут он считается протухшим, и следующий запуск забирает блокировку независимо от того, закончил предыдущий или нет. При интервале в пять минут один затык даёт максимум шесть пропусков подряд, дальше система расклинивается сама. Обратная сторона та же: если обмен действительно работает дольше получаса, защита молча исчезает ровно тогда, когда она нужна, и два процесса идут параллельно.

Когда обменов стало много, понадобилось разделить и диагностику

После дробления общего обмена появилась возможность отдельно видеть состояние разных потоков. В 1С мы сделали регистр журнала регламентных заданий. Отдельные статусы есть для сопутствующих товаров, промоакций, промотоваров, скидок и наценок, товаров по скидкам и полного обмена. Отдельно в журнал попадают mutex-маркеры.

Это заметно лучше исходной схемы с одним большим потоком. Если падает выгрузка товаров по скидкам, по журналу можно увидеть именно ее, а не просто обнаружить, что «обмен с сайтом опять не работает».

Но здесь важно не приписывать текущей системе то, чего в ней пока нет. Полноценной единой панели состояния всех обменов сейчас нет. Для прямых HTTP-потоков нет и общего активного оповещения об ошибках. Автоматический алертинг реализован на стороне шины для недоставленных сообщений.

Инцидент с mutex хорошо показывает, почему одного контроля исключений недостаточно. Для часто выполняемого обмена полезно знать не только появилась ли ошибка, но и когда поток последний раз успешно завершался. Несколько сотен последовательных пропусков сами по себе уже являются состоянием, на которое стоит реагировать.

Что получилось после переделки

Получившуюся архитектуру нельзя свести к одному изменению вроде «поставили шину и все ускорилось». Эффект появился из нескольких решений: тяжелую сверку вынесли из рабочего времени, оперативные изменения оставили отдельным механизмом, общий обмен начали дробить по сущностям, для скидок изменили способ получения связей, а часть интеграции отвязали от конкретных потребителей через шину.

Это уже можно проверить не только по архитектуре, но и по рабочим данным.

Онлайн-обмен успевает разбирать изменения

За 31 день медианное количество изменений номенклатуры составляло 490 в сутки, среднее — 810, а максимум — 3723.

На момент замера 14 августа очередь плана ОстаткиИЦеныДляСайта была равна нулю. То есть при наблюдаемом потоке система успевала разобрать зарегистрированные изменения. Одного такого замера недостаточно, чтобы утверждать, что запас одинаков в любой пиковый час, но backlog в момент проверки отсутствовал.

Большинство дней прошло без ошибок обмена

По журналу регламентных заданий за тот же 31-дневный период ошибки обменов были зарегистрированы в трех днях. Остальные 28 дней прошли без записей об ошибках.

Это не означает, что интеграция стала неуязвимой: тот же случай с mutex показывает, что отсутствие обычной ошибки еще не гарантирует свежесть данных. Но теперь отдельные потоки проще диагностировать, а недоставленные сообщения шинной части контролируются отдельно.

Оптимизация скидок пока дает частичный результат

Для скидок, уже переведенных на новый регистр связей с номенклатурой, расчет вместо часов занимает минуты. Но миграция еще не закончена, поэтому этот результат пока относится именно к переведенной части данных, а не ко всему механизму скидок.

Метрика

Было

Стало

Основание

Полный обмен, устойчивость

Крупные пакеты упирались в таймаут, обмен срывался

Проходит стабильно, вынесен в ночь

Разбор кода выгрузки

Задержка цены и остатка на сайте

Десятки минут в пик

Очередь пуста, задержка ограничена интервалом: 1-5 минут

Замер очереди плана обмена

Изменений номенклатуры в сутки

Не измерялось

Медиана 490, среднее 810, пик 3 723

Замер по регистру версий за 31 день

Ошибок обмена в сутки

Не измерялось

28 дней из 31 полностью без ошибок

Замер по журналу регламентных заданий

Расчёт товаров по скидкам

Часы

Минуты там, где перенос выполнен

см. схема "Как изменили получение товаров по скидкам"

211 тысяч товаров оказались не главной проблемой

Когда смотришь на такую систему впервые, естественно предположить, что главная причина проблем — большой справочник номенклатуры. В базе действительно больше 211 тысяч позиций, а один полный обмен включает почти 179 тысяч из них.

Но в нашем случае объем оказался только одной частью задачи. Полный каталог можно выгрузить порциями и вынести тяжелую операцию на ночь. Гораздо больше проблем появляется на границах процессов: когда полный и оперативный обмены конкурируют друг с другом, когда приемник работает медленнее отправителя, когда в один поток постепенно складываются разные сущности, когда для получения данных приходится выполнять сотни отдельных компоновок или когда транспорт меняется с синхронного HTTP на асинхронную шину.

Поэтому итоговая архитектура получилась не в результате одного большого рефакторинга. Она складывалась последовательными изменениями: развели обмены по времени, затем по сущностям, оптимизировали тяжелую обработку внутри 1С, часть потоков вынесли на шину и отдельно занялись диагностикой каждого обмена.

И, пожалуй, самый полезный результат этой истории в том, что производительность интеграции определяется не размером каталога как таковым. Важнее весь путь изменения с момента записи объекта в 1С до момента, когда актуальные данные действительно может использовать конечный потребитель.