При работе с высоконагруженными системами на платформе 1С 8.3 программисты часто сталкиваются с ситуацией, когда длительные фоновые задания (выполняющиеся часы или даже сутки) внезапно прерываются или начинают свою работу заново. Это критично для тяжелых выгрузок или формирования регламентированной отчетности по большому количеству подразделений (например, отчет П-4 в ЗУП 3.1). Разберем причины этого поведения и способы создания отказоустойчивых алгоритмов.
Первым делом проанализируем настройки консоли администрирования кластера серверов 1С. В свойствах рабочего сервера существуют параметры, управляющие «жизненным циклом» рабочих процессов rphost. Рассмотрим наиболее важные из них:
Когда rphost достигает критической отметки по памяти, он помечается как «выключенный», и платформа создает новый процесс для новых соединений. Старый процесс пытается завершить текущие задания, но если они длятся слишком долго, он принудительно завершается, обрывая все выполняющиеся в нем фоновые задания.
Выясним, почему задание может начаться «с начала». Если вы используете метод ФоновыеЗадания.ОжидатьЗавершения() в клиентском сеансе или в родительском фоновом задании, платформа при сбое рабочего процесса может попытаться восстановить состояние системы. В некоторых случаях, если клиентское соединение «потеряло» серверный контекст из-за падения rphost, повторная попытка обращения к серверу (автоматический реконнект) может привести к тому, что инициализирующий код запустится заново. Если в коде не предусмотрена проверка уже обработанных данных, задания будут запущены с исходными параметрами.
Проанализируем ситуацию с точки зрения кода. Если фоновое задание формирует тяжелый отчет (например, П-4), оно активно работает с временными таблицами и кешем компоновки данных. Чтобы снизить нагрузку на память, выполним следующие шаги:
Для процессов, длящихся несколько суток, полагаться на стабильность одного фонового задания нельзя. Разберем самый надежный метод — использование вспомогательного регистра сведений для хранения статусов обработки. Рассмотрим пример реализации:
Создадим регистр сведений ОчередьОбработкиДанных с измерениями ОбъектОбработки (например, Ссылка на Подразделение) и ресурсами Статус (Перечисление: НеОбработано, ВРаботе, Завершено) и ДатаОбновления.
Алгоритм работы будет выглядеть следующим образом:
// 1. Заполнение очереди
Процедура ЗаполнитьОчередь(МассивПодразделений)
Для Каждого Подразделение Из МассивПодразделений Цикл
МенеджерЗаписи = РегистрыСведений.ОчередьОбработкиДанных.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ОбъектОбработки = Подразделение;
МенеджерЗаписи.Статус = ПредопределенноеЗначение("Перечисление.СтатусыЗаданий.НеОбработано");
МенеджерЗаписи.Записать();
КонецЦикла;
КонецПроцедуры
Затем основная обработка запускает несколько «воркеров» (фоновых заданий), которые берут задачи из этой очереди:
// 2. Код внутри фонового задания (Воркера)
Процедура ВыполнитьЗаданиеИзОчереди()
Пока Истина Цикл
// Используем объектную блокировку или транзакцию для захвата задачи
НачатьТранзакцию();
Запрос = Новый Запрос("ВЫБРАТЬ ПЕРВЫЕ 1 ОбъектОбработки ИЗ РегистрСведений.ОчередьОбработкиДанных
ГДЕ Статус = &Статус СУПОРЯДОЧИТЬ ПО ДатаОбновления ДЛЯ ИЗМЕНЕНИЯ");
Запрос.УстановитьПараметр("Статус", ПредопределенноеЗначение("Перечисление.СтатусыЗаданий.НеОбработано"));
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ТекущийОбъект = Выборка.ОбъектОбработки;
// Помечаем как "В работе"
МенеджерЗаписи = РегистрыСведений.ОчередьОбработкиДанных.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ОбъектОбработки = ТекущийОбъект;
МенеджерЗаписи.Статус = ПредопределенноеЗначение("Перечисление.СтатусыЗаданий.ВРаботе");
МенеджерЗаписи.Записать();
ЗафиксироватьТранзакцию();
// Выполняем полезную работу (например, формирование отчета)
Результат = СформироватьОтчетПоПодразделению(ТекущийОбъект);
// Фиксируем завершение
МенеджерЗаписи.Статус = ПредопределенноеЗначение("Перечисление.СтатусыЗаданий.Завершено");
МенеджерЗаписи.Записать();
Иначе
ОтменитьТранзакцию();
Прервать; // Очередь пуста
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Преимущества такой схемы:
Рассмотрим ситуацию, когда вы передаете список подразделений напрямую в параметры метода ФоновыеЗадания.Выполнить(). Если объектов тысячи, структура параметров может стать слишком тяжелой. Вместо передачи огромного массива ссылок, используйте ПоместитьВоВременноеХранилище(). Передавайте в фоновое задание только АдресХранилища. Это снижает нагрузку на сериализацию данных при вызове серверного метода.
Проанализируем интересный эффект: почему при автоматическом перезапуске задания в него попадают измененные данные (как в сообщении №41)? Это происходит потому, что при «мягком» перезапуске кластер фактически инициирует выполнение задания заново. Если ваш код формирования списка задач (запрос по подразделениям) находится внутри процедуры, вызываемой как фоновое задание, то каждый новый старт будет выполнять этот запрос к текущему состоянию базы данных. Чтобы этого избежать, фиксируйте состав пакета данных (Snapshot) до момента запуска параллельных процессов, например, записывая его в промежуточную таблицу или файл.
Резюмируем: Для стабильной работы десятидневных процессов необходимо отказаться от линейного выполнения в пользу архитектуры с фиксацией состояний. Используйте регистры сведений для очереди задач, следите за лимитами памяти в консоли кластера и не бойтесь оптимизировать типовые запросы 1С, если они становятся узким местом в вашей инфраструктуре.