При автоматизации крупных торговых или производственных предприятий разработчики часто сталкиваются с задачей загрузки и проведения массивных документов (например, "Поступление товаров и услуг" или "Реализация товаров и услуг"), содержащих огромное количество строк в табличной части — 100 000 и более. Разберем по шагам, с какими проблемами вы столкнетесь при попытке записать такие объемы данных, и проанализируем различные подходы к решению этой задачи, включая использование многопоточной обработки данных.
Первая мысль, которая приходит в голову для ускорения проведения гигантского документа — распараллелить процесс. Давайте выясним причину, почему "в лоб" записать один набор записей в несколько потоков не получится.
Архитектура платформы 1С устроена так, что регистр накопления жестко подчинен своему регистратору. Правило гласит: один регистратор = один набор записей. Если мы попытаемся разделить 100 000 строк на 8 порций и запустим 8 фоновых потоков для записи движений по одному и тому же документу, потоки будут перезаписывать данные друг друга. В итоге в регистре останутся только те записи, которые сформировал поток, завершивший транзакцию последним.
Исторически в платформе 1С существует ограничение на максимальное количество строк в табличной части документа — 99 999 строк. Если попытаться программно или интерактивно добавить 100-тысячную строку, платформа выдаст ошибку.
Важное обновление: Начиная с версии платформы 1С:Предприятие 8.3.27, это ограничение можно снять. В метаданных табличной части появилось свойство ДлинаНомераСтроки. Увеличив его с 5 до 9 символов, вы сможете хранить почти 1 миллиард строк. Однако, даже если платформа технически позволит это сделать, держать 100 000 строк в документе — плохая практика, так как форма будет открываться и сохраняться неприемлемо долго.
Рассмотрим несколько проверенных способов оптимизации таких объемных загрузок.
Согласно методологическим рекомендациям, для работы с гигантскими массивами данных лучше вообще отказаться от табличных частей внутри документа. Загружайте данные напрямую в специально созданный независимый регистр сведений, а сам документ используйте только в качестве объекта-регистратора (подобно документу "Операция"). При проведении документ будет просто читать данные из регистра сведений и формировать движения.
Если мы не можем перестроить конфигурацию, надежным решением станет разделение данных. Разделим 100 000 строк на 10 независимых документов по 10 000 строк в каждом. Далее используем механизм Отложенного проведения. Запускаем проведение этих 10 документов асинхронно.
В типовых конфигурациях (например, УТ 11) для многопоточного проведения рекомендуется использовать готовый API БСП (Библиотеки стандартных подсистем) — МеханизмыПараллельнойОбработки. Он позволит безопасно сформировать пул фоновых заданий и ограничить количество потребляемых ядер сервера, чтобы не "положить" базу.
Если вы всё же хотите сохранить один документ, но распараллелить его запись, применим хитрость с частичной записью набора. Разберем алгоритм:
Замещать = Ложь.Посмотрим на пример кода, который будет выполняться внутри потока:
// Внутри фонового задания
НаборЗаписей = РегистрыНакопления.ТоварыНаСкладах.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаНашДокумент);
// Формируем движения для текущей порции данных
Для Каждого СтрокаПорции Из МассивДанныхПотока Цикл
Движение = НаборЗаписей.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = ДатаДокумента;
Движение.Номенклатура = СтрокаПорции.Номенклатура;
Движение.Количество = СтрокаПорции.Количество;
КонецЦикла;
// ВАЖНО: Пишем без замещения, чтобы не стереть результаты других потоков
НаборЗаписей.Записать(Ложь);
Осторожно: подводные камни СУБД! Параллельная вставка множества записей с одинаковым регистратором и свойством Замещать = Ложь может привести к эскалации блокировок или взаимоблокировкам (Deadlock) на уровне базы данных (MS SQL, PostgreSQL). Чтобы этого избежать, порции данных для разных потоков должны иметь строго уникальные комбинации измерений.
При проведении гигантских документов классический построчный контроль остатков убьет всю производительность. Проанализируем ситуацию: как проконтролировать наличие 100 000 товаров быстро?
Правильный подход — сделать массовый контроль остатков один раз до начала формирования движений. Свернем все данные документа и сравним их с виртуальной таблицей остатков одним запросом.
Посмотрим на пример такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура,
| СУММА(ТЧ.Количество) КАК ТребуемоеКоличество
|ПОМЕСТИТЬ ВТ_Требования
|ИЗ
| &ТаблицаТоваров КАК ТЧ
|СГРУППИРОВАТЬ ПО
| ТЧ.Номенклатура
|;
|
|ВЫБРАТЬ
| ВТ_Требования.Номенклатура,
| ВТ_Требования.ТребуемоеКоличество - ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Нехватка
|ИЗ
| ВТ_Требования КАК ВТ_Требования
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
| &МоментВремени,
| Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_Требования)
| ) КАК Остатки
|ПО ВТ_Требования.Номенклатура = Остатки.Номенклатура
|ГДЕ
| ВТ_Требования.ТребуемоеКоличество > ЕСТЬNULL(Остатки.КоличествоОстаток, 0)";
Запрос.УстановитьПараметр("ТаблицаТоваров", ВыгруженнаяТаблицаЧасть);
Запрос.УстановитьПараметр("МоментВремени", ДокументСсылка.МоментВремени());
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
// Обрабатываем ошибку нехватки и прерываем проведение
КонецЕсли;
Такой подход займет доли секунды. Если выявляется нехватка, процесс прерывается сразу, не тратя ресурсы сервера на формирование и запись движений. Если же остатков хватает, мы можем смело передавать данные в фоновые потоки или писать порциями без дополнительного оперативного контроля.
Используя комбинированный подход — вынос больших данных в регистры, правильную группировку при контроле остатков и грамотное использование механизмов БСП для отложенного проведения — вы сможете эффективно обрабатывать документы любых объемов без ущерба для стабильности работы системы.