Как передать большой отчет через COM-соединение и избежать ошибки Недостаточно памяти в 1С

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

Разберем распространенную проблему архитектуры распределенных вычислений в 1С: когда тяжелый аналитический отчет формируется во внешней «базе для отчетов» через COM-соединение, а результат требуется отобразить в рабочей сессии пользователя. При попытке сериализовать результирующий ТабличныйДокумент в XML или JSON и десериализовать его в базе-приемнике платформа 1С аварийно завершает работу с системной ошибкой «Недостаточно памяти».

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

Почему возникает ошибка Недостаточно памяти

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

  1. Коэффициент раздувания данных (Memory Footprint): ТабличныйДокумент — это не плоская таблица, а сложный объект со множеством свойств оформления. Каждая отдельная ячейка хранит стили шрифтов, границы, цвета заливки, форматирование, типы данных и параметры расшифровки. При чтении сериализованного файла размером 1–2 ГБ коэффициент расширения данных в оперативной памяти составляет от 4 до 10 раз. Соответственно, для развертывания такого документа приложению требуется единовременный непрерывный блок оперативной памяти объемом от 6 до 15 ГБ и более.
  2. Ограничения 32-разрядных процессов: Если клиентское приложение 1С или соединение COMConnector выполняется в 32-битном адресном пространстве, максимальный объем виртуальной памяти для процесса жестко ограничен 2 ГБ (независимо от разрядности Windows и физического объема ОЗУ). Из-за фрагментации памяти выделить крупный непрерывный сегмент невозможно уже при потреблении 1,2–1,5 ГБ, что мгновенно провоцирует сбой.
  3. Специфика V83.COMConnector: По умолчанию COM-соединитель запускается внутри процесса текущего приложения (in-process). В итоге внешняя база отчетов и основная сессия делят общий пул памяти одного процесса, ускоряя его исчерпание.
  4. Ограничения интерфейса: Элемент управления формы «Поле табличного документа» не рассчитан на отрисовку сотен тысяч строк и миллионов ячеек. Даже если данные загрузятся, графическая подсистема операционной системы (дескрипторы GDI) приведет к зависанию интерфейса при попытке прокрутки.

Способ 1. Потоковая передача элементов результата через СКД

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

Посмотрим на практическую реализацию этого механизма:


&НаСервереБезКонтекста
Процедура СформироватьОтчетЧерезСКД(РезультатТабДок)
    СтрокаСоединения = "Srvr=""ReportServer"";Ref=""ReportBase"";";
    Соединитель = Новый COMObject("V83.COMConnector");
    Соединение = Соединитель.Connect(СтрокаСоединения);
    
    COMОтчет = Соединение.Отчеты.АнализПродаж.Создать();
    COMКомпоновщикМакета = Соединение.NewObject("КомпоновщикМакетаКомпоновкиДанных");
    COMМакетКомпоновки = COMКомпоновщикМакета.Выполнить(COMОтчет.СхемаКомпоновкиДанных, COMОтчет.СхемаКомпоновкиДанных.НастройкиПоУмолчанию);
    
    COMПроцессорКомпоновки = Соединение.NewObject("ПроцессорКомпоновкиДанных");
    COMПроцессорКомпоновки.Инициализировать(COMМакетКомпоновки);
    
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(РезультатТабДок);
    ПроцессорВывода.НачатьВывод();
    
    Пока Истина Цикл
        COMЭлементРезультата = COMПроцессорКомпоновки.Следующий();
        Если COMЭлементРезультата = Неопределено Тогда
            Прервать;
        КонецЕсли;
        
        // Сериализуем единичный элемент в строку XML
        COMЗаписьXML = Соединение.NewObject("ЗаписьXML");
        COMЗаписьXML.УстановитьСтроку();
        Соединение.СериализаторXDTO.ЗаписатьXML(COMЗаписьXML, COMЭлементРезультата);
        СтрокаXML = COMЗаписьXML.Закрыть();
        
        // Десериализуем в текущей базе и сразу выводим
        ЧтениеXML = Новый ЧтениеXML;
        ЧтениеXML.УстановитьСтроку(СтрокаXML);
        ЭлементРезультата = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
        ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
    КонецЦикла;
    
    ПроцессорВывода.ЗакончитьВывод();
    Соединение = Неопределено;
КонецПроцедуры

В данном подходе в памяти никогда не находится полный XML-снимок всего документа. Обработка идет поточно, что кардинально снижает нагрузку на оперативную память.

Способ 2. Построчная передача областей табличного документа

Если отчет построен на базе устаревшего ПостроительОтчета или алгоритма прямого заполнения макета, разбить процесс на элементы СКД невозможно. В этом случае применим порционную (построчную) передачу областей табличного документа.

Рассмотрим, как передавать сформированный документ строками:


// На стороне базы-приемника через COM-соединение
COMТабДок = Соединение.СформироватьОтчетПостроителем();
КоличествоСтрок = COMТабДок.ВысотаТаблицы;
Ширина = COMТабДок.ШиринаТаблицы;

ТабличныйДокументРезультат = Новый ТабличныйДокумент;
ЧтениеXML = Новый ЧтениеXML;

Для НомерСтроки = 1 По КоличествоСтрок Цикл
    // Выделяем одну строку как отдельную область
    ОбластьCOM = COMТабДок.ПолучитьОбласть(НомерСтроки, 1, НомерСтроки, Ширина);
    
    ЗаписьXML = Соединение.NewObject("ЗаписьXML");
    ЗаписьXML.УстановитьСтроку();
    Соединение.СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ОбластьCOM);
    СтрокаXML = ЗаписьXML.Закрыть();
    
    ЧтениеXML.УстановитьСтроку(СтрокаXML);
    ОбластьСтроки = СериализаторXDTO.ПрочитатьXML(ЧтениеXML, Тип("ТабличныйДокумент"));
    
    ТабличныйДокументРезультат.Вывести(ОбластьСтроки);
КонецЦикла;

Обратите внимание: при цикле Для НомерСтроки = 1 По КоличествоСтрок не следует делать ручное увеличение шага внутри тела итерации, иначе строки будут обрабатываться через одну.

Способ 3. Переход на 64-битный клиент и серверное исполнение

Если задача требует сохранения единой структуры документа без ее дробления, проанализируем настройки окружения:

  1. Использование x64-клиента: В версиях платформы 1С:Предприятие 8.3 доступен 64-разрядный клиент. Он снимает барьер 2 ГБ виртуального адресного пространства на процесс.
  2. Исполнение кода на 64-битном сервере: Перенос процедур с клиента в серверный общий модуль &НаСервереБезКонтекста позволяет использовать все ресурсы серверного пула памяти. Для передачи документа на клиент его нельзя возвращать как мутабельное значение напрямую в тонком клиенте — используйте временное хранилище (ПоместитьВоВременноеХранилище и ПолучитьИзВременногоХранилища).
  3. Вынос COM-соединителя в отдельный процесс (COM+ Application): Чтобы V83.COMConnector не делил память с основным процессом 1С, настройте в диспетчере компонентов Windows его запуск в виде серверного COM+-приложения. Тогда он запустится в отдельном системном процессе dllhost.exe.

Способ 4. Потоковая выгрузка в CSV через ЗаписьТекста

Когда пользователю требуется отчет размером в сотни мегабайт или гигабайты (например, полный разрез номенклатуры, складов, остатков и покупателей), открывать его на экране не имеет практического смысла. Ни один пользователь не в состоянии визуально изучить сотни тысяч строк в интерфейсе 1С.

В таких сценариях наилучшей практикой является прямая генерация текстового файла (CSV) с помощью объекта ЗаписьТекста прямо на стороне сервера отчетов:


Запись = Новый ЗаписьТекста("C:\Temp\ExportReport.csv", КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Номенклатура;Склад;Покупатель;Остаток;Резерв");

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Стр = Строка(Выборка.Номенклатура) + ";" 
        + Строка(Выборка.Склад) + ";" 
        + Строка(Выборка.Покупатель) + ";" 
        + Строка(Выборка.Остаток) + ";" 
        + Строка(Выборка.Резерв);
    Запись.ЗаписатьСтроку(Стр);
КонецЦикла;
Запись.Закрыть();

Объект ЗаписьТекста пишет данные на диск буферизованно малыми порциями. Расход памяти остается стабильно низким на протяжении всей выгрузки независимо от размера итогового файла, а пользователь получает готовый файл для загрузки в специализированные аналитические системы или Excel.

← На главную