При интеграции через веб-сервисы или при пакетной загрузке данных разработчики часто сталкиваются с ситуацией, когда несколько связанных документов одного типа записываются в базу данных в одну и ту же секунду. В дальнейшем при расчете остатков, формировании движений или получении среза последних в регистрах сведений обнаруживается, что документ, созданный физически позже, в выборке располагается раньше предыдущего. Выясним причину такого поведения платформы 1С и разберем практические способы решения этой проблемы.
Разберем внутренний механизм генерации ссылок платформой. 1С формирует идентификаторы документов на базе стандарта UUID v1, который базируется на метке времени и MAC-адресе сетевой карты. Однако порядок байтов, в котором платформа сохраняет ссылку в таблицах СУБД (поле _IDRRef), отличается от текстового представления GUID:
clock_seq).node).time_hi, time_mid, time_low).В СУБД (например, Microsoft SQL Server или PostgreSQL) сравнение бинарных полей binary(16) выполняется слева направо. Это означает, что база данных сначала сравнивает первые байты, отвечающие за clock_seq. До временной метки сравнение доходит только в том случае, если начальные блоки полностью совпали.
Посмотрим на практический пример из обсуждения. Документ, созданный раньше, получил GUID 2600c52a-12eb-11e7-b318-005056a1008a (бинарно: 0xb318005056a1008a11e712eb2600c52a), а документ, созданный позже — 067bb7a4-19c0-11e7-ae82-005056a1008a (бинарно: 0xae82005056a1008a11e719c0067bb7a4). Так как значение 0xae82 меньше 0xb318, более поздний документ с точки зрения базы данных оказывается строго меньше раннего.
Проанализируем, почему значение clock_seq регулярно изменяется. Значение тактов генерируется рабочим процессом сервера 1С (rphost). Серии непрерывно возрастающих идентификаторов гарантируются только внутри одного сеанса и одного рабочего потока на протяжении короткого периода времени.
Рассмотрим тест генерации ссылок в цикле:
Для инд = 1 По 100 Цикл
Спр = Справочники.Номенклатура.СоздатьЭлемент();
Спр.Записать();
Сообщить(XMLСтрока(Спр.Ссылка));
КонецЦикла;
При выполнении этого кода можно заметить, что через определенные интервалы серия ссылок меняется, и в начале нового блока чисел младшие разряды снова сбрасываются. При вызовах веб-сервисов каждый входящий HTTP- или SOAP-запрос может обрабатываться в отдельном сеансе или даже разными процессами кластера rphost. В таком сценарии надеяться на совпадение физического времени создания и бинарного порядка ссылок категорически нельзя.
Объекты МоментВремени и Граница предназначены не для восстановления хронологии реального мира, а для детерминированности расчетов платформы. Они гарантируют, что если два документа уже записаны в базу данных, то порядок их взаимного расположения на оси времени будет постоянным и не изменится при повторных выборках или пересчете итогов.
Если два документа одного вида имеют одинаковую секунду, виртуальная таблица СрезПоследних подчиненного регистра сведений будет определять «последний» документ исключительно по сравнению ссылок в бинарном представлении СУБД. Никаких встроенных настроек для изменения логики работы СрезПоследних платформа не предоставляет.
Самое простое и надежное решение для документов одной цепочки — исключить их попадание в одну и ту же секунду. При обработке входящего вызова веб-сервиса проверяем дату последнего созданного документа в базе. Если входящий документ создается с тем же временем, увеличиваем его реквизит Дата на 1 секунду вперед относительно предыдущего.
Такой подход полностью устраняет зависимость от внутреннего формата GUID, и объект МоментВремени всегда выстраивает документы в правильном порядке.
Если искусственно изменять дату запрещено бизнес-логикой, необходимо отказаться от использования ссылки как критерия упорядочивания. Реализуем собственный механизм определения актуальности записей:
НомерПакета или НомерОперации), передаваемый из внешней учетной системы.СрезПоследних напишем прямой запрос к реальной таблице регистра сведений.Посмотрим на пример запроса для получения актуальной записи по максимальному номеру операции:
ВЫБРАТЬ
Регистр.Объект КАК Объект,
МАКСИМУМ(Регистр.НомерОперации) КАК МаксНомер
ПОМЕСТИТЬ ВТ_МаксимальныеНомера
ИЗ
РегистрСведений.СтатусыОбъектов КАК Регистр
СГРУППИРОВАТЬ ПО
Регистр.Объект
;
ВЫБРАТЬ
Регистр.Объект,
Регистр.Статус,
Регистр.НомерОперации
ИЗ
РегистрСведений.СтатусыОбъектов КАК Регистр
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксимальныеНомера КАК ВТ
ПО Регистр.Объект = ВТ.Объект
И Регистр.НомерОперации = ВТ.МаксНомер
Если строгая хронология требуется для самого регистра сведений, целесообразно перевести его в режим независимого (не подчиненного регистратору). В этом случае измерением регистра делается явный числовой идентификатор очереди или позиции, а первичный ключ таблицы больше не опирается на поле Период регистратора.
При параллельных вызовах веб-сервисов два запроса могут выполняться одновременно в параллельных потоках, что создает состояние гонки. Чтобы входящие данные выстраивались в правильную цепочку, разделим прием сообщений и их обработку:
Вывод: Ссылка документа и значение GUID в платформе 1С не предназначены для отслеживания хронологии создания объектов. Попытка влиять на нее через метод УстановитьСсылкуНового нестабильна при перезапуске кластера серверов и распределенных базах. Корректным архитектурным решением является разнесение документов по секундам либо ведение собственного числового нумератора последовательности.