При разработке высоконагруженных систем на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью обработки огромных массивов данных. Одной из самых сложных задач является передача таблицы значений (ТЗ) объемом в несколько миллионов строк в фоновое задание для параллельной обработки. Попытка передать такой объект «в лоб» через параметры фонового задания обычно приводит к катастрофическому потреблению оперативной памяти (десятки гигабайт) и зависанию рабочего процесса сервера 1С. Разберем, почему это происходит и какие архитектурные решения позволяют обойти это ограничение.
Проанализируем ситуацию: когда мы передаем таблицу значений в ФоновыеЗадания.Выполнить(), платформа инициирует процесс сериализации данных. ТЗ превращается в XML-поток (XDTO). Для таблицы в 12 миллионов строк объем XML-данных в памяти может в несколько раз превышать размер самих данных в базе. В 64-битных системах каждая строка ТЗ со служебной информацией занимает значительный объем RAM. В итоге, еще до начала выполнения кода в фоне, сервер тратит все ресурсы на упаковку объекта, что и приводит к потреблению 30 ГБ и более, как описано в проблеме автора.
Это самый правильный и рекомендуемый подход с точки зрения архитектуры 1С. Вместо того чтобы формировать таблицу в основном сеансе и пытаться её «протолкнуть» в фон, мы должны передать в фоновое задание только параметры для формирования этой таблицы (например, структуру фильтров, период или массив ссылок).
Рассмотрим алгоритм действий:
Таким образом, мы полностью исключаем стадию сериализации гигантского объекта между сеансами.
Если данные для ТЗ собираются сложным путем и не могут быть получены одним запросом внутри фона, или если данные «живут» только в памяти текущего сеанса, используем Регистр Сведений (РС) как временное хранилище. Коллеги в обсуждении справедливо называют это «аналогом глобальной временной таблицы».
Разберем шаги реализации:
ИдентификаторПакета (Тип УникальныйИдентификатор) и НомерСтроки.УникальныйИдентификатор.Важно: Запись в базу данных — операция не мгновенная, но она гораздо стабильнее работы с 30 ГБ оперативной памяти и позволяет избежать падения рабочего процесса rphost.
Если запись в базу данных (РС) кажется избыточной, можно использовать файловую систему сервера. Рассмотрим вариант с бинарной сериализацией, который работает быстрее, чем стандартный ЗначениеВСтрокуВнутр.
// В основном сеансе:
ПутьКФайлу = ПолучитьИмяВременногоФайла("bin");
ЗаписьДанных = Новый ЗаписьДанных(ПутьКФайлу);
ЗаписатьРезультатСериализации(ЗаписьДанных, МояОгромнаяТЗ);
ЗаписьДанных.Закрыть();
ПараметрыФона = Новый Массив;
ПараметрыФона.Добавить(ПутьКФайлу);
ФоновыеЗадания.Выполнить("МойМодуль.ОбработатьФайлВФоне", ПараметрыФона);
В фоновом задании мы считываем файл. При использовании ДвоичныеДанные и сжатия (архивирования), передача информации происходит значительно эффективнее. Платформа не пытается анализировать структуру каждой из 12 млн строк при передаче пути к файлу.
Обработка 12 миллионов строк в одном потоке может занять часы. Чтобы реально ускорить процесс, необходимо разделить таблицу на части и запустить несколько параллельных фоновых заданий. Выясним, как это организовать правильно.
Оптимальное количество потоков обычно составляет от 4 до 8, в зависимости от ядер процессора и нагрузки на дисковую подсистему (IOPS). Разберем пример логики разделения:
// Пример разделения таблицы на части
РазмерПакета = 1000000; // По 1 млн строк на поток
ВсегоСтрок = МояТЗ.Количество();
ТекущаяСтрока = 0;
Пока ТекущаяСтрока < ВсегоСтрок Цикл
// Создаем подтаблицу или передаем границы индексов
ПараметрыЗадания = Новый Структура;
ПараметрыЗадания.Вставить("НачальныйИндекс", ТекущаяСтрока);
ПараметрыЗадания.Вставить("Количество", РазмерПакета);
ФоновыеЗадания.Выполнить("МойМодуль.ОбработатьЧастьДанных", ПараметрыЗадания);
ТекущаяСтрока = ТекущаяСтрока + РазмерПакета;
КонецЦикла;
При таком подходе каждое задание получает лишь инструкции, какую часть данных «взять» из общего хранилища (файла или регистра). Это позволяет избежать перегрузки памяти в каждом отдельном потоке.
Если ваша задача требует, чтобы все фоновые задания завершились перед выполнением следующего шага (например, корректировки регистров), используйте метод ФоновыеЗадания.ОжидатьЗавершения(). Посмотрим на пример кода:
МассивЗаданий = Новый Массив;
// ... запуск заданий и добавление их в массив ...
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий, 3600); // Ожидание 1 час
Сообщить("Все потоки завершены успешно");
Исключение
Сообщить("Одно из заданий завершилось с ошибкой или превышено время ожидания");
КонецПопытки;
Для стабильной работы с таблицами объемом в 12 000 000 записей в 1С необходимо придерживаться следующих правил:
ТаблицаЗначений такого размера напрямую в параметры функции фонового задания.Соблюдение этих рекомендаций позволит вам эффективно использовать многопоточность в 1С без риска обрушить сервер из-за нехватки оперативной памяти.