Лирическое отступление.
Лет десять назад я открыл для себя Конвертацию Данных 2.0. И до сих пор нежно люблю этот инструмент. Он одинаково хорошо подходит для регулярных обменов данными между конфигурациями 1С и реализации разовых выгрузок. Я даже реализовывал обработку данных в одной и той же конфигурации последовательно выгрузив их и загрузив обратно - просто это было быстрее написать, чем реализовывать обработкой с нуля...
Примерно в это же время фирма 1С выкатила новое решение - конвертацию данных 3.0 и XDTO пакет EnterpriseData. Отличная идея - выгружать данные в некий стандартный формат, чтобы затем можно было считывать из него в разные конфигурации-приёмники.
Я был среди первых слушателей курсов по этой технологии обменов и надеялся, что это предтеча появления полноценной шины данных. Даже спрашивал про это преподавателя курсов, а тот, в свою очередь, задавал этот вопрос разработчикам 1С (была у него такая возможность). К сожалению, таких планов у 1С не было. Шина впоследствии вышла, но типовые обмены EnterpriseData так и остались в парадигме "точка-точка". Впрочем, я отвлекся, вернемся к сути...
Суть проблемы.
Текущая технология обменов, хоть и использует новый формат данных, технологически напоминает старые добрые обмены КД 2. Одно из преимуществ таких обменов, если от базы-приёмника не получена "квитанция" о приёме очередного пакета данных, то источник заново выгружает ранее выгруженные данные и добавляет к ним новые. Таким образом достигается надёжность передачи данных, но этот способ имеет и недостатки - если за рабочий день в источнике создается (изменяется) несколько десятков тысяч документов, то просто не получается подобрать адекватное расписание для обменов. Пока база-приёмник записывает и проводит документы из очередного пакета, источник уже начинает выгрузку следующего (который, напомню, содержит данные предыдущего пакета плюс новые). Таким образом размер пакетов, а значит и время на их обработку, вырастает как снежный ком. Если задать в расписании выгрузки большой интервал, тоже ничего хорошего не происходит. За это время накапливается большое количество изменений и всё равно файл обмена будет занимать сотни мегабайт, а то и несколько гигабайт. Плюс, в 2025 году пользователи уже не хотят ждать данные даже один день.
Очевидным решением будет переход к обменам через шину данных. Такой подход позволяет добиться почти мгновенного появления данных в базах-приёмниках. Но переход на шину процесс не быстрый, а негатив пользователей надо гасить уже сейчас. Поэтому попробуем выжать максимум из существующих типовых обменов, при этом не затрачивая на задачу больших ресурсов (всё равно же шину внедрять).
Решение.
Решено было действовать в двух направлениях: 1. Сократить время выдачи "квитанции" от базы-приёмника. Тут напрашивалось решение - проводить документы асинхронно (ведь обмен, по сути, прошел, зачем задерживать загрузку следующего пакета?) 2. Сократить интервал выгрузки данных, но при этом заставить базу-источник ожидать "квитанцию", чтобы не выгружать повторно лишние данные.
Забегая вперед, скажу, что всё получилось. Уже несколько месяцев у нас работают обмены с расписанием "раз в 5 минут" на выгрузку и "раз в 100 секунд" на загрузку. Файлы обмена при этом редко превышают 10Мб, а чаще находятся в пределах 2-5Мб.
Технические детали.
Сперва я занялся асинхронным проведением и был удивлён тем, что в современных релизах БСП практически всё готово. Документы проводятся на последнем этапе, а сведения о том, какие документы нужно провести собраны в специальную таблицу. Беда лишь в том, что находится эта таблица в памяти. Поэтому решение здесь получилось простым - перехватываем процедуру проведения и выгружаем таблицу документов в заранее созданный справочник (либо регистр сведений, тут кому как удобнее). Завершаем обмен, проведение поручаем специально обученному регламентному заданию.
Итак, первым делом создаем два справочника - "ДополнительныеНастройкиУзловКорреспондента" для хранения настроек, и "ОчередьОтложенногоПроведения" для хранения очереди документов для отложенного проведения.
Справочник "ДополнительныеНастройкиУзловКорреспондента":
Описание реквизитов:
Название колонки |
Тип данных |
УзелКорреспондента |
План обмена ссылка |
ИспользоватьОтложенноеПроведение |
Булево |
РазмерПорцииДокументовДляПроведения |
Число (5,0) |
ДожидатьсяЗагрузкиВыгруженногоПакета |
Булево |
Модуль менеджера справочника "ДополнительныеНастройкиУзловКорреспондента"
Функция ИспользоватьОтложенноеПроведение(УзелКорреспондента) Экспорт
РезультатФункции = Ложь;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ДополнительныеНастройкиУзловКорреспондента.ИспользоватьОтложенноеПроведение КАК ИспользоватьОтложенноеПроведение
|ИЗ
| Справочник.ДополнительныеНастройкиУзловКорреспондента КАК ДополнительныеНастройкиУзловКорреспондента
|ГДЕ
| ДополнительныеНастройкиУзловКорреспондента.ПометкаУдаления = ЛОЖЬ
| И ДополнительныеНастройкиУзловКорреспондента.УзелКорреспондента = &УзелКорреспондента";
Запрос.УстановитьПараметр("УзелКорреспондента", УзелКорреспондента);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
РезультатФункции = ВыборкаДетальныеЗаписи.ИспользоватьОтложенноеПроведение;
КонецЦикла;
Возврат РезультатФункции;
КонецФункции
Функция РазмерПорцииДокументовДляПроведения(УзелКорреспондента) Экспорт
РезультатФункции = 0;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ДополнительныеНастройкиУзловКорреспондента.РазмерПорцииДокументовДляПроведения КАК РазмерПорцииДокументовДляПроведения
|ИЗ
| Справочник.ДополнительныеНастройкиУзловКорреспондента КАК ДополнительныеНастройкиУзловКорреспондента
|ГДЕ
| ДополнительныеНастройкиУзловКорреспондента.ПометкаУдаления = ЛОЖЬ
| И ДополнительныеНастройкиУзловКорреспондента.УзелКорреспондента = &УзелКорреспондента";
Запрос.УстановитьПараметр("УзелКорреспондента", УзелКорреспондента);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
РезультатФункции = ВыборкаДетальныеЗаписи.РазмерПорцииДокументовДляПроведения;
КонецЦикла;
Возврат РезультатФункции;
КонецФункции
Функция ДожидатьсяЗагрузкиВыгруженногоПакета(УзелКорреспондента) Экспорт
РезультатФункции = Ложь;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ДополнительныеНастройкиУзловКорреспондента.ДожидатьсяЗагрузкиВыгруженногоПакета КАК ДожидатьсяЗагрузкиВыгруженногоПакета
|ИЗ
| Справочник.ДополнительныеНастройкиУзловКорреспондента КАК ДополнительныеНастройкиУзловКорреспондента
|ГДЕ
| ДополнительныеНастройкиУзловКорреспондента.ПометкаУдаления = ЛОЖЬ
| И ДополнительныеНастройкиУзловКорреспондента.УзелКорреспондента = &УзелКорреспондента";
Запрос.УстановитьПараметр("УзелКорреспондента", УзелКорреспондента);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
РезультатФункции = ВыборкаДетальныеЗаписи.ДожидатьсяЗагрузкиВыгруженногоПакета;
КонецЦикла;
Возврат РезультатФункции;
КонецФункции
Справочник "ОчередьОтложенногоПроведения" выглядит так:
Описание реквизитов:
Название колонки |
Тип данных |
ДатаВремяДобавления |
Дата и время |
ДокументСсылка |
Документ ссылка |
ДатаДокумента |
Дата и время |
УзелКорреспондента |
План обмена ссылка |
ДополнительныеСвойстваОбъекта |
Строка (0) |
Модуль менеджера справочника "ОчередьОтложенногоПроведения"
Функция ВернутьОчередьНаПроведение(УзелКорреспондента=Неопределено, КолвоЭлементов=0) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ
| ОчередьОтложенногоПроведения.Ссылка КАК Ссылка,
| ОчередьОтложенногоПроведения.ДатаВремяДобавления КАК ДатаВремяДобавления,
| ОчередьОтложенногоПроведения.ДокументСсылка КАК ДокументСсылка,
| ОчередьОтложенногоПроведения.ДатаДокумента КАК ДатаДокумента,
| ОчередьОтложенногоПроведения.УзелКорреспондента КАК УзелКорреспондента
|ИЗ
| Справочник.ОчередьОтложенногоПроведения КАК ОчередьОтложенногоПроведения
|ГДЕ
| ОчередьОтложенногоПроведения.ПометкаУдаления = ЛОЖЬ
| И [ОтборПоУзлу]
|
|УПОРЯДОЧИТЬ ПО
| ДатаДокумента,
| ДокументСсылка,
| ДатаВремяДобавления УБЫВ";
Если КолвоЭлементов = 0 Тогда
Запрос.Текст = СтрЗаменить(Запрос.Текст,"ПЕРВЫЕ","");
Иначе
Запрос.Текст = СтрЗаменить(Запрос.Текст,"ПЕРВЫЕ","ПЕРВЫЕ "+Формат(КолвоЭлементов,"ЧГ="));
КонецЕсли;
Если УзелКорреспондента=Неопределено Тогда
Запрос.Текст = СтрЗаменить(Запрос.Текст,"[ОтборПоУзлу]","ИСТИНА");
Иначе
Запрос.Текст = СтрЗаменить(Запрос.Текст,"[ОтборПоУзлу]","ОчередьОтложенногоПроведения.УзелКорреспондента = &УзелКорреспондента");
Запрос.Параметры.Вставить("УзелКорреспондента", УзелКорреспондента);
КонецЕсли;
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Возврат РезультатЗапроса;
КонецФункции
Процедура ЗаписатьВОчередьНаПроведение(ДокументыДляОтложенногоПроведения, УзелКорреспондента, ДополнительныеСвойстваДляОтложенногоПроведения, РазмерПорции=50) Экспорт
ДатаВремяДобавления = ТекущаяДата();
Если РазмерПорции < 1 Тогда
РазмерПорции = 1;
КонецЕсли;
инд = 0;
НачатьТранзакцию();
Для каждого СтрокаТаблицы из ДокументыДляОтложенногоПроведения цикл
инд = инд + 1;
Об = Справочники.ОчередьОтложенногоПроведения.СоздатьЭлемент();
Об.ДатаВремяДобавления = ДатаВремяДобавления;
Об.ДокументСсылка = СтрокаТаблицы.ДокументСсылка;
Об.ДатаДокумента = СтрокаТаблицы.ДатаДокумента;
Об.УзелКорреспондента = УзелКорреспондента;
ДополнительныеСвойстваОбъекта = Неопределено;
Если ДополнительныеСвойстваДляОтложенногоПроведения <> Неопределено Тогда
ДополнительныеСвойстваОбъекта = ДополнительныеСвойстваДляОтложенногоПроведения.Получить(СтрокаТаблицы.ДокументСсылка);
КонецЕсли;
Об.ДополнительныеСвойстваОбъекта = ЗначениеВСтрокуВнутр(ДополнительныеСвойстваОбъекта);
Об.Записать();
Если инд = РазмерПорции Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
инд = 0;
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
КонецПроцедуры
Теперь, когда справочники готовы, можно заняться модернизацией кода. Изменять код БСП я рекомендую в расширении и обязательно в режиме ИзменениеИКонтроль. Вот такая вставка нам понадобится в общем модуле "ОбменДаннымиСервер":
Этого достаточно, чтобы во время обмена документы не проводились, а записывались в нашу очередь.
Осталось сделать регламентное задание, которое будет вызывать нашу функцию асинхронного проведения. Расписание ставим хоть раз в минуту и не забываем прописать ключ (на тот случай, если предыдущий вызов ещё не закончился).
Код процедуры асинхронного проведения
Процедура оп_ВыполнитьОтложенноеПроведениеДокументовОбмена(УзелКорреспондента) Экспорт
Если УзелКорреспондента <> ПланыОбмена.СинхронизацияДанныхЧерезУниверсальныйФормат.ПустаяСсылка() И
Справочники.ДополнительныеНастройкиУзловКорреспондента.ИспользоватьОтложенноеПроведение(УзелКорреспондента) Тогда
ПорцияДляПроведения = Справочники.ДополнительныеНастройкиУзловКорреспондента.РазмерПорцииДокументовДляПроведения(УзелКорреспондента);
Если ПорцияДляПроведения < 1 Тогда
ПорцияДляПроведения = 1;
КонецЕсли;
ДокументыДляОтложенногоПроведения = Справочники.ОчередьОтложенногоПроведения.ВернутьОчередьНаПроведение(УзелКорреспондента);
Если ДокументыДляОтложенногоПроведения.Количество() = 0 Тогда
Возврат; // нет документов в очереди
КонецЕсли;
ПроверкаДатЗапретаОтключена = ПроверкаДатЗапретаОтключена();
ОтключитьПроверкуДатЗапрета(Истина);
ПроведенныйДокумент = Неопределено;
КолвоПроведенных = 0;
Для Каждого СтрокаТаблицы Из ДокументыДляОтложенногоПроведения Цикл
Если СтрокаТаблицы.ДокументСсылка.Пустая() Тогда
Продолжить;
КонецЕсли;
Если Не ОбщегоНазначения.СсылкаСуществует(СтрокаТаблицы.ДокументСсылка) Тогда
Продолжить;
КонецЕсли;
ЭлементОчереди = СтрокаТаблицы.Ссылка.ПолучитьОбъект();
Если СтрокаТаблицы.ДокументСсылка <> ПроведенныйДокумент Тогда
Если КолвоПроведенных = ПорцияДляПроведения Тогда
Прервать;
КонецЕсли;
ПроведенныйДокумент = СтрокаТаблицы.ДокументСсылка;
ДополнительныеСвойстваОбъекта = Неопределено;
ДополнительныеСвойстваДляОтложенногоПроведения = ЭлементОчереди.ДополнительныеСвойстваОбъекта;
Если ЗначениеЗаполнено(ДополнительныеСвойстваДляОтложенногоПроведения) Тогда
Попытка
ДополнительныеСвойстваОбъекта = ЗначениеИзСтрокиВнутр(ДополнительныеСвойстваДляОтложенногоПроведения);
исключение
ДополнительныеСвойстваОбъекта = Неопределено;
КонецПопытки;
КонецЕсли;
Попытка
ВыполнитьПроведениеДокументаПриЗагрузке(
СтрокаТаблицы.УзелКорреспондента,
СтрокаТаблицы.ДокументСсылка,
Истина,
ДополнительныеСвойстваОбъекта);
Исключение
ЗаписьЖурналаРегистрации("ОтложенноеПроведениеУТ_БП", УровеньЖурналаРегистрации.Ошибка,,СтрокаТаблицы.ДокументСсылка,ОписаниеОшибки());
//ВызватьИсключение;
КонецПопытки;
КолвоПроведенных = КолвоПроведенных + 1;
КонецЕсли;
//удаляем объект из очереди
Попытка
ЭлементОчереди.Удалить();
Исключение
ЗаписьЖурналаРегистрации("ОтложенноеПроведениеУТ_БП", УровеньЖурналаРегистрации.Ошибка,,СтрокаТаблицы.Ссылка,ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
ОтключитьПроверкуДатЗапрета(ПроверкаДатЗапретаОтключена);
КонецЕсли;
КонецПроцедуры
Работа на стороне приёмника закончена. Настал черед базы-источника.
Справочник настроек я взял готовый из приёмника со всеми процедурами в модуле менеджера. Хотя здесь нам понадобится единственный флаг "ДожидатьсяЗагрузкиВыгруженногоПакета".
Здесь меня тоже ожидал сюрприз от создателей БСП, но на этот раз неприятный. Оказалось, что между процедурами загрузки и выгрузки данных не передаются никакие параметры (хотя, на мой взгляд, логично дать разработчикам такую возможность). Пришлось придумать небольшие костыли.
Вновь обращаемся к общему модулю "ОбменДаннымиСервер". Наша задача перехватить две процедуры и сделать в них свои вставки. Сначала перехватываем процедуру "ВыполнитьОбменДаннымиПоСценариюОбменаДанными". Эта процедура срабатывает и при выгрузке и при загрузке. В определенный момент в ней заполняется структура "СтруктураНастроекОбмена", но в случае выгрузки данных эта структура уже будет заполнена. Поэтому нам нужно перехватить её до того, как она будет повторно инициализирована.
Конечно, было бы лучше, если бы СтруктураНастроекОбмена инициализировалась лишь однажды и передавалась бы сквозным методом из процедур загрузки в процедуры выгрузки. Но, увы.
Вторая процедура, которую нам нужно перехватить "ПрочитатьСообщениеСИзменениямиДляУзла". Здесь всё просто, мы узнаем номер пакета и запоминаем его в СтруктураНастроекОбмена.
Помимо общего модуля "ОбменДаннымиСервер" нам придется немного изменить обработку "КонвертацияОбъектовXDTO". Здесь модифицируем лишь одну процедуру в модуле объекта и вполне можем использовать директиву "&После":
На этом доработка завершена. Можно переходить к тестированию. Обратите внимание, что цепочка вызовов процедур при "ручном" выполнении обмена и при срабатывании типового регламентного задания обмена данных - разная. Поэтому для тестирования нужно править расписание. Есть ещё один нюанс, при ручном запуске обмен происходит от имени текущего пользователя, а при срабатывании РЗ от имени пользователя, указанного в настройках. У них могут быть разные даты запрета изменения данных, поэтому ручной режим я даже не тестировал, всегда запускал РЗ, изменяя расписание.
Пара слов о хранении настроек.
Настройки я выделил в отдельный справочник, так в будущем меньше возни при обновлениях. Но вы можете просто добавить реквизиты для узлов нужного плана обмена. В принципе можно вообще обойтись без настроек. Вместо отключения флага "дожидаться загрузки выгруженного пакета" можно изменять номер выгруженного пакета в типовой обработке регистрации изменений для обмена. Ситуации, когда такое нужно крайне редки. В моей практике это было, когда проводилось затяжное обновление релиза бухгалтерии. В это время накопились документы в УТ и я решил не дожидаться загрузки очередного пакета, а выгрузить заново всё, что накопилось. Остальные настройки можно жёстко зашить в код. Отключать асинхронное проведение не доводилось. Однако я привык всегда иметь "план Б" и всем рекомендую так делать.
Бонус - готовые расширения.
Во вложении расширения, которые были созданы для обмена УТ - Бухгалтерия корп. Тестировались на версиях УТ 11.4.14.181 и Бух корп 3.0.151.27. Вариант обмена - файловый, через общую папку. Единственный момент, который нужно поправить в коде расширения - в приёмнике, в процедуре, которая вызывается из регламентного задания нужно указать код вашего узел обмена. После чего установите галку "Использование" у РЗ (в поставке оно отключено, чтобы не было ошибок).
Впрочем в статье выделены все принципиальные моменты и вы можете самостоятельно реализовать обе доработки.