При интенсивном обмене данными с государственной системой «Меркурий» (ВетИС.API) разработчики и администраторы 1С часто сталкиваются с ошибкой блокировки транзакций на стороне веб-шлюза. Наиболее распространенной и критичной из них является ошибка с кодом MERC56620.
Текст ошибки обычно выглядит следующим образом: «MERC56620 Операция попыталась получить доступ к записи журнала, однако одновременно с ней работали другие операции. Время ожидания доступа к записи журнала истекло, попробуйте еще раз». В этой статье мы подробно разберем причины возникновения этой проблемы и рассмотрим эффективные архитектурные и программные методы борьбы с ней в среде 1С:Предприятие.
Для того чтобы понять природу этой ошибки, проанализируем внутреннее устройство складского журнала и транзакционной системы ВетИС. Ошибка MERC56620 по своей сути аналогична взаимоблокировке (deadlock) или таймауту ожидания блокировки в СУБД. Она возникает на стороне серверов Россельхознадзора, когда несколько параллельных запросов пытаются одновременно изменить или использовать одну и ту же сущность. Источником проблемы могут выступать два сценария:
stock entry GUID). Система ВетИС блокирует эту запись для проведения первой операции, а все последующие параллельные транзакции выстраиваются в очередь ожидания. Если время ожидания превышает установленный лимит шлюза, возвращается ошибка MERC56620.Особо остро эта проблема проявляется на предприятиях, практикующих «производство под реализацию», когда на каждую расходную накладную (реализацию) в системе оформляется индивидуальная микро-партия производства. Это создает колоссальную избыточную нагрузку на шлюз ВетИС.API.
Если специфика вашего учета требует работы с одними и теми же записями складского журнала или в рамках единого незавершенного производства, главным правилом должно стать исключение параллелизма. Необходимо переписать логику отправки пакетов в Меркурий так, чтобы документы уходили строго последовательно.
Рассмотрим, как реализовать простейший механизм очереди отправки сообщений ВетИС с использованием объектных блокировок в 1С. Вместо мгновенного запуска фонового задания на отправку при проведении документа, мы будем помещать задачу в специальный регистр сведений «ОчередьОбменаМеркурий» и обрабатывать его последовательно.
Процедура ОбработатьОчередьОтправки() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 100
| ОчередьОбменаМеркурий.ДокументСсылка КАК ДокументСсылка,
| ОчередьОбменаМеркурий.ИдентификаторНЗП КАК ИдентификаторНЗП
|ИЗ
| РегистрСведений.ОчередьОбменаМеркурий КАК ОчередьОбменаМеркурий
|ГДЕ
| НЕ ОчередьОбменаМеркурий.ВРаботе
|УПОРЯДОЧИТЬ ПО
| ОчередьОбменаМеркурий.МоментВремени ВОЗР";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
// Используем менеджер блокировок для предотвращения параллельного захвата потоков
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьОбменаМеркурий");
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
НачатьТранзакцию();
Попытка
// Блокируем запись очереди, чтобы другие фоновые процессы её не взяли
ЭлементБлокировки.УстановитьЗначение("ДокументСсылка", ВыборкаДетальныеЗаписи.ДокументСсылка);
Блокировка.Заблокировать();
// Выполняем отправку транзакции в Меркурий (ВетИС.API)
РезультатОтправки = ОтправитьДанныеВМеркурий(ВыборкаДетальныеЗаписи.ДокументСсылка);
Если РезультатОтправки Тогда
ИсключитьИзОчереди(ВыборкаДетальныеЗаписи.ДокументСсылка);
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьЛогОшибки(ВыборкаДетальныеЗаписи.ДокументСсылка, ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Такой подход гарантирует, что даже если пользователи одновременно создадут 100 документов производства, использующих сырье с одной партии, они будут обработаны строго друг за другом, исключая конкуренцию на сервере Меркурия.
Создание отдельного производственного транзита под каждую реализацию — это архитектурная ошибка, которая приводит к лавинообразному увеличению количества запросов к шлюзу ВетИС. Вместо этого рекомендуется перейти к стратегии агрегации.
Рассмотрим подробнее этот процесс:
registerProductionOperation.Важное ограничение: Не стоит объединять слишком много позиций в один документ (более 500-1000 строк), так как это может вызвать другую ошибку — превышение максимального объема XML-пакета или таймаут обработки на стороне веб-сервиса Меркурия. Экспериментально подберите оптимальный размер пакета для вашей информационной базы (обычно в пределах 100-200 строк выпуска в одной транзакции).
Поскольку ошибка MERC56620 носит временный характер (транзакция освобождается, как только конкурирующий процесс завершает свою работу), самым простым и действенным способом борьбы «в моменте» является добавление интеллектуального автоповтора при отправке.
Проанализируем пример кода 1С, который перехватывает ошибку блокировки ВетИС, приостанавливает поток выполнения на несколько секунд и повторяет попытку отправки до 5 раз перед тем, как окончательно зафиксировать сбой.
Функция ОтправитьЗапросСАвтоповтором(ДанныеЗапроса, ПараметрыПодключения)
МаксимальноеКоличествоПопыток = 5;
ИнтервалПаузы = 2; // Пауза в секундах перед повтором
ТекущаяПопытка = 1;
Успешно = Ложь;
ОтветШлюза = Неопределено;
Пока ТекущаяПопытка <= МаксимальноеКоличествоПопыток И НЕ Успешно Цикл
Попытка
ОтветШлюза = ВыполнитьЗапросКШлюзу(ДанныеЗапроса, ПараметрыПодключения);
Успешно = Истина;
Исключение
ИнформацияОбОшибке = ИнформацияОбОшибке();
ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке);
// Проверяем, содержит ли текст ответа код ошибки MERC56620
Если Найти(ТекстОшибки, "MERC56620") > 0 Тогда
ЗаписьЖурналаРегистрации("ВетИС.Интеграция",
УровеньЖурналаРегистрации.Предупреждение, , ,
"Обнаружена блокировка MERC56620. Попытка " + Строка(ТекущаяПопытка) + " из " + Строка(МаксимальноеКоличествоПопыток));
// Делаем паузу перед следующей попыткой
СигналПаузы(ИнтервалПаузы);
ТекущаяПопытка = ТекущаяПопытка + 1;
Иначе
// Если ошибка другая, прерываем цикл автоповторов
ВызватьИсключение ТекстОшибки;
КонецЕсли;
КонецПопытки;
КонецЦикла;
Если НЕ Успешно Тогда
ВызватьИсключение "Не удалось отправить транзакцию в Меркурий из-за постоянных блокировок на сервере ВетИС.";
КонецЕсли;
Возврат ОтветШлюза;
КонецФункции
Процедура СигналПаузы(Секунды)
// Реализация паузы без загрузки процессора
ПодключитьОбработчикОжидания("ПустаяПроцедура", Секунды, Истина);
// Для фоновых заданий можно использовать альтернативный метод:
// Систематизированное ожидание через вызов Windows API или метод глобального контекста Предупреждение с таймаутом (на клиенте)
// Либо через выполнение блокирующего вызова к системным утилитам ping/timeout на сервере:
КомандаСистемы("timeout /t " + Строка(Секунды) + " /nobreak", Ложь);
КонецПроцедуры
Для полного устранения ошибки MERC56620 на высоконагруженных предприятиях рекомендуется применить комплексный подход:
MERC56620 и автоматическим повторением запроса через небольшую паузу.