Как записать 1.5 миллиона записей в регистр сведений 1С без зависаний и падения системы?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Работа со сверхбольшими объемами данных в платформе 1С:Предприятие всегда требует от разработчика понимания внутренних механизмов платформы и используемой СУБД. Попытка записать более миллиона записей в независимый регистр сведений «в один запуск» — классический сценарий, приводящий к аварийному завершению работы рабочего процесса сервера 1С (падению по недостатку памяти) или зависанию СУБД. В этой статье мы подробно разберем, почему возникает эта проблема, как правильно реализовать порционную запись, оптимизировать настройки СУБД (особенно PostgreSQL) и использовать новые возможности платформы 1С версий 8.3.25 и 8.3.26.

Почему падает или зависает система при массовой записи?

Прежде чем перейти к написанию кода, давайте проанализируем физический смысл происходящего на сервере при попытке записать 1.5 млн строк за один раз:

  1. Переполнение оперативной памяти (OOM) на сервере приложений 1С. Когда мы выгружаем огромный массив данных в таблицу значений и пытаемся передать его в объект РегистрСведенийНаборЗаписей, платформа сериализует эти данные в ОЗУ. Память расходуется как под саму таблицу, так и под метаданные набора записей. При превышении лимитов оперативной памяти рабочий процесс rphost просто перезапускается операционной системой.
  2. Лавинообразный рост лога транзакций СУБД. Запись всего объема в рамках одной неявной транзакции заставляет СУБД удерживать гигантские объемы данных в оперативной памяти и логах транзакций (WAL в PostgreSQL или ldf-файле в MS SQL) до полной фиксации.
  3. Блокировки таблиц. На время выполнения одной длинной транзакции накладывается исключительная блокировка на таблицу регистра, что парализует работу остальных пользователей и может вызвать ошибку времени ожидания блокировки.
  4. Журнал регистрации. Платформа по умолчанию фиксирует транзакционные изменения. Запись миллионов транзакций в ЖР создает колоссальную нагрузку на дисковую подсистему.

Для решения этой проблемы мы рассмотрим комплексный подход, состоящий из правильного разделения данных на порции, оптимизации СУБД и отключения сопутствующих механизмов платформы.

Решение 1. Реализация порционной (пакетной) записи в 1С

Самый надежный способ избежать падения сервера 1С — это разбиение всего объема данных на порции (батчи) оптимального размера. Оптимальным размером одной порции для большинства систем является диапазон от 10 000 до 50 000 записей. Меньший размер приведет к лишним накладным расходам на открытие/закрытие транзакций, больший — к риску нехватки памяти.

Рассмотрим правильный алгоритм порционной записи. Основная ошибка начинающих разработчиков заключается в использовании метода Загрузить() без понимания его специфики, либо в построчной записи в цикле (что работает крайне медленно из-за постоянных обращений к СУБД). Метод Загрузить() у объекта типа РегистрСведенийНаборЗаписей просто заполняет коллекцию в памяти, но физическая запись происходит только при вызове метода Записать().

Давайте разберем по шагам правильный шаблон кода для порционной записи из исходной таблицы значений ТаблицаВывод:


// Определяем размер порции
РазмерПорции = 20000;
ВсегоСтрок = ТаблицаВывод.Количество();
ТекущаяСтрока = 0;

Пока ТекущаяСтрока < ВсегоСтрок Цикл
    
    // Создаем набор записей для каждой порции
    НаборЗаписей = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
    
    // Ограничиваем транзакцию рамками одной порции
    НачатьТранзакцию();
    Попытка
        
        // Заполняем порцию данных
        Для Счетчик = 1 По РазмерПорции Цикл
            Если ТекущаяСтрока >= ВсегоСтрок Тогда
                Прервать;
            КонецЕсли;
            
            СтрокаИсточника = ТаблицаВывод[ТекущаяСтрока];
            НоваяСтрока = НаборЗаписей.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
            
            ТекущаяСтрока = ТекущаяСтрока + 1;
        КонецДля;
        
        // Записываем порцию. Параметр Ложь указывает, что мы НЕ замещаем весь регистр, 
        // а дозаписываем данные в соответствии с установленными отборами (если они есть).
        НаборЗаписей.Записать(Ложь);
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации("МассоваяЗагрузка",
            УровеньЖурналаРегистрации.Ошибка,,,
            ОписаниеОшибки());
        ВызватьИсключение "Ошибка при записи порции данных: " + ОписаниеОшибки();
    КонецПопытки;
    
    // Даем серверу "передохнуть" и освободить память (при необходимости)
    #Если Сервер Тогда
        // Небольшая пауза или вызов обработчика ожидания для очистки контекста rphost
    #КонецЕсли
    
КонецЦикла;

В этом коде критически важно использовать явное управление транзакциями через НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Если этого не сделать, СУБД будет открывать и закрывать неявную транзакцию на каждую строку или пакет, что увеличит время выполнения в десятки раз из-за постоянных дисковых операций сброса кэша.

Решение 2. Использование новых режимов записи (1С 8.3.25 и 8.3.26)

Если вы работаете на современных версиях платформы 1С:Предприятие 8.3.25 и выше, вам больше не нужно вручную вычитывать данные для сверки уникальности ключей в оперативной памяти сервера приложений. В платформе появились революционные режимы записи независимых регистров сведений через перечисление РежимЗамещения:

Применение этих режимов на уровне платформы транслируется в эффективные SQL-запросы типа MERGE (в MS SQL) или INSERT ... ON CONFLICT (в PostgreSQL), существенно снижая нагрузку на оперативную память.

Решение 3. Почему «вешается» PostgreSQL и как это исправить?

В практическом опыте многие разработчики сталкиваются с ситуацией, когда одна и та же загрузка отлично отрабатывает на MS SQL Server, но намертво вешает PostgreSQL уже на первой сотне тысяч записей. Это связано с архитектурными различиями СУБД.

PostgreSQL использует механизм многоверсионности (MVCC). При модификации (UPDATE) или удалении (DELETE) старая версия строки физически остается в таблице в виде «мертвой строки» (Dead Tuple). При порционной записи больших объемов данных генерация этих мертвых строк происходит лавинообразно. Если СУБД настроена некорректно, стандартный механизм очистки Autovacuum может запуститься прямо во время вашей транзакции, заблокировать таблицы и вызвать жесткое зависание системы.

Для успешной загрузки миллионов строк на PostgreSQL выполните следующие настройки в файле postgresql.conf:

  1. Увеличьте лимиты лога предзаписи (WAL). Установите параметр max_wal_size в значение не менее 2GB – 4GB. Это позволит СУБД реже выполнять принудительные контрольные точки (checkpoints), «замораживающие» дисковый ввод-вывод.
  2. Настройте агрессивность автовакуума. Рекомендуется установить autovacuum_vacuum_scale_factor = 0.05 (или даже 0.01 для сверхнагруженных таблиц), чтобы очистка мусора происходила чаще, но мелкими порциями, не накапливая миллионы мертвых строк.
  3. Отключите временное подтверждение транзакций на диске. Если данные не критичны к потере при внезапном отключении питания сервера именно в момент загрузки, установите параметр synchronous_commit = off. Это ускорит пакетную вставку данных в PostgreSQL примерно на 30-40%.
  4. Выделите достаточный объем памяти для обслуживания индексов СУБД. На время загрузки увеличьте параметр maintenance_work_mem (например, до 1–2 Гб), чтобы СУБД могла эффективно перестраивать индексы по окончании записи порций.

Решение 4. Отключение скрытых убийц производительности в 1С

Существуют два платформенных механизма, которые способны замедлить запись данных в десятки раз и привести к дедлокам на уровне базы данных:

  1. Авторегистрация в планах обмена. Если записываемый регистр сведений входит в состав какого-либо плана обмена (например, для синхронизации данных или РИБ), то при записи каждой порции платформа будет пытаться зарегистрировать изменения для всех узлов. Это вызовет лавинообразную запись в таблицы регистрации изменений (вида <ИмяРегистра>.Изменения).
    Решение: Перед записью набора обязательно отключайте авторегистрацию программно: НаборЗаписей.ОбменДанными.Получатели.Автозаполнение = Ложь; После загрузки всех данных можно зарегистрировать изменения на узлах обмена одним общим пакетом, если это необходимо.
  2. Журнал регистрации. Постоянная запись событий транзакций сильно нагружает диск. Если загрузка выполняется в монопольном режиме, временно переведите уровень детализации журнала регистрации в положение "Не регистрировать" или "Только ошибки".
  3. Активные фоновые задания и сеансы пользователей. Перед выполнением загрузки 1.5 млн строк рекомендуется завершить все активные сеансы пользователей, закрыть конфигуратор и временно заблокировать регламентные и фоновые задания в консоли администрирования кластера 1С.

Итоговый эталонный шаблон кода для массовой записи

Сведем все рекомендации в единый, готовый к использованию шаблон кода, учитывающий отключение авторегистрации изменений и эффективную работу с транзакциями:


Процедура ЗагрузитьДанныеВРегистрПорциями(ТаблицаИсточника) Экспорт
    
    РазмерПорции = 15000;
    ВсегоСтрок = ТаблицаИсточника.Количество();
    ТекущаяСтрока = 0;
    
    // Отключаем транзакции во время подготовки, управляем ими вручную
    Пока ТекущаяСтрока < ВсегоСтрок Цикл
        
        НаборЗаписей = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
        
        НачатьТранзакцию(РежимБлокировкиДанных.Управляемый);
        Попытка
            
            // Наполнение порции
            Для Счетчик = 1 По РазмерПорции Цикл
                Если ТекущаяСтрока >= ВсегоСтрок Тогда
                    Прервать;
                КонецЕсли;
                
                СтрокаТаблицы = ТаблицаИсточника[ТекущаяСтрока];
                НоваяЗапись = НаборЗаписей.Добавить();
                ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаТаблицы);
                
                ТекущаяСтрока = ТекущаяСтрока + 1;
            КонецДля;
            
            // Важно: Отключаем регистрацию изменений для планов обмена на время загрузки
            НаборЗаписей.ОбменДанными.Получатели.Автозаполнение = Ложь;
            НаборЗаписей.ОбменДанными.Загрузка = Истина;
            
            // Записываем набор без замещения существующих данных вне нашего набора
            // Если платформа 8.3.25+, можно использовать: 
            // НаборЗаписей.Записать(РежимЗамещенияРегистраСведений.Слияние);
            НаборЗаписей.Записать(Ложь);
            
            ЗафиксироватьТранзакцию();
            
        Исключение
            ОтменитьТранзакцию();
            ЗаписьЖурналаРегистрации("МассоваяЗагрузка",
                УровеньЖурналаРегистрации.Предупреждение,,,
                "Ошибка на строке " + ТекущаяСтрока + ": " + ОписаниеОшибки());
            ВызватьИсключение; // Передаем ошибку выше для остановки процесса
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

Применение предложенных подходов гарантирует стабильную, быструю и предсказуемую запись миллионов строк в независимые регистры сведений на любой СУБД без риска уронить рабочий процесс сервера 1С.

← На главную