При выполнении массовых операций в 1С, таких как обновление тысяч заказов из внешних XML-файлов, разработчики часто сталкиваются с ситуацией, когда система «замирает», а потребление памяти процессом sqlservr.exe или rphost.exe достигает критических отметок. Рассмотрим подробнее, почему это происходит, и проанализируем конкретный пример кода, который приводит к подобным сбоям.
В представленном сценарии программист пытается обновить около 1200 заказов. Каждый заказ представлен отдельным файлом XML. При обработке небольших пачек (за один день) система работает стабильно, но при попытке обработать данные за месяц происходит отказ. Выясним причину, изучив представленный алгоритм работы. Проблема здесь комплексная и кроется в нескольких архитектурных узких местах.
Посмотрим на фрагмент кода, отвечающий за передачу данных на сервер:
Для каждого ФайлПуть Из МассивФайлов Цикл
ДвоичныеДанныеЗаказХХХ = Новый ДвоичныеДанные(ФайлПуть.ПолноеИмя);
УникальныйИдентификаторХранилища = Новый УникальныйИдентификатор;
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанныеЗаказХХХ, УникальныйИдентификатор);
Результат = ЗагрузитьНаСервере(Адрес);
КонецЦикла;
Проанализируем этот участок. Использование метода ПоместитьВоВременноеХранилище с передачей нового УникальныйИдентификатор внутри цикла — это классическая ошибка, ведущая к прогрессивному росту потребления RAM. В 1С данные, помещенные в хранилище с привязкой к идентификатору формы, живут до тех пор, пока форма не будет закрыта. Поскольку итераций 1200, в памяти сервера приложений (процесс rphost) скапливается 1200 копий бинарных данных файлов.
Как исправить: Необходимо использовать один и тот же адрес для каждой итерации или явно удалять данные. Рекомендуется передавать во второй параметр метода ПоместитьВоВременноеХранилище адрес, полученный в предыдущей итерации, либо не использовать привязку к идентификатору вовсе, если данные нужны только на один вызов.
Рассмотрим функцию поиска заказа в системе, которую реализовал автор:
Для каждого ЗаказСсылка Из ЗаказыКлиентов Цикл
Если ЗаказСсылка.ЗаказКлиента.НомерЗаказаХХХ = СтруктураЗаказа.НомерЗаказаХХХ Тогда
НовыйЗаказКлиента = ЗаказСсылка.ЗаказКлиента.ПолучитьОбъект;
Прервать;
КонецЕсли;
КонецЦикла;
Здесь мы видим перебор коллекции ЗаказыКлиентов (таблица значений или массив на форме) внутри цикла по файлам. Если у нас 1200 файлов и в таблице 1200 ссылок, то в худшем случае система выполнит 1 440 000 сравнений. Это создает колоссальную нагрузку на процессор сервера 1С.
Решение: Перед началом цикла обработки файлов следует выгрузить все необходимые заказы в объект Соответствие (Map). Ключом будет выступать НомерЗаказаХХХ, а значением — Ссылка. Поиск в соответствии выполняется методом хэш-таблицы практически мгновенно, что сократит время выполнения этой части кода до минимума.
Автор упоминает ограничение в 8 Гб. Это критический порог. При проведении документов 1С выполняет запись во множество таблиц (регистры накопления, сведения, журналы). Каждая транзакция требует места в памяти для кэширования индексов и записи лога транзакций.
Когда память SQL доходит до предела (особенно в MS SQL Server Express), сервер начинает активно использовать файл подкачки на диске. Скорость работы падает в десятки раз, что пользователь воспринимает как «зависание». Перезапуск службы 1С, который помог автору, принудительно разрывает соединения и очищает кэш объектов на стороне сервера приложений, но не решает проблему фрагментации и объемов данных на стороне SQL.
В коде используется чтение через ПотокВПамяти. Хотя это современный подход, в данном случае он избыточен и потребляет лишние ресурсы:
НовыйПоток = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(НовыйПоток);
ЗаписьДанных.Записать(ДвоичныеДанныеЗаказХХХ);
ЗаписьДанных.Закрыть;
Разберем, как сделать лучше. Вместо того чтобы гонять байты через память, правильнее использовать метод ЧтениеXML.ОткрытьФайл() непосредственно на сервере, если файлы доступны серверу, либо передавать строку XML. Также крайне важно после завершения работы с объектом НовыйЗаказКлиента убедиться, что ссылка на него не удерживается в памяти. Хотя 1С имеет сборщик мусора, при массовой обработке в одном серверном вызове кэш объектов может раздуваться до огромных размеров.
Проанализировав ситуацию, составим план оптимизации процесса:
Соответствие. Подготовьте его один раз перед началом загрузки.УдалитьИзВременногоХранилища(Адрес) в конце каждой итерации цикла на клиенте.Выясним, как мог бы выглядеть оптимизированный фрагмент поиска документа:
// Предварительная подготовка на сервере
КартаЗаказов = Новый Соответствие;
Для каждого Стр Из ТЧЗаказы Цикл
КартаЗаказов.Вставить(Стр.НомерЗаказаХХХ, Стр.ЗаказСсылка);
КонецЦикла;
// В цикле обработки файлов
СсылкаНаЗаказ = КартаЗаказов.Получить(НомерИзXML);
Если СсылкаНаЗаказ <> Неопределено Тогда
ОбъектЗаказа = СсылкаНаЗаказ.ПолучитьОбъект();
// ... логика обновления ...
КонецЕсли;
Такой подход не только решит проблему с зависанием, но и ускорит работу обработки в несколько раз. Помните, что стабильность 1С при работе с большими данными напрямую зависит от того, насколько бережно вы относитесь к памяти сервера и ресурсам SQL-сервера.