Как ограничить выгрузку элементов справочника в зависимости от узла плана обмена в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При проектировании распределенных информационных систем на платформе 1С:Предприятие часто возникает задача выборочной миграции данных. Типичный сценарий: в системе функционирует более 20 удаленных узлов (например, торговых точек, складов или судов), и на каждый узел должна выгружаться не вся номенклатурная база, а только та ее часть, которая соответствует определенным признакам узла. Ситуация осложняется, если справочник номенклатуры имеет иерархическую структуру (более 100 000 элементов), а правила отбора привязаны к свойствам родительских групп.

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

Анализ возможных архитектурных решений

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

  1. Фильтрация при отправке данных (в модуле плана обмена в обработчике ПриОтправкеДанныхПодчиненному). Этот вариант кажется самым простым в реализации: мы регистрируем все изменения подряд, а в момент выгрузки проверяем условия и отсекаем ненужное. Однако, согласно официальному стандарту разработки «1С» №701, этот подход является грубой архитектурной ошибкой. Платформа будет тратить колоссальные ресурсы на чтение таблиц регистрации, сериализацию объектов в памяти и последующее их удаление из пакета. Для базы в 100 000+ элементов это приведет к зависанию обменов и эскалации блокировок СУБД.
  2. Использование правил регистрации объектов (ПРО) в «1С:Конвертации данных». Декларативный подход БСП удобен для небольших баз. Но под капотом БСП обрабатывает правила регистрации через интерпретацию XML-макетов, часто выполняя код в обработчиках через функцию Выполнить(). На объемах данных свыше 100 000 элементов интерпретатор ПРО начинает сильно уступать в скорости прямому коду на встроенном языке, написанному в подписках на события.
  3. Адресная регистрация изменений в подписке на событие ПриЗаписи. Это наиболее сбалансированное и каноничное решение. Мы отключаем авторегистрацию для справочника номенклатуры в составе плана обмена. Вся логика проверки условий переносится в подписку на событие. При записи элемента мы запросом определяем список узлов, удовлетворяющих критериям, и регистрируем изменения точечно с помощью метода ПланыОбмена.ЗарегистрироватьИзменения().

Проблема иерархии («первого родителя») и ее решение через денормализацию

По условию задачи, элементы должны выгружаться по признаку первого (самого верхнего) родителя. Если при каждой записи элемента выполнять рекурсивный поиск родителя в транзакции или строить тяжелые запросы с конструкцией В ИЕРАРХИИ, система начнет существенно тормозить при интерактивной работе пользователей.

Для обхода этой проблемы мы применим паттерн денормализации (кэширования) данных. Разберем этот процесс по шагам:

  1. В справочник Номенклатура добавим скрытый реквизит ГоловнаяГруппа (тип ссылки на этот же справочник) или напрямую добавим реквизиты-признаки головной группы.
  2. При записи элемента (или при изменении его родителя) этот реквизит будет автоматически перезаполняться ссылкой на папку самого верхнего уровня.
  3. В подписке на событие регистрации изменений мы избавимся от тяжелых иерархических запросов и будем обращаться к значению реквизита ГоловнаяГруппа «в лоб». Это снизит время выполнения транзакции практически до нуля.

Пошаговая программная реализация оптимального решения

Рассмотрим детальный алгоритм реализации выборочного обмена с ручной регистрацией изменений в подписке на событие.

Шаг 1. Настройка плана обмена

Откроем наш план обмена в конфигураторе. Перейдем на вкладку «Состав». Для справочника Номенклатура установим свойство «Авторегистрация» в значение Запретить. Теперь платформа не будет автоматически регистрировать изменения для всех узлов подряд при записи элементов.

Шаг 2. Создание подписки на событие

Создадим новую подписку на событие со следующими свойствами:

Шаг 3. Реализация обработчика подписки

Напишем эффективный и безопасный код обработчика. Наша задача — определить, какие признаки установлены у головной группы записываемого товара, сопоставить их с признаками узлов плана обмена и зарегистрировать изменения только на подходящих узлах.

Проанализируем программный код реализации этого алгоритма:


Процедура ОпределитьРегистрациюНоменклатуры(Источник, Отказ) Экспорт

    // Исключаем работу обработчика при загрузке данных, чтобы избежать зацикливания обмена
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Исключаем обработку групп справочника, если они регистрируются по другим правилам
    Если Источник.ЭтоГруппа Тогда
        Возврат;
    КонецЕсли;

    СсылкаНаЭлемент = Источник.Ссылка;
    
    // Получаем признаки головной группы элемента. 
    // Предполагаем, что у нас есть реквизит "ГоловнаяГруппа", в котором хранится ссылка на топ-родителя,
    // и у групп/узлов есть реквизиты "Признак1" и "Признак2" (Булево).
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.ГоловнаяГруппа.Признак1 КАК ПризнакЭлемента1,
        |	Номенклатура.ГоловнаяГруппа.Признак2 КАК ПризнакЭлемента2
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаЭлемент);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Не Выборка.Следующий() Тогда
        Возврат;
    КонецЕсли;
    
    Признак1 = Выборка.ПризнакЭлемента1;
    Признак2 = Выборка.ПризнакЭлемента2;
    
    // Если у головной группы не взведен ни один признак, товар никуда не выгружается
    Если Не Признак1 И Не Признак2 Тогда
        Возврат;
    КонецЕсли;
    
    // Теперь найдем все узлы нашего плана обмена, признаки которых пересекаются с признаками номенклатуры
    ЗапросУзлы = Новый Запрос;
    ЗапросУзлы.Текст = 
        "ВЫБРАТЬ
        |	ПланОбмена.Ссылка КАК Узел
        |ИЗ
        |	ПланОбмена.НашСпециальныйПланОбмена КАК ПланОбмена
        |ГДЕ
        |	ПланОбмена.Ссылка <> &ЭтотУзел
        |	И ПланОбмена.ПометкаУдаления = ЛОЖЬ
        |	И ВЫБОР
        |		КОГДА &ПризнакЭлемента1 И ПланОбмена.Признак1 ТОГДА ИСТИНА
        |		КОГДА &ПризнакЭлемента2 И ПланОбмена.Признак2 ТОГДА ИСТИНА
        |		ИНАЧЕ ЛОЖЬ
        |	КОНЕЦ";
        
    ЗапросУзлы.УстановитьПараметр("ЭтотУзел", ПланыОбмена.НашСпециальныйПланОбмена.ЭтотУзел());
    ЗапросУзлы.УстановитьПараметр("ПризнакЭлемента1", Признак1);
    ЗапросУзлы.УстановитьПараметр("ПризнакЭлемента2", Признак2);
    
    ТаблицаУзлов = ЗапросУзлы.Выполнить().Выгрузить();
    
    Если ТаблицаУзлов.Количество() > 0 Тогда
        СписокУзлов = ТаблицаУзлов.ВыгрузитьКолонку("Узел");
        // Регистрируем изменения пачкой для всех подходящих узлов
        ПланыОбмена.ЗарегистрироватьИзменения(СписокУзлов, СсылкаНаЭлемент);
    КонецЕсли;

КонецПроцедуры

Паттерн «Очередь регистрации» для высоконагруженных систем

Рассмотрим ситуацию, когда запись номенклатуры в базе происходит очень интенсивно (например, идет массовая загрузка прайс-листов или регулярная синхронизация со сторонними сервисами). Даже оптимизированный запрос в транзакции ПриЗаписи начнет замедлять работу пользователей. Чтобы избежать этого, применим паттерн асинхронной (отложенной) регистрации.

Выясним детали реализации этого паттерна по шагам:

  1. Создадим вспомогательный регистр сведений ОчередьРегистрацииНоменклатуры с единственным измерением Номенклатура (тип СправочникСсылка.Номенклатура).
  2. В подписке на событие ПриЗаписи откажемся от любых сложных запросов. Мы будем просто записывать измененную ссылку в этот регистр. Операция выполняется мгновенно и не блокирует транзакцию записи товара.
  3. Настроим регламентное задание, которое будет запускаться по расписанию (например, каждые 5 минут) и обрабатывать накопившуюся очередь пакетами.

Посмотрим на пример кода регламентного задания для асинхронной регистрации изменений:


Процедура ОбработатьОчередьРегистрацииНоменклатуры() Экспорт

    // Выбираем первые 1000 элементов из очереди для обработки пакетом
    ЗапросОчередь = Новый Запрос;
    ЗапросОчередь.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ ПЕРВЫЕ 1000
        |	Очередь.Номенклатура КАК Ссылка
        |ИЗ
        |	РегистрСведений.ОчередьРегистрацииНоменклатуры КАК Очередь";
        
    РезультатОчереди = ЗапросОчередь.Выполнить();
    Если РезультатОчереди.Пустой() Тогда
        Возврат;
    КонецЕсли;
    
    ТаблицаЭлементов = РезультатОчереди.Выгрузить();
    МассивНоменклатуры = ТаблицаЭлементов.ВыгрузитьКолонку("Ссылка");
    
    // Запросом сопоставляем всю пачку номенклатуры с подходящими узлами
    ЗапросРегистрация = Новый Запрос;
    ЗапросРегистрация.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Ссылка КАК Товар,
        |	ПланОбмена.Ссылка КАК Узел
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПланОбмена.НашСпециальныйПланОбмена КАК ПланОбмена
        |		ПО (ПланОбмена.Ссылка <> &ЭтотУзел)
        |			И (ПланОбмена.ПометкаУдаления = ЛОЖЬ)
        |			И (ВЫБОР
        |				КОГДА Номенклатура.ГоловнаяГруппа.Признак1 И ПланОбмена.Признак1 ТОГДА ИСТИНА
        |				КОГДА Номенклатура.ГоловнаяГруппа.Признак2 И ПланОбмена.Признак2 ТОГДА ИСТИНА
        |				ИНАЧЕ ЛОЖЬ
        |			КОНЕЦ)
        |ГДЕ
        |	Номенклатура.Ссылка В (&МассивНоменклатуры)";
        
    ЗапросРегистрация.УстановитьПараметр("ЭтотУзел", ПланыОбмена.НашСпециальныйПланОбмена.ЭтотУзел());
    ЗапросРегистрация.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    
    ВыборкаРегистрации = ЗапросРегистрация.Выполнить().Выбрать();
    
    // Группируем результаты для вызова ПланыОбмена.ЗарегистрироватьИзменения() пачками
    СоответствиеУзлов = Новый Соответствие;
    Пока ВыборкаРегистрации.Следующий() Цикл
        МассивТоваровУзла = СоответствиеУзлов.Получить(ВыборкаРегистрации.Узел);
        Если МассивТоваровУзла = Неопределено Тогда
            МассивТоваровУзла = Новый Массив;
            СоответствиеУзлов.Вставить(ВыборкаРегистрации.Узел, МассивТоваровУзла);
        КонецЕсли;
        МассивТоваровУзла.Добавить(ВыборкаРегистрации.Товар);
    КонецЦикла;
    
    // Выполняем регистрацию изменений на узлах
    НачатьТранзакцию();
    Попытка
        Для Каждого КлючИЗначение Из СоответствиеУзлов Цикл
            ПланыОбмена.ЗарегистрироватьИзменения(КлючИЗначение.Ключ, КлючИЗначение.Значение);
        КонецЦикла;
        
        // Очищаем обработанные элементы из регистра очереди
        НаборЗаписей = РегистрыСведений.ОчередьРегистрацииНоменклатуры.СоздатьНаборЗаписей();
        Для Каждого Товар Из МассивНоменклатуры Цикл
            НаборЗаписей.Отбор.Номенклатура.Установить(Товар);
            НаборЗаписей.Записать();
        КонецЦикла;
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписатьЛогСобытия("Ошибка асинхронной регистрации изменений обмена: " + ОписаниеОшибки());
        ВызватьИсключение;
    КонецПопытки;

КонецПроцедуры

Как обрабатывать изменение настроек узлов и добавление новых узлов

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

Если узел обмена №3 раньше имел Признак1 = Истина, а теперь его сменили на Признак2 = Истина, то:

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


Процедура ПеререгистрироватьДанныеПриИзмененииНастроекУзла(УзелСсылка) Экспорт

    // 1. Сначала удаляем всю существующую регистрацию номенклатуры для данного узла
    ПланыОбмена.УдалитьРегистрациюИзменений(УзелСсылка, Метаданные.Справочники.Номенклатура);
    
    // 2. Выбираем всю номенклатуру, подходящую под новые настройки узла, и регистрируем её заново
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Ссылка КАК Товар
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.ЭтоГруппа = ЛОЖЬ
        |	И (ВЫБОР
        |		КОГДА &ПризнакУзла1 И Номенклатура.ГоловнаяГруппа.Признак1 ТОГДА ИСТИНА
        |		КОГДА &ПризнакУзла2 И Номенклатура.ГоловнаяГруппа.Признак2 ТОГДА ИСТИНА
        |		ИНАЧЕ ЛОЖЬ
        |	КОНЕЦ)";
        
    Запрос.УстановитьПараметр("ПризнакУзла1", УзелСсылка.Признак1);
    Запрос.УстановитьПараметр("ПризнакУзла2", УзелСсылка.Признак2);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    МассивРегистрации = Новый Массив;
    Пока Выборка.Следующий() Цикл
        МассивРегистрации.Добавить(Выборка.Товар);
        
        // Регистрируем порциями по 1000 элементов для экономии памяти
        Если МассивРегистрации.Количество() >= 1000 Тогда
            ПланыОбмена.ЗарегистрироватьИзменения(УзелСсылка, МассивРегистрации);
            МассивРегистрации.Очистить();
        КонецЕсли;
    КонецЦикла;
    
    Если МассивРегистрации.Количество() > 0 Тогда
        ПланыОбмена.ЗарегистрироватьИзменения(УзелСсылка, МассивРегистрации);
    КонецЕсли;

КонецПроцедуры

Для первоначального заполнения данных при создании абсолютно нового узла в БСП используется переопределяемый модуль ОбменДаннымиПереопределяемый и процедура РегистрацияИзмененийНачальнойВыгрузкиДанных. Написание логики инициализации в этой процедуре гарантирует, что при первом сеансе связи новый узел получит ровно те данные, которые соответствуют его настройкам.

Резюме

Реализация условного обмена для справочников объемом 100 000+ элементов требует взвешенного подхода к производительности. Использование авторегистрации и последующей фильтрации при выгрузке приведет к падению скорости работы базы данных. Наилучшим решением является адресная регистрация в подписке на событие с предварительной денормализацией (кэшированием) иерархических признаков непосредственно в карточке номенклатуры. Для экстремально нагруженных баз рекомендуется внедрить схему асинхронной очереди регистрации на базе регламентных заданий.

← На главную