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