При необходимости обработать массив документов (например, при переносе данных, закрытии периода или массовой загрузке) разработчики нередко сталкиваются с ситуацией, когда скорость выполнения операции кажется недостаточной, а показатели загрузки серверного оборудования (CPU, RAM, сеть) остаются на уровне 10–15%. Разберем архитектурные причины такого поведения платформы 1С:Предприятие и рассмотрим конкретные технические способы многократного ускорения массовой записи и проведения документов.
Проанализируем природу выполнения серверного кода в платформе «1С:Предприятие». Процесс проведения или записи документов в рамках одного пользовательского или фонового сеанса является строго последовательным и однопоточным. Выясним ключевые факторы, создающие иллюзию низкой нагрузки оборудования:
rphost загружает одно ядро на 100%, суммарный мониторинг операционной системы покажет общую загрузку CPU всего в несколько процентов. Платформа 1С не умеет автоматически распараллеливать линейный код проведения одного документа на несколько ядер.По умолчанию каждый вызов метода Записать() в 1С неявно открывает и фиксирует отдельную транзакцию базы данных. Это приводит к колоссальным накладным расходам на дисковые операции fsync и запись страниц лога. Объединим запись документов в явные пакетные транзакции.
Рассмотрим пример пакетной обработки порциями:
РазмерПорции = 100;
Счетчик = 0;
НачатьТранзакцию();
Для Каждого СтрокаТаблицы Из ТаблицаДокументов Цикл
ДокументОбъект = СтрокаТаблицы.Ссылка.ПолучитьОбъект();
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Счетчик = Счетчик + 1;
Если Счетчик % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если Счетчик % РазмерПорции <> 0 Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Важное предостережение: Не следует помещать всю выборку (например, 100 000 документов) в одну гигантскую транзакцию. Слишком длинная транзакция приводит к лавинообразному разрастанию журнала транзакций СУБД, захвату эскалированных блокировок и исчерпанию оперативной памяти. Оптимальный размер порции обычно составляет от 50 до 200 документов.
При проведении документов платформа 1С автоматически обновляет агрегаты итогов регистров накопления и бухгалтерии. Если проводится большой массив исторических документов, СУБД выполняет постоянный пересчет последующих итоговых таблиц, что многократно снижает общую скорость.
Применим программное отключение пересчета итогов на время пакетной операции:
// Отключаем использование итогов перед массовым проведением
РегистрыНакопления.ТоварыНаСкладах.УстановитьИспользованиеИтогов(Ложь);
РегистрыНакопления.ВзаиморасчетыСКонтрагентами.УстановитьИспользованиеИтогов(Ложь);
Попытка
// Выполняем массовое проведение документов
ВыполнитьМассовоеПроведение();
// Включаем итоги обратно и пересчитываем их затронутый период
РегистрыНакопления.ТоварыНаСкладах.УстановитьИспользованиеИтогов(Истина);
РегистрыНакопления.ВзаиморасчетыСКонтрагентами.УстановитьИспользованиеИтогов(Истина);
РегистрыНакопления.ТоварыНаСкладах.ПересчитатьИтоги();
РегистрыНакопления.ВзаиморасчетыСКонтрагентами.ПересчитатьИтоги();
Исключение
РегистрыНакопления.ТоварыНаСкладах.УстановитьИспользованиеИтогов(Истина);
РегистрыНакопления.ВзаиморасчетыСКонтрагентами.УстановитьИспользованиеИтогов(Истина);
ВызватьИсключение;
КонецПопытки;
Также, если минимальная дата проводимых документов известна, перед проведением можно временно сдвинуть минимальную границу рассчитанных итогов вперед с помощью метода УстановитьМинимальныйПериодРассчитанныхИтогов().
Единственный способ полностью загрузить процессорные мощности многоядерного сервера — распределить поток документов на независимые неблокирующие очереди и запустить их обработку параллельно через фоновые задания.
Разберем алгоритм распараллеливания по шагам:
ФоновыеЗадания.Выполнить(). Количество потоков обычно выбирается равным количеству свободных физических ядер сервера (от 4 до 16).Разрешить разделение итогов, что позволит разным потокам параллельно записывать движения без монопольной блокировки таблиц агрегатов.
МассивЗаданий = Новый Массив;
Для НомерПотока = 1 По КоличествоПотоков Цикл
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(СтруктураПараметровПотока);
Задание = ФоновыеЗадания.Выполнить("МодульПроведения.ПараллельноеПроведениеПотока",
ПараметрыЗадания,
,
"Проведение_Поток_" + Строка(НомерПотока));
МассивЗаданий.Добавить(Задание);
КонецЦикла;
// Ожидаем завершения всех параллельных потоков
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
Если задача заключается в первичной загрузке или физической записи данных, когда прикладные проверки модуля объекта не требуются, применим флаг загрузки данных:
ДокументОбъект = Ссылка.ПолучитьОбъект();
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Установка ОбменДанными.Загрузка = Истина полностью отключает обработчики событий ПередЗаписью, ПриЗаписи, проверку заполнения обязательных реквизитов и платформенную регистрацию изменений для планов обмена.
Дополнительно рекомендуется временно отключить следующие подсистемы на время пакетной операции:
Проанализируем параметры конфигурации СУБД для максимального быстродействия транзакционного контура 1С:
Shared Memory (для MS SQL Server) или локальные сокеты (для PostgreSQL) устраняет сетевой оверхед и в разы снижает задержки между запросами.DELAYED_DURABILITY = FORCED позволяет подтверждать фиксацию транзакций в оперативной памяти до физического сброса на диск, что многократно увеличивает темп коротких записей. Для PostgreSQL аналогичный эффект дает параметр synchronous_commit = off.MAXDOP = 1 на уровне СУБД. Попытки SQL-сервера распараллелить мелкие запросы 1С на несколько ядер приводят к накладным расходам на синхронизацию потоков и падению производительности.