Файл CommerceML передан целиком, 1С показывает зелёную галочку, а в каталоге сайта по-прежнему вчерашние остатки. Кто виноват? Бухгалтер скажет, что выгрузка прошла. Администратор сайта скажет, что товаров нет. Оба правы, и в этом вся суть: «передача файла» и «обновление каталога» — два разных события, между которыми лежит отдельная работа. Если строить собственный узел обмена, то именно на этом шве он и держится.
Эта статья про то, как разложить обмен на этапы, которые нельзя склеивать в одну операцию, и что из этого стоит держать на своём VPS. Мы пройдём путь одного файла от авторизации до записей в каталоге, а потом посмотрим, что делать с повторами, зависшей обработкой и данными, которые меняются прямо во время обмена.
Три предмета обмена и три владельца
Прежде чем говорить о протоколе, полезно ответить на скучный вопрос: чьи это данные. Обмен «1С и магазин» на самом деле состоит из нескольких потоков с разным направлением и разным хозяином.
| Данные | Источник истины | Куда движутся | Что случается при расхождении |
|---|---|---|---|
| Каталог: номенклатура, цены, остатки | 1С | 1С → сайт | Правка на сайте будет затёрта следующей выгрузкой или, хуже, останется и разойдётся с учётом |
| Заказы | Сайт в момент создания, затем учётная система | сайт → 1С | Заказ задвоится либо потеряется, если нет признака «уже выгружен» |
| Статусы заказов | 1С: оплата, сборка, отгрузка | 1С → сайт | Клиент видит «в обработке», а товар уже в пути |
Заметьте, что заказ меняет хозяина по ходу жизни: родился на сайте, потом им распоряжается учёт. Это нормальная ситуация, но она требует явного правила: с какого момента правки заказа на сайте перестают считаться авторитетными. Без такого правила интеграция превращается в тихую войну двух систем, где побеждает тот, кто писал последним.
Практический вывод для архитектуры: у каждого потока должен быть свой журнал состояния. «Каталог выгружен», «заказ №123 передан в 1С», «статус заказа №123 применён на сайте» — три разных вида фактов. Если сложить их в одну колонку «синхронизировано: да/нет», вы потеряете именно ту точность, ради которой затевался собственный сервис.
Протокол как последовательность, а не как «отправить файл»
Обмен через CommerceML 2 описан в документации 1С по протоколу обмена с сайтом. По ней файлы CommerceML 2 передаются на сайт запросами POST, целиком или частями. Важно, что это один из вариантов обмена, а не единственный способ связать любую конфигурацию 1С с любым магазином. Если ваша конфигурация или модуль работают иначе, пользуйтесь тем, что поддерживают они; ниже речь о логике именно этого протокола.
Последовательность выглядит так. Названия режимов и параметров, кроме перечисленных в документации, зависят от вашей конфигурации и магазина, поэтому здесь они даны условно, как роли шагов, а не как готовый API.
- Аутентификация. 1С обращается к сайту, тот проверяет учётные данные и возвращает cookie. Именно cookie затем сопровождает все последующие запросы сессии.
- Согласование возможностей. Сайт сообщает, умеет ли он принимать ZIP, и называет file_limit — максимальный размер одной порции файла.
- Передача файла. Файл уходит целиком, если укладывается в лимит, иначе частями — несколькими POST-запросами.
- Запуск обработки. Отдельным обращением сайту предлагают разобрать переданный файл.
- Повторы до завершения. Пока обработка не закончена, сайт отвечает, что запрос нужно повторить.
- Завершение по этому файлу. Сайт сообщает об окончании обработки.
Схематично это можно записать так (псевдозапись роли шагов, не конкретные адреса вашего модуля):
1. вход -> ответ с cookie
2. сведения -> поддержка ZIP, file_limit
3. файл, часть 1 (cookie)
файл, часть 2 (cookie)
...
4. импорт файла (cookie) -> progress
импорт файла (cookie) -> progress
импорт файла (cookie) -> successОбратите внимание, что передача и импорт здесь разнесены по разным пунктам. Это не прихоть формата. Принять байты и превратить их в товары — разные виды работы с разной ценой ошибки.
Cookie: маленькая деталь, из-за которой ломаются большие обмены
Раз сессию держит cookie, то все запросы одного обмена должны нести именно её. Если между шагами сессия потеряна, часть запросов окажется «чужой». Типичные причины в собственной инфраструктуре: балансировщик, который разводит запросы одной сессии на разные экземпляры приложения; прокси, срезающий заголовки; клиент, который не сохраняет cookie между вызовами. Для узла на одном VPS эта проблема проще, чем для кластера: один экземпляр приложения, одно место хранения сессий. Но если вы поставите перед обработчиками несколько воркеров, вопрос «где живёт сессия» придётся решить явно.
Про безопасность канала скажем отдельно. В исторических примерах протокола встречается обычный HTTP, но из этого не следует, что так можно делать сейчас: логин, пароль и cookie без TLS передаются открыто. Для своего узла нужен HTTPS с нормальным сертификатом, и это не пожелание, а условие того, что сессионный cookie не станет чужим ключом.
Файл частями и file_limit
Лимит порции задаёт сайт, а не 1С. Это логично: ограничения зависят от настроек веб-сервера, прокси и памяти обработчика на принимающей стороне. Если ваш узел стоит на VPS, значения лимита вы выбираете сами, и выбор имеет последствия:
- слишком большой лимит упирается в таймауты и лимиты размера тела запроса у веб-сервера или обратного прокси: порция пройдёт на тестовом файле и упадёт на реальном;
- слишком маленький даёт сотни запросов на крупный каталог, каждый со своими накладными расходами и шансом на сетевой сбой;
- при передаче частями нужно отслеживать порядок и целостность: «часть 7 из 12 получена» — это состояние, которое надо где-то хранить.
Поддержка ZIP, о которой сайт сообщает на втором шаге, — вопрос трафика и времени: архив уменьшает объём передачи, но перед разбором его нужно распаковать, то есть потратить CPU и место на диске. Для узла на VPS это значит: свободное место под распакованное содержимое нужно считать с запасом, а не по размеру архива. Картинки товаров и XML сжимаются по-разному, поэтому точное соотношение зависит от вашего каталога, а не от общих цифр.
Два ответа, которые нельзя путать: progress и success
Теперь о самом важном. В пошаговом импорте ответ progress означает «запрос нужно повторить»: обработка продолжается и ещё не закончена. Ответ success сообщает, что обработка именно этого файла завершена. Документация при этом разделяет успешную запись переданного файла и успешный импорт: это разные этапы, и первый не гарантирует второй.
Почему так сделано, понятно из любого опыта работы с большими выгрузками. Каталог на десятки тысяч позиций нельзя разобрать за один HTTP-запрос: упрётесь во время ожидания. Поэтому обработчик берёт порцию, записывает позиции, отвечает «ещё не всё», 1С повторяет вызов, и так до конца. Каждый повтор — не ошибка, а штатный ход.
Из этого вытекают три правила для вашего сервиса.
- Статус привязан к файлу. Не «обмен завершён», а «файл import.xml: передан полностью; импорт идёт; импорт завершён». Выгрузка обычно состоит из нескольких файлов (например, отдельно структура каталога и отдельно предложения с ценами и остатками), и у каждого свой путь.
- Progress — это не сбой. Мониторинг, который тревожится при каждом повторе, будет звонить впустую. Тревожить должно другое: слишком долгое отсутствие продвижения.
- Success — это точка фиксации. Только после неё имеет смысл говорить «этот файл применён» и записывать это в журнал.
Если вы строите собственный обработчик, удобно держать для каждого файла маленький автомат состояний: «получаем» → «получен целиком» → «разбирается» → «разобран» либо «ошибка». Переходы фиксируются в базе состояния, а не в памяти процесса, иначе после перезапуска сервиса вы не поймёте, где остановились.
Файл принят — значит ли это, что товары появились
Вернёмся к сцене из начала. Файл передан целиком. Что именно проверено на этот момент? Лишь то, что сайт получил байты и записал их. Записей в каталоге это не гарантирует: разбор может ещё не начаться, идти, упасть на одной неправильной позиции или застрять.
Поэтому приёмку обмена стоит строить на сопоставлении, а не на ответе протокола. Идея простая: из самого файла вы знаете, что в нём заявлено, а в базе магазина можете посмотреть, что появилось. Разница между этими числами и есть диагностика.
| Показатель | В файле | На сайте | Что это говорит |
|---|---|---|---|
| Товаров в каталоге | 1200 | 1200 | Состав совпал |
| Позиций с ценой | 1200 | 1150 | Файл с предложениями обработан не полностью или не тот файл |
| Остатки по складу | 1200 | 0 обновлено | Импорт предложений не запускался или завершился ошибкой |
Такой отчёт не требует догадок. Он прямо говорит, на каком этапе остановились: структура каталога применена, а цены и остатки нет. А это уже совсем другое сообщение, чем «не синхронизируется».
Для сопоставления нужны стабильные ключи. В обмене участвуют идентификаторы, которые 1С присваивает номенклатуре, и ваш сервис должен хранить соответствие «идентификатор в 1С — запись на сайте». Если такого соответствия нет, вы не отличите «товар обновился» от «создался дубль». Конкретные поля зависят от вашего магазина, поэтому придумывать их мы не будем: сверяйтесь со схемой данных вашей платформы.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Что на самом деле работает на VPS
Собственный узел обмена — это не один скрипт, а набор небольших частей с разными задачами. Вот разумный минимум для такой роли.
- Приёмный HTTP-слой. Обрабатывает вход, выдаёт cookie, принимает части файлов. За ним стоит обратный прокси с TLS. Его нагрузка — короткие запросы и приём тела, поэтому ему важны лимиты размера и таймауты, а не гигагерцы.
- Хранилище файлов. Полученные части, собранные файлы, распакованное содержимое. Здесь важен объём и скорость диска: распаковка и разбор XML упираются в ввод-вывод. На VPS с NVMe это неплохо ложится, но объём всё равно нужно считать под самый крупный реальный каталог плюс запас на распаковку.
- Очередь задач разбора. Принять запрос и разобрать файл — разные нагрузки, поэтому их стоит развести. Очередь позволяет ответить 1С быстро (progress), а работу делать воркером.
- База состояния обмена. Таблицы файлов, их этапов, соответствий идентификаторов, журнала попыток. Это память узла.
- Воркеры разбора. Читают XML, пишут в базу магазина или вызывают его API.
По ресурсам разбор крупного XML обычно требует больше памяти и диска, чем приём запросов, особенно если парсер читает документ целиком. Потоковое чтение экономит память, но усложняет код. Если ваши выгрузки небольшие, подойдёт скромная конфигурация вроде Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe); если каталог крупный, а разбор идёт параллельно с приёмом заказов, логично смотреть на Uab (2 vCPU, 4 ГБ, 80 ГБ) или Heka (4 vCPU, 8 ГБ, 160 ГБ). Это отправная прикидка, а не обещание ёмкости: реальную нагрузку покажет замер на ваших данных.
У VPS MAATRIX доступен root, а значит, вы сами ставите обратный прокси, базу и воркеры и настраиваете лимиты под file_limit вашего магазина. Площадки перечислены в публичном предложении: UK, US, RU и FR; какую выбрать, решайте по задержке до 1С и до магазина, а не по общим соображениям. Параметры тарифов и наличие образов уточняйте на странице аренды VPS и в форме заказа. Если ваша 1С тоже живёт на облачном сервере, об этом отдельная статья: пошаговая настройка VPS под 1С в облаке с нуля.
Повтор этапа: безопасно ли спрашивать дважды
Сеть ненадёжна. Запрос ушёл, ответ не дошёл, 1С повторяет. Теперь вопрос: что произошло на стороне сайта в первый раз? Возможны варианты: запрос не дошёл вовсе; дошёл и выполнился; дошёл и выполняется до сих пор. Со стороны 1С они неразличимы, а последствия разные.
Отсюда разбор по этапам, насколько повтор безопасен. Выводы здесь авторские, и они зависят от того, как вы напишете обработчик.
- Вход. Повтор безвреден: вы просто получите новую сессию.
- Часть файла. Повтор безопасен, если запись идемпотентна по смыслу: часть №7 того же файла перезаписывает часть №7, а не дописывается второй раз. Если обработчик просто дописывает байты в конец, повтор испортит файл. Значит, нужен номер или смещение части.
- Запуск разбора. Повтор безопасен только если вы различаете «ещё идёт» и «ещё не начинался». Иначе второй запуск запустит параллельный разбор того же файла.
- Запись товаров. Безопасна, если запись выполняется как «создать или обновить по идентификатору», а не как «создать». Тогда повтор лишь перезапишет те же значения.
Здесь легко поддаться соблазну: придумать новый идентификатор попытки и считать, что он сам всё решает. Нет, он только позволяет отличать попытки друг от друга. Идемпотентность появляется не от идентификатора, а от логики: что обработчик делает, увидев знакомую попытку или знакомый товар.
Транзакции помогают, но у них есть граница
Разбор файла хочется обернуть в транзакцию: либо весь каталог применился, либо ничего. Для операций внутри базы данных это работает. Как описано в документации PostgreSQL по транзакциям, транзакция объединяет шаги в операцию «всё или ничего»: COMMIT фиксирует изменения, ROLLBACK отменяет изменения незавершённой транзакции. Там же важная оговорка по смыслу: эти гарантии относятся к операциям самой базы, а не к произвольному внешнему HTTP API.
Для интеграции это ключевая граница. Допустим, ваш воркер в одной транзакции пишет в свою базу состояния и вызывает HTTP-метод магазина. Откат транзакции отменит запись в вашей базе, но уже отправленный запрос в магазин назад не вернёт. Единой транзакции «своя база плюс внешний HTTP» не существует, если нет отдельного подтверждённого механизма, и обещать её нельзя.
Что делать практически. Транзакцию применяйте там, где она честно работает: записи в вашей базе (и в базе магазина, если вы пишете в неё напрямую и она это поддерживает) группируйте по порциям, а переходы состояний файла фиксируйте отдельно и после факта. А для внешних вызовов строите схему из двух частей: сначала фиксируете в базе намерение («отправляем заказ №123»), затем вызываете API, затем фиксируете результат. Если процесс упал между вторым и третьим, при перезапуске вы видите «намерение есть, результата нет» и решаете, что делать: проверить на стороне получателя или повторить, если вызов идемпотентен.
Зависшая обработка: как отличить долгую работу от мёртвой
Progress может приходить долго, и это нормально. А может приходить бесконечно, и это уже авария: воркер умер, а состояние осталось «разбирается». Снаружи эти два случая похожи, поэтому нужен признак жизни.
Практичная схема: воркер при обработке порции обновляет в базе состояния отметку времени и счётчик обработанных позиций. Тогда «зависло» определяется не по общему времени, а по отсутствию продвижения: счётчик не растёт, отметка старая. Порог выбирается по вашим данным: самый долгий нормальный разбор плюс запас. Универсального числа нет.
Когда зависание обнаружено, решение зависит от этапа:
- Если разбор идёт по схеме «создать или обновить по идентификатору», его можно безопасно запустить заново с начала: уже применённые позиции будут перезаписаны теми же значениями.
- Если разбор не идемпотентен, сначала нужно выяснить, какая часть применена, и только потом продолжать. Иначе вы рискуете получить дубли.
- Перед повторным запуском нужно убедиться, что прежний воркер действительно остановлен. Два воркера на одном файле — отдельный вид аварии. Для этого служит блокировка файла в базе состояния.
Хорошо, если сообщение об ошибке привязано к файлу и этапу: «import.xml: разбор остановился на позиции 8400 из 12000». Такая запись экономит часы разговоров.
Когда данные меняются прямо во время обмена
Идеальный обмен происходит в тишине: пока выгружается каталог, на сайте никто не оформляет заказы. Реальность другая. Пока файл едет и разбирается, покупатель может купить товар, менеджер в 1С может изменить цену, а сайт — получить новый заказ. Это не баг, а свойство системы из двух независимых частей.
Рассмотрим конкретный случай, условный: в 10:00 1С формирует выгрузку остатков, где у товара осталось 5 штук. К 10:03 файл дошёл и начал разбираться. В 10:02 на сайте купили 2 штуки. Когда файл применится, на сайте снова окажется 5, хотя реально их 3 с учётом нового заказа, который в 1С ещё не доехал. Выгрузка в момент создания была верной, к моменту применения устарела.
Единого рецепта нет, но есть несколько приёмов, каждый со своей ценой:
- Резерв на стороне сайта. Сайт хранит собственный счётчик резерва по заказам, которые ещё не подтверждены учётом, и вычитает его из пришедшего остатка. Сложнее, но точнее.
- Отметка времени выгрузки. Остаток применяется, только если он новее уже применённого значения. Защищает от того, что старый файл затрёт свежие данные, но не решает вопрос с заказом в пути.
- Окна обмена. Обмен в часы низкой активности сокращает расхождения, но не устраняет их и не подходит магазинам с круглосуточными продажами.
- Частые маленькие обмены. Передавать только изменения, а не весь каталог: короче окно устаревания, меньше нагрузка на разбор. Ограничение: нужно, чтобы источник умел отдавать изменения.
То же касается заказов. Заказ, передаваемый в 1С, может быть изменён клиентом или менеджером ещё до того, как 1С его забрала. Поэтому в журнале нужен признак версии заказа или времени последнего изменения: иначе вы не узнаете, что в 1С ушла устаревшая версия. Опять же, конкретные поля определяются вашей платформой, и подбирать их надо по её схеме.
Отдельный вопрос — порядок файлов. Если предложения (цены и остатки) применятся раньше, чем структура каталога, они повиснут на несуществующих товарах. Поэтому внутри узла полезно задать зависимость: файл предложений ждёт успешного завершения импорта каталога. Это ещё одна причина считать статусы по файлам, а не по обмену в целом.
Как выглядит диагностика, когда этапы разделены
Теперь соберём всё вместе и посмотрим на типичную жалобу. Прежняя формулировка: «1С не синхронизируется». Что с ней делать? Ничего: в ней нет информации. Теперь представим, что узел ведёт журнал по этапам. Тот же случай читается так:
| Файл | Вход | Передача | Импорт | Сверка с сайтом |
|---|---|---|---|---|
| Структура каталога | выполнен | все части получены | завершён (success) | состав совпал |
| Цены и остатки | выполнен | все части получены | идёт, нет продвижения 40 минут | не проверялась |
Из таблицы видно главное: последний полностью завершённый этап — импорт структуры каталога. Файл с ценами и остатками передан, но обработка остановилась. Проблема не в сети, не в логине и не в 1С: нужно смотреть воркер и его последнюю обработанную позицию. Это уже задача, которую можно поручить, а не расплывчатое «посмотрите, что не так».
Для такой картины от узла требуется скромное: для каждого файла хранить этапы с временем, счётчик продвижения при разборе и результат сверки. Это дешёвые данные, которые занимают мало места в базе, а ценность у них огромная: они превращают спор между отделами в одно предложение.
Когда вы можете точно сказать «передача файла предложений завершена, импорт остановился на позиции 8400, сверка не проводилась», обмен перестаёт быть чёрным ящиком. Последний завершённый этап назван, следующий известен, а виновный участок цепочки ограничен одним воркером, а не всей интеграцией.