При разработке высоконагруженных систем на платформе 1С:Предприятие мы часто сталкиваемся с необходимостью распараллелить тяжелые процессы для ускорения работы. Стандартный подход — использование фоновых заданий. Однако, если запуск фоновых заданий происходит внутри транзакции главного сеанса, возникает серьезная архитектурная проблема: как откатить изменения, сделанные фоновыми заданиями, если главная транзакция завершилась с ошибкой?
Давайте проанализируем ситуацию и разберем по шагам, почему возникает эта проблема и какие существуют надежные способы ее решения.
Рассмотрим типичный упрощенный сценарий, описанный разработчиком:
ОтменитьТранзакцию().Что происходит на самом деле? Главный документ и записи регистра буфера из главного сеанса откатываются. Но сотни созданных объектов (номенклатура или ордера) остаются в базе данных! Почему? Выясним причину.
Дело в том, что фоновые задания в платформе 1С всегда запускаются в изолированных сеансах рабочего сервера. Каждый такой сеанс устанавливает собственное соединение с СУБД и имеет собственный, полностью независимый транзакционный контекст. Платформа 1С не поддерживает распределенные транзакции (DTC) между разными сеансами. Как только фоновое задание выполняет метод Записать() для объекта и завершает свою локальную работу, данные физически фиксируются в СУБД. Откат транзакции в главном сеансе на них никак не влияет.
Посмотрим на несколько архитектурных подходов, которые помогут нам решить эту задачу элегантно и надежно.
Поскольку глобальная транзакция невозможна, мы можем применить подход из мира распределенных систем — компенсирующие действия. Если процесс падает в исключение, мы должны явно (вручную) удалить то, что успели создать фоновые задания.
Разберем алгоритм:
Попытка ... Исключение.Пример реализации в коде:
НачатьТранзакцию();
Попытка
// ... запуск фоновых заданий, ожидание их завершения ...
// Попытка записи главного документа
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Выполняем компенсирующие действия (удаление мусора)
УдалитьСозданныеФоновымиЗаданиямиОбъекты(ИДПроцесса);
ВызватьИсключение;
КонецПопытки;
В процедуре УдалитьСозданныеФоновымиЗаданиямиОбъекты можно запустить отдельное асинхронное задание, которое прочитает лог по ИДПроцесса, вызовет метод УдалитьОбъект() для каждого элемента и очистит сам лог, чтобы не задерживать пользователя.
Часто фоновые задания нужны не столько для параллельной записи в БД, сколько для распараллеливания тяжелых вычислений (распределение товаров по ордерам, сложные запросы к остаткам). Изменим подход: пусть фоновые задания вообще ничего не пишут в базу!
Рассмотрим, как это работает:
ПоместитьВоВременноеХранилище().ПолучитьИзВременногоХранилища().ОбменДанными.Загрузка = Истина.При таком подходе проблема отката исчезает полностью: пока главная транзакция не завершится успехом, в базу данных не попадет ни одной новой записи.
Если мы заранее можем определить, сколько объектов нам потребуется создать, мы можем изменить последовательность действий.
Разберем архитектуру:
Документы.ИмяДокумента.ПолучитьСсылку() или генерации нового УИД, мы заранее резервируем ссылки для будущих объектов.УстановитьСсылкуНового(), чтобы объект записался именно с тем GUID, который мы указали в главном документе.Если на этапе 3 главная транзакция отвалится, зарезервированные GUID-ы просто "сгорят", и в базе не останется никакого мусора, так как фоновые задания даже не будут запущены.
В сложных системах с множеством документов (например, пулы из 500 заказов) попытка уместить весь процесс в одну синхронную транзакцию почти всегда приводит к блокировкам и таймаутам. Проанализируем более надежный подход с использованием состояний (State Machine).
Такой подход делает систему отказоустойчивой, прозрачной для пользователя (он видит прогресс выполнения) и исключает длительные блокируемые транзакции.
Мы подробно разобрали, почему стандартный метод ОтменитьТранзакцию() не работает для фоновых заданий. Чтобы избежать появления "мусорных" данных в базе, необходимо менять архитектурный подход. В зависимости от вашей задачи, выберите наиболее подходящий паттерн: разделяйте расчеты и запись, используйте компенсирующие транзакции для явного удаления мусора, или переходите на асинхронную модель работы через статусы документов.