Работа с планами обмена в 1С — это фундамент любой системы синхронизации данных. Однако даже в «простых и надежных» самописных механизмах программисты сталкиваются с ситуацией, когда часть документов (обычно 1–5%) не попадает в базу-приемник. Причина всегда одна: запись либо не зарегистрировалась в плане обмена, либо была очищена до момента фактической отправки. Разберем подробно, какие подводные камни таят в себе планы обмена и как локализовать «плавающую» ошибку в высоконагруженной системе.
Рассмотрим стандартную ситуацию: авторегистрация отключена, а запись в таблицу изменений выполняется программно в подписке на событие ПриЗаписи. На первый взгляд, логика безупречна, но давайте проанализируем ситуацию глубже. Первым делом нам необходимо выяснить, на каком этапе происходит сбой: в момент записи или в момент очистки.
Для этого применим метод расширенного логирования. Мы не можем постоянно сидеть в отладчике на «проде», поэтому заставим систему саму фиксировать все важные шаги. Нам нужно создать механизм, который будет записывать в журнал регистрации (или в отдельный регистр сведений) факт регистрации изменения и факт его удаления.
Важный нюанс: логировать нужно не только факт вызова процедуры, но и контекст. Рассмотрим пример кода, который стоит внедрить в точки регистрации изменений:
Процедура ЗарегистрироватьИзменениеДокумента(Источник) Экспорт
// Проверяем, не установлена ли блокировка обмена
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Узел = ПланыОбмена.СинхронизацияДанных.НайтиПоКоду("Приемник");
Если НЕ Узел.Пустая() Тогда
ПланыОбмена.ЗарегистрироватьИзменения(Узел, Источник.Ссылка);
// Логируем для отладки
ЗаписьЖурналаРегистрации("ОбменДанными.Регистрация",
УровеньЖурналаРегистрации.Информация,
Источник.Метаданные(),
Источник.Ссылка,
"Зарегистрировано изменение для узла: " + Строка(Узел));
КонецЕсли;
КонецПроцедуры
Если в логах запись о регистрации присутствует, а в базе-приемнике документа нет — значит, проблема в процедуре очистки. Если записи в логе нет — значит, выполнение кода до ЗарегистрироватьИзменения не доходит (например, из-за условий в подписке или исключений в смежных модулях).
Проанализируем логику очистки регистрации, описанную в теме: база-приемник после записи документа вызывает веб-сервис источника, который выполняет УдалитьРегистрациюИзменений. Здесь кроется самый опасный «подводный камень» — незавершенные транзакции.
Представим сценарий:
Результат: в источнике регистрации больше нет, а в приемнике объект так и не появился. Чтобы избежать этого, вызов очистки должен происходить строго вне транзакции записи в приемнике, только после успешного подтверждения фиксации данных.
Выясним причину, почему стандартные методы платформы могут вести себя непредсказуемо. Рассмотрим метод ПланыОбмена.УдалитьРегистрациюИзменений(Узел, Ссылка). Проанализируем типичные ошибки при его вызове:
Неопределено или пустая ссылка, платформа может повести себя по-разному в зависимости от версии. В некоторых случаях это вызывает исключение, которое «тихо» гасится блоком Попытка...Исключение, и остальная часть массива не обрабатывается.ТипЗнч перед удалением — хорошая практика.При работе с большими объемами данных (десятки тысяч документов) проблема может переместиться на уровень СУБД. Таблицы изменений (имеющие префикс _Chng в SQL) подвержены эскалации блокировок. Проанализируем ситуацию:
Когда выполняется массовая регистрация, SQL Server может заблокировать всю таблицу изменений целиком. Если в этот же момент веб-сервис очистки пытается выполнить запрос к этой таблице, он может получить ошибку блокировки. Если в коде 1С не предусмотрен повтор операции при блокировке, регистрация останется или, в худшем случае, запрос вернет пустой результат из-за специфики уровней изоляции (например, если используется Read Committed Snapshot), и логика решит, что удалять нечего.
Чтобы исключить потерю 1–5% документов, рекомендуем выполнить следующие шаги:
ПриЗаписи, убедитесь, что она выполняется в самом конце процедуры, когда основные проверки пройдены. Еще лучше использовать подписку ПриЗаписи с проверкой ДополнительныеСвойства, чтобы избежать повторных срабатываний.НомерСообщения, а при получении квитанции удаляйте регистрацию только по этому номеру (метод УдалитьРегистрациюИзменений(Узел, НомерСообщения)). Это гарантирует, что если документ был изменен в источнике после отправки, но до получения ответа, его новая регистрация не будет затерта.НомерТранзакцииДомена(). Это позволит сопоставить действия в источнике и приемнике и понять, не произошло ли удаление раньше фиксации.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 1 ИЗ Документ.ВашДокумент.Изменения ГДЕ Узел = &Узел И Ссылка = &Ссылка";
// ... установка параметров
Если НЕ Запрос.Выполнить().Пустой() Тогда
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, Ссылка);
КонецЕсли;
Подводя итог, можно сказать, что «плавающие» ошибки в планах обмена чаще всего связаны с нарушением транзакционной целостности между двумя базами и отсутствием детального логирования. Использование штатных механизмов с номерами сообщений вместо ручной очистки по ссылкам — самый надежный способ избежать потери данных.