Почему зависает сервер 1С и течет память rphost при формировании большого отчета в веб-клиенте?

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

Разработка и поддержка высоконагруженных отчетов в системе «1С:Предприятие 8.3» часто преподносит неприятные сюрпризы. Типичный сценарий: отчет, выводящий около 1,5–2 миллионов строк, прекрасно отрабатывает в тонком или толстом клиенте. Он формируется за разумное время, потребляет приемлемый объем оперативной памяти на клиенте (3–4 ГБ) и не создает критической нагрузки на сервер приложений. Однако при запуске того же самого отчета через веб-клиент (в браузере под управлением Apache или IIS) сервер буквально «ложится»: рабочий процесс rphost стремительно поглощает всю доступную оперативную память (вплоть до 200–250 ГБ), уводя систему в глубокий своп, а сам отчет либо формируется часами, либо аварийно завершается.

В этой статье мы подробно проанализируем, почему возникает такая разница в поведении клиентов, разберем архитектурные особенности работы веб-клиента 1С с большими объемами данных и рассмотрим пошаговые практические методы оптимизации кода и платформы для устранения этой проблемы.

В чем разница? Особенности рендеринга в тонком и веб-клиенте

Для того чтобы понять корень проблемы, проанализируем, как именно платформа 1С передает и отображает данные в различных типах клиентов:

  1. Тонкий и толстый клиенты: Сервер приложений 1С выполняет запрос к СУБД, производит компоновку данных и сериализует результат в компактный бинарный поток (сжатый формат MXL). Этот поток передается на локальный компьютер пользователя. Вся тяжелая, ресурсоемкая работа по рендерингу интерфейса — расчет ширины колонок, отрисовка ячеек, применение шрифтов, стилей оформления и границ — выполняется на стороне клиента силами процессора и оперативной памяти локального ПК. Сервер приложений сразу после отправки бинарных данных может полностью очистить оперативную память, выделенную под этот отчет.
  2. Веб-клиент (браузер): Веб-браузеры физически не умеют напрямую работать с проприетарным бинарным форматом MXL от 1С. Чтобы отобразить отчет, сервер 1С вынужден самостоятельно преобразовать весь табличный документ в сложную разметку HTML/CSS с поддержкой клиентских скриптов JavaScript. Для объема в 2 миллиона строк генерация полноценного DOM-дерева на сервере требует колоссальных ресурсов. Объем оперативной памяти, необходимый для хранения такой HTML-структуры в памяти сервера, возрастает экспоненциально по сравнению с исходными данными.

Виртуальный скроллинг и удержание сеансовых данных

Ни один современный браузер не способен единовременно отобразить HTML-страницу, содержащую миллионы строк табличного документа — вкладка браузера немедленно упадет из-за нехватки памяти (Out of Memory). Для решения этой проблемы в веб-клиенте 1С реализован механизм виртуального скроллинга (динамической загрузки данных).

Браузер запрашивает у сервера приложений только те строки таблицы, которые в данный момент видны на экране пользователя. При прокрутке (скроллинге) отправляются новые AJAX-запросы на получение следующих порций данных. Чтобы сервер rphost мог мгновенно отдавать нужные строки при обращении браузера, он вынужден постоянно удерживать в оперативной памяти (в кэше сеанса или временном хранилище рабочего процесса) весь сформированный табличный документ и его полную HTML-карту. Эта память жестко блокируется на сервере и не может быть освобождена сборщиком мусора до тех пор, пока пользователь физически не закроет вкладку с отчетом или пока не истечет время таймаута веб-сеанса.

Ошибки платформы и фрагментация памяти

Ситуация усугубляется особенностями конкретных релизов платформы 1С. Например, в линейках 8.3.15 (включая популярный релиз 8.3.15.1534) и 8.3.16 присутствовали зарегистрированные баги, связанные с утечкой памяти в веб-клиенте.

При сериализации гигантских табличных документов в формат XML/JSON для передачи веб-серверу (Apache/IIS) менеджер памяти C++ выделяет миллионы мелких блоков. Из-за специфики встроенного аллокатора памяти 1С в этих версиях, после уничтожения объектов память не возвращалась операционной системе в полном объеме. Возникал эффект сильной фрагментации памяти: физически память свободна, но операционная система считает ее занятой процессом rphost. Кроме того, на ПРОФ-лицензиях в этих релизах были временно урезаны возможности тонкой настройки лимитов расхода памяти на один рабочий процесс, что лишало администраторов возможности защитить сервер от падения.

Анализ кода и выявление скрытых уязвимостей

Посмотрим на код процедуры компоновки результата, который часто используется разработчиками при выводе внешних источников данных:


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

В этом коде кроются две критические проблемы, которые при больших объемах данных кратно увеличивают потребление памяти процессом rphost:

1. Использование механизма расшифровки (ДанныеРасшифровки):
В коде инициализируется и заполняется объект расшифровки. При выводе отчета платформа сопоставляет каждую ячейку таблицы с ее метаданными, чтобы пользователь мог кликнуть на ячейку и открыть детальный отчет, документ или справочник. Для 2 миллионов строк карта расшифровки в оперативной памяти начинает весить колоссальное количество гигабайт. В веб-клиенте эта карта дублируется в памяти сеанса для обеспечения интерактивности.

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

3. Использование объединенных ячеек в макетах оформления:
Если в макете СКД или в кастомном макете оформления присутствуют объединенные ячейки, алгоритм расчета их геометрии и генерации HTML-кода для веб-клиента начинает работать крайне неэффективно. На объемах данных свыше 100 тысяч строк это приводит к зависанию процесса вывода.


Пошаговый план решения проблемы и оптимизации

Рассмотрим детально, как мы можем оптимизировать этот процесс и гарантировать стабильную работу сервера 1С.

Шаг 1. Отказ от ссылочных типов во внешней таблице значений (ТЗ)

Первое правило работы с внешними источниками в СКД для больших объемов данных — максимальный уход от ссылок. В ТЗ должны передаваться только примитивные типы данных (строки, числа, даты, булево).

Проанализируем пример оптимизации наполнения таблицы значений. Вместо передачи ссылок на справочники, мы должны на этапе запроса получить наименования (или коды) объектов и заполнить ТЗ строковыми типами с явным указанием квалификаторов:


// Оптимизированное создание таблицы значений со строгой типизацией
ТЗ = Новый ТаблицаЗначений;

МассивТиповСтрока = Новый Массив;
МассивТиповСтрока.Добавить(Тип("Строка"));
ОписаниеТипаСтрока = Новый ОписаниеТипов(МассивТиповСтрока,, Новый КвалификаторыСтроки(150));

МассивТиповЧисло = Новый Массив;
МассивТиповЧисло.Добавить(Тип("Число"));
ОписаниеТипаЧисло = Новый ОписаниеТипов(МассивТиповЧисло,, Новый КвалификаторыЧисла(15, 3));

// Явно задаем колонки только с примитивными типами
ТЗ.Колонки.Добавить("МагазинНаименование", ОписаниеТипаСтрока);
ТЗ.Колонки.Добавить("НоменклатураНаименование", ОписаниеТипаСтрока);
ТЗ.Колонки.Добавить("Количество", ОписаниеТипаЧисло);

// При заполнении ТЗ из запроса извлекаем только Строки (Представления или Наименования)
// Пример: Выборка.Магазин.Наименование КАК МагазинНаименование

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

Шаг 2. Отключение механизма расшифровки

Если пользователю в веб-клиенте не требуется интерактивная расшифровка (например, переход по клику к карточке номенклатуры), этот механизм необходимо полностью отключить. Мы передаем значение Неопределено вместо переменной ДанныеРасшифровки во все методы компоновщика и процессора вывода:


Процедура ПриКомпоновкеРезультата(Результат, ДанныеРасшифровки, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;
    
    ТЗ = ПолучитьИзУТ; // Оптимизированная ТЗ только с примитивными типами
    Если ТЗ = Неопределено Тогда
        Сообщить("Нет данных!");
        Return;
    КонецЕсли;
    
    ВнешнийНаборДанных = Новый Структура("ТЗ", ТЗ);
    СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    
    // Передаем Неопределено в качестве расшифровки
    МакетКомпоновки = КомпоновщикМакета.Выполнить(
        СхемаКомпоновкиДанных, 
        КомпоновщикНастроек.Настройки, 
        Неопределено
    );
    
    ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешнийНаборДанных, Неопределено);
    
    // Выводим данные без поддержки расшифровки
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных, Истина);
КонецПроцедуры

Этот простой шаг снижает объем оперативной памяти, удерживаемой процессом rphost, в несколько раз.

Шаг 3. Прямой вывод в файл (минуя отрисовку в браузере)

Вывод 2 миллионов строк на экран в браузере лишен практического смысла — человек физически не сможет просмотреть такой объем информации глазами. Единственная цель формирования такого отчета — последующий анализ во внешних системах класса Big Data или банальная выгрузка в MS Excel.

Для веб-клиента идеальным решением будет генерация отчета сразу в файл (например, XLSX или сжатый MXL) непосредственно на сервере, без вывода табличного документа на экран. Рассмотрим, как реализовать этот алгоритм:


// Пример программного формирования отчета в файл на сервере
ТабДок = Новый ТабличныйДокумент;

// Отключаем отображение сетки и авторасчет высоты строк для ускорения генерации
ТабДок.ОтображатьСетку = Ложь;
ТабДок.ОтображатьЗаголовки = Ложь;

// Компонуем результат напрямую в созданный ТабличныйДокумент
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабДок);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);

// Сохраняем во временный файл Excel на сервере
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);

// Помещаем файл во временное хранилище для передачи клиенту
АдресФайлаВХранилище = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ИмяФайла));

// На стороне веб-клиента вызываем команду скачивания файла:
// ПолучитьФайлССервера(АдресФайлаВХранилище, "ОстаткиМагазинов.xlsx");

При таком подходе сервер приложений 1С формирует файл линейно, записывает его на диск и сразу же полностью освобождает всю оперативную память. Веб-серверу Apache не нужно выполнять рендеринг миллионов тегов HTML, нагрузка на сервер падает практически до нуля.

Шаг 4. Использование фоновых заданий для асинхронного выполнения

Формирование тяжелых отчетов в основном потоке веб-клиента часто приводит к разрыву HTTP-соединения веб-сервером Apache/IIS по таймауту. Чтобы избежать этого, перенесем формирование отчета в асинхронное фоновое задание.

Схема работы выглядит следующим образом:

  1. Пользователь нажимает кнопку «Сформировать» в веб-клиенте.
  2. Система запускает фоновое задание на сервере при помощи метода ФоновыеЗадания.Выполнить().
  3. На клиенте запускается обработчик ожидания (таймер), который раз в 3–5 секунд опрашивает статус выполнения задания.
  4. После успешного завершения фонового задания, сформированный файл извлекается из временного хранилища и скачивается пользователем через браузер.

Шаг 5. Обновление и настройка платформы

Если вышеописанные методы оптимизации кода не помогли полностью решить проблему, необходимо обратить внимание на системные настройки:

← На главную