При обработке и загрузке огромных массивов информации (например, многогигабайтных текстовых файлов выгрузки) классическая последовательная обработка в один поток занимает непозволительно много времени. Естественным решением становится параллельная обработка через фоновые задания платформы 1С:Предприятие. Однако сразу возникают практические вопросы: сколько фоновых заданий можно запускать одновременно, какие аппаратные ресурсы они потребляют и как правильно организовать архитектуру загрузки, чтобы не обрушить рабочий процесс сервера rphost и СУБД. Разберем эту задачу комплексно.
Проанализируем зависимость количества фоновых потоков от аппаратной конфигурации сервера. Создание фонового задания платформой 1С не является созданием легкого системного потока в рамках одной программы — это полноценный отдельный сеанс со своими накладными расходами.
Выясним причину, почему нельзя бездумно создавать произвольное количество фоновых задач (например, 50 или 100), даже если задача кажется независимой.
rphost. Если в кластере 1С настроены параметры «Максимальный объем памяти рабочих процессов» или «Безопасный расход памяти за один вызов», взрывной рост потребления памяти потоками приведет к аварийному завершению и перезапуску рабочих процессов кластера, что прервет выполнение всех параллельных задач.Посмотрим на частую ошибку проектирования. Разработчики нередко передают в фоновое задание начальный и конечный номер строки большого текстового файла, полагая, что поток быстро перейдет к нужному фрагменту:
// Ошибочный подход для огромных файлов
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(ПутьКФайлу);
ПараметрыЗадания.Добавить(100000); // Номер начальной строки
ПараметрыЗадания.Добавить(150000); // Номер конечной строки
ФоновыеЗадания.Выполнить("ОбщийМодульЗагрузки.ЗагрузитьДиапазон", ПараметрыЗадания);
Проанализируем ситуацию: объект платформы ЧтениеТекста не поддерживает прямое позиционирование по номеру строки без последовательного чтения предшествующих данных. Если запустить 20 фоновых заданий, каждый поток будет вынужден открывать один и тот же многогигабайтный файл и последовательно с первой строки считывать символы до своего смещения. Это вызовет колоссальную паразитную нагрузку на дисковую подсистему и сведет на нет весь выигрыш от многопоточности.
Правильное решение: предварительно разбить исходный монолитный файл на несколько отдельных файлов-порций (чанков). Это можно сделать либо одним подготовительным линейным проходом в 1С, либо с помощью быстрых консольных утилит операционной системы. Затем каждому фоновому заданию передается имя его собственного сформированного файла, который считывается линейно с самого начала.
Разберем по шагам, как построить надежный механизм управления параллельными задачами.
ФоновыеЗадания.ОжидатьЗавершения(), после чего запускает следующую порцию.Загрузка = Истина. Это отключает избыточные проверки и тяжелые обработчики событий (ПередЗаписью, ПриЗаписи), существенно снижая нагрузку на процессор и вероятность блокировок.НачатьТранзакцию() и ЗафиксироватьТранзакцию(), чтобы не переполнять журнал транзакций СУБД.Посмотрим на базовый пример управляющего кода, который запускает порционную обработку через пул заданий:
// Массив с именами предварительно нарезанных файлов
МассивФайлов = ПолучитьСписокФайловПорций();
МаксимумПотоков = 4; // Рассчитано исходя из доступных ядер CPU
АктивныеЗадания = Новый Массив;
Для Каждого ИмяФайла Из МассивФайлов Цикл
Пока АктивныеЗадания.Количество() >= МаксимумПотоков Цикл
// Ожидаем завершения хотя бы одного задания из пула
ЗавершенноеЗадание = ФоновыеЗадания.ОжидатьЗавершения(АктивныеЗадания, 5);
Если ЗавершенноеЗадание <> Неопределено Тогда
АктивныеЗадания.Удалить(АктивныеЗадания.Найти(ЗавершенноеЗадание));
КонецЕсли;
КонецЦикла;
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(ИмяФайла);
НовоеЗадание = ФоновыеЗадания.Выполнить("ОбщийМодульЗагрузки.ОбработатьФайлПорции", ПараметрыЗадания);
АктивныеЗадания.Добавить(НовоеЗадание);
КонецЦикла;
// Ожидаем завершения оставшихся заданий
Если АктивныеЗадания.Количество() > 0 Тогда
ФоновыеЗадания.ОжидатьЗавершения(АктивныеЗадания);
КонецЕсли;
Подобный подход исключает пиковые перегрузки оборудования, защищает рабочие процессы кластера от падения по лимиту памяти и обеспечивает максимально эффективную утилизацию вычислительных мощностей сервера 1С.