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

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

Сначала подтвердите, где именно пропадает заказ

Фраза «склад не видит заказ» описывает результат, но не место сбоя. Один и тот же заказ может отсутствовать в интерфейсе склада, хотя он уже принят API и ожидает обработки. Или запись может находиться в очереди, но не пройти проверку схемы. Иногда заказ импортирован, однако скрыт фильтром подразделения, статуса, даты или ответственного. Поэтому для одного проблемного номера соберите цепочку подтверждений от источника до конечной карточки.

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

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

Нарисуйте фактический маршрут данных

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

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

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

Проверьте гонку на границе окна синхронизации

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

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

ORDER BY updated_at ASC, id ASC WHERE updated_at > :cursor_time OR (updated_at = :cursor_time AND id > :cursor_id)

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

Не смешивайте created_at и updated_at без правил

Выборка только по created_at подходит лишь для действительно неизменяемых объектов. Заказ после создания получает оплату, адрес, состав, комментарий, статус сборки и отмену. Если склад должен видеть изменения, курсор обычно строят по updated_at или по журналу событий. При этом ручная правка старого заказа не должна менять порядок так, чтобы новые записи выпадали из нестабильной пагинации.

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

Проверьте пагинацию и стабильность сортировки

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

Для непрерывного потока используют keyset pagination: следующая страница начинается после последней пары сортировочных значений предыдущей страницы. Сортировка должна быть полной и однозначной. Одного времени недостаточно, потому что несколько заказов могут иметь одинаковую метку вплоть до микросекунды или база может хранить меньшую точность, чем приложение. Дополнительный уникальный id устраняет неопределенность.

Учитывайте транзакции, реплики и часовые пояса

Синхронизация может читать не основную базу, а реплику. При задержке репликации новый заказ еще не виден, хотя курсор уже рассчитан по времени приложения. После появления строки ее timestamp оказывается раньше сохраненного курсора. Проверьте, откуда выполняется SELECT, измеряется ли replica lag и допускает ли алгоритм повторное чтение перекрывающегося окна. Для критичного экспорта иногда разумнее читать outbox с primary или использовать журнал изменений, а не надеяться на мгновенную репликацию.

Сравните часовые пояса базы, приложения, worker и внешнего API. Внутри системы безопаснее хранить время в UTC, а локальное представление формировать только в интерфейсе. Нельзя добавлять или вычитать три часа вручную в разных местах кода: при миграции, смене настроек или импорте это создает непредсказуемые интервалы. В журнале рядом со временем указывайте offset или используйте однозначный ISO 8601.

Проверьте очередь, подтверждение и повторные попытки

Если заказ выбран правильно, но теряется после отправки в очередь, проверьте момент подтверждения сообщения. Worker не должен подтверждать задачу до успешной записи на приемнике или до сохранения гарантированного состояния для продолжения. При временной сетевой ошибке сообщение возвращается в очередь с ограниченным backoff. После исчерпания попыток оно попадает в dead-letter очередь, а не исчезает.

  • для каждого события сохраняются event_id, order_id, номер попытки и correlation_id;
  • таймаут не считается доказательством отказа: приемник мог сохранить заказ, но ответ потерялся;
  • повторная доставка использует тот же idempotency key;
  • ошибки валидации отделены от временных ошибок сети и лимитов API;
  • dead-letter очередь контролируется, а ее сообщения можно безопасно вернуть в обработку;
  • метрика возраста самого старого необработанного события дополняет обычный размер очереди.

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

Проверьте фильтры статусов и преобразование данных

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

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

Сделайте повторную передачу идемпотентной

Идемпотентность означает, что повтор одного события не создает второй заказ и не выполняет побочные действия повторно. Ключом обычно служит пара source_system и immutable_order_id, а не номер, который пользователь может изменить или который совпадает у разных магазинов. На стороне приемника для этой пары задают уникальное ограничение.

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

Как безопасно восстановить пропущенные заказы

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

  1. определите последний подтвержденный заказ до пропуска и первый подтвержденный после него;
  2. расширьте интервал в обе стороны, чтобы не потерять записи на границе;
  3. получите неизменяемые идентификаторы заказов из исходной системы;
  4. получите внешние идентификаторы уже импортированных заказов со стороны склада;
  5. сравните наборы и отдельно проверьте записи с ошибками или неизвестным состоянием;
  6. устраните причину пропуска и включите идемпотентность до повторной отправки;
  7. повторите один тестовый заказ и убедитесь, что существующий не дублируется;
  8. отправляйте отсутствующие записи небольшими порциями с сохранением результата каждой;
  9. после импорта пересчитайте сверку и проверьте товары, суммы, резервы и статусы;
  10. только затем возобновите штатную синхронизацию с исправленного курсора.

Если невозможно гарантировать идемпотентность старого API, перед каждым повтором выполняйте точный поиск по внешнему идентификатору и фиксируйте решение. Такой режим медленнее, но безопаснее массовой отправки. Ручные заказы, созданные сотрудниками склада в обход интеграции, учитываются отдельно: совпадение клиента и суммы не является надежным признаком того же заказа.

Пошаговый порядок исправления синхронизации

  1. зафиксируйте пример пропущенного заказа и соседние успешные записи;
  2. соберите журналы всех звеньев с единым идентификатором и временем в UTC;
  3. временно исключите параллельные запуски одной и той же задачи;
  4. проверьте SQL-выборку, фильтры, сортировку, пагинацию и источник чтения;
  5. найдите момент, когда сохраняется cursor или watermark, и сравните его с подтверждением приемника;
  6. добавьте устойчивый курсор по времени и id либо последовательность outbox;
  7. внедрите перекрывающееся окно только вместе с дедупликацией;
  8. разделите временные ошибки, ошибки данных и неизвестный результат таймаута;
  9. настройте retry с backoff, dead-letter очередь и наблюдаемые статусы;
  10. проверьте один повтор, затем восстановите ограниченную порцию пропусков;
  11. выполните полную сверку за интервал и только после нее верните расписание;
  12. создайте автоматический контроль расхождений, чтобы следующий пропуск обнаруживался сразу.

Как проверить результат после исправления

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

  • каждый тестовый заказ появляется на складе ровно один раз;
  • последовательность не теряет строки при добавлении данных между страницами;
  • таймаут и повтор не создают дубли и повторные резервы;
  • ошибка одной записи не перескакивает курсором через нее;
  • после перезапуска worker продолжает с последней подтвержденной позиции;
  • задержка реплики или API не закрывает непрочитанный интервал;
  • изменение старого заказа передается как новая версия, а не новый заказ;
  • сверка количества и идентификаторов за час и сутки не показывает расхождений.

Дополнительно проверьте содержимое: состав заказа, количество, единицы измерения, цены, скидки, налоги, способ доставки, склад, резервы и статус оплаты. Интеграция может доставить карточку, но потерять часть строк или неверно применить повторное обновление. Контроль одной цифры «заказ создан» недостаточен.

Типичные ошибки при исправлении

  • перевести курсор на текущее время и окончательно пропустить старые записи;
  • повторно отправить весь день без уникального внешнего идентификатора;
  • считать timeout гарантированным отказом приемника;
  • использовать OFFSET для изменяющейся выборки без снимка данных;
  • сортировать только по времени, которое одинаково у нескольких заказов;
  • сохранять cursor до завершения всей порции или до подтверждения записи;
  • читать с реплики, не учитывая ее задержку;
  • скрывать ошибки преобразования пустым catch и продолжать обработку;
  • запускать несколько экземпляров задания без блокировки или разделения партиций;
  • исправить один статус, но не проверить остальные переходы заказа;
  • дедуплицировать по номеру заказа без пространства имен источника;
  • удалять журналы раньше, чем завершается период возможной сверки.

Как предотвратить повторение пропусков

Надежность обмена строится не только на retry. Нужны наблюдаемые состояния: selected, queued, sent, accepted, applied, rejected и dead-letter. Для каждого заказа должна быть понятна последняя подтвержденная стадия. Метрики показывают количество выбранных, успешно принятых и отклоненных записей, задержку от создания до склада и возраст самого старого необработанного события.

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

  • контракт интеграции и карта статусов хранятся версионно;
  • изменения схемы тестируются на копии обезличенных данных;
  • секреты и персональные данные не попадают в обычные логи;
  • курсор, очередь и dead-letter включены в резервное копирование;
  • для планировщика действует защита от случайного параллельного запуска;
  • после обновления магазина, WMS или API выполняются граничные тесты;
  • оператор может безопасно повторить отдельное событие без доступа к базе и без ручного создания заказа.

Когда нужна помощь с синхронизацией склада

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