Вопрос быстродействия: таблица регистрации плана обмена или регистр сведений?

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

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

Архитектура планов обмена: как это устроено на уровне платформы

Планы обмена — это специализированный механизм платформы 1С, предназначенный для отслеживания изменений данных. Когда для какого-либо объекта (справочника, документа) включена регистрация изменений на узле плана обмена, платформа создает скрытые таблицы регистрации изменений (на уровне СУБД они имеют постфикс _CHNGR или аналогичный в зависимости от типа объекта).

Рассмотрим, почему запись изменений через планы обмена изначально спроектирована очень быстрой:

  1. Отсутствие контроля уникальности на уровне записи: При записи объекта базы данных платформа просто добавляет запись в таблицу регистрации изменений. В отличие от регистров сведений, здесь нет необходимости проверять уникальность комбинации измерений, что экономит значительное количество процессорного времени СУБД и сервера приложений.
  2. Интеграция в транзакцию записи: Регистрация изменений происходит автоматически на уровне платформы параллельно с записью самого объекта. Если включена авторегистрация, разработчику вообще не нужно писать код. Данные попадают в базу в рамках одной транзакции, минимизируя накладные расходы на интерпретацию кода 1С.
  3. Минимальный объем данных: Таблица регистрации изменений содержит только ссылку на измененный объект и ссылку на узел плана обмена, для которого зарегистрировано это изменение. Это делает саму таблицу крайне компактной.

Регистрация изменений через регистры сведений (паттерн Outbox)

Идея использования независимого непериодического регистра сведений для регистрации изменений привлекает разработчиков своей гибкостью. Этот подход часто называют паттерном «Очередь сообщений» или «Outbox». В этом случае в обработчиках событий объектов (например, ПередЗаписью или ПриЗаписи) программно прописывается логика фиксации изменений.

Рассмотрим пример кода, реализующий регистрацию изменений в независимом регистре сведений:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Проверим, нужно ли регистрировать изменения для данного объекта
    Если ЭтотОбъект.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Создаем запись в нашей очереди изменений
    МенеджерЗаписи = РегистрыСведений.ОчередьИзменений.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Объект = ЭтотОбъект.Ссылка;
    МенеджерЗаписи.УзелИсточника = Константы.ТекущийУзел.Получить();
    МенеджерЗаписи.ДатаРегистрации = ТекущаяДатаСеанса();
    
    // Записываем без проверки уникальности в транзакции основного объекта
    МенеджерЗаписи.Записать(Истина);
    
КонецПроцедуры

Почему этот подход может казаться более удобным? Разработчик полностью контролирует процесс регистрации, может наложить произвольные фильтры в коде (например, не регистрировать документы старше года или определенного вида), а также легко может очистить или модифицировать эту очередь обычными запросами. Однако здесь кроются серьезные проблемы с производительностью.

Проблема блокировок и Highload в планах обмена

Главный недостаток стандартных планов обмена проявляется в высоконагруженных многопользовательских системах. Он заключается в природе транзакционных блокировок СУБД при чтении и очистке зарегистрированных изменений.

Проанализируем классическую ситуацию возникновения взаимоблокировок (Deadlocks) при использовании планов обмена:

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

Оптимизация регистрации изменений через управляемые блокировки в регистрах сведений

Если мы выбираем путь использования регистра сведений для обмена, мы обязаны вручную настроить механизм транзакционных блокировок, чтобы избежать проблем, присущих планам обмена. Основное преимущество регистра сведений в данном случае — это возможность использовать управляемые блокировки.

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


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

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

Современная альтернатива: История данных (Data History)

Начиная с версии платформы 1С 8.3.13, разработчикам доступна мощная альтернатива планам обмена и кастомным регистрам — механизм «История данных». Его ключевое преимущество для сценариев интеграции заключается в асинхронности.

При использовании Истории данных процесс записи версии объекта происходит мгновенно на уровне платформы в рамках транзакции записи. Однако вся тяжелая аналитическая логика (выявление измененных реквизитов, подготовка дельты изменений) выносится во внебюджетный фоновый процесс. Платформа предоставляет специальный обработчик ОбработкаПослеЗаписиВерсийИсторииДанных, в котором можно настроить асинхронную отправку данных во внешние системы.

Основные преимущества использования Истории данных для интеграций:

  1. Минимальное влияние на транзакцию записи пользователя (User Experience не страдает).
  2. Возможность выгружать не весь объект целиком, а только изменившиеся реквизиты (передача дельты).
  3. Автоматическое отслеживание удаления объектов «из коробки».

Сравнительный анализ решений

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

Критерий сравнения Планы обмена (Стандарт) Регистр сведений (Ручной) История данных (Платформа)
Скорость записи в БД Очень высокая (нет проверок уникальности на уровне СУБД) Средняя (требуется проверка уникальности индексов) Максимальная (асинхронная обработка изменений)
Блокировки в Highload Высокие (блокировки уровня SERIALIZABLE при чтении изменений) Низкие (при правильном использовании управляемых блокировок) Отсутствуют (за счет разделения транзакций записи и обработки)
Простота разработки Очень простая (встроенный платформенный механизм) Средняя (требуется вручную писать перехватчики и блокировки) Средняя (требуется настройка метаданных и фоновых заданий)
Регистрация удалений Автоматически (через удаление объекта) Сложно (требуется подписка на событие ПередУдалением) Автоматически (фиксируется версия удаления)

Выводы и рекомендации

Подводя итог нашему анализу, можно сформулировать следующие правила выбора оптимального механизма регистрации изменений:

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

Для высоконагруженных Highload-систем стандартные планы обмена могут стать узким горлышком из-за неконтролируемых блокировок СУБД в моменты выгрузки данных. В таких случаях оправдан переход на независимые регистры сведений с четко настроенными управляемыми блокировками и пакетной обработкой, либо внедрение современного асинхронного механизма Истории данных.

← На главную