Почему HTTP-сервис 1С работает на 200 тыс. строк, но падает на 400 тыс. строк?

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

При проектировании интеграционных решений на платформе 1С:Предприятие разработчики часто сталкиваются со специфическим поведением HTTP-сервисов. Типичный сценарий: один и тот же запрос, возвращающий около 200 тысяч строк данных, успешно отрабатывает в веб-сервисе, но при увеличении объема выборки до 400 тысяч строк метод Запрос.Выполнить() вызывает аварийное завершение процесса или возвращает ошибку. При этом запуск аналогичного запроса через внешнюю обработку в обычном сеансе проходит без каких-либо затруднений.

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

Архитектурная причина сбоя: Ограничение 32-битного процесса

Основная причина, по которой Запрос.Выполнить() падает в контексте веб-сервиса, кроется в ограничениях виртуальной памяти 32-разрядных процессов. Проанализируем, как именно распределяются ресурсы в данной связке:

  1. Единое адресное пространство процесса. При публикации файловой базы данных 1С на 32-битном веб-сервере Apache 2.4 все операции выполняются внутри единого рабочего процесса httpd.exe. В адресное пространство этого процесса загружается модуль расширения веб-сервера 1С (wsap24.dll), программное ядро самой платформы, а также движок файловой СУБД 1С (dbeng8.dll).
  2. Жесткий лимит памяти в 2 ГБ. Любой 32-битный процесс в операционной системе Windows имеет строгое ограничение на объем виртуального адресного пространства, составляющее 2 ГБ. Это ограничение непреодолимо для стандартных конфигураций приложений, не скомпилированных с флагом Large Address Aware (LAA), который на веб-серверах использовать крайне небезопасно.
  3. Специфика работы файлового движка. В файловом режиме работы 1С вся обработка данных (сортировка, индексация, фильтрация, создание временных таблиц) ложится на плечи клиентского процесса. В нашем случае этим процессом является httpd.exe. Выборка в 400 тысяч строк, содержащих длинные текстовые значения (например, 150-символьные акцизные марки), создает колоссальную нагрузку на оперативную память. Платформа 1С расходует память не только на сами строковые данные, но и на служебные метаданные каждого объекта и строки таблицы значений.
  4. Фрагментация оперативной памяти. К моменту выполнения тяжелого запроса процесс Apache уже успевает обработать множество других запросов, что неизбежно приводит к фрагментации «кучи» (heap) оперативной памяти. Когда платформа пытается выделить один крупный непрерывный блок памяти для создания таблицы результатов запроса на 400 тысяч строк, операционная система отвечает отказом. В результате происходит падение рабочего потока или всего процесса веб-сервера с ошибкой нарушения прав доступа (Access Violation, код 0xc0000005).

Почему тот же запрос успешно выполняется во внешней обработке?

Разберем, почему аналогичный код работает во внешней обработке в рамках обычного сеанса пользователя. При запуске внешней обработки выполнение происходит в контексте изолированного клиентского процесса (1cv8.exe или 1cv8c.exe). Этот процесс:

Проблема сериализации данных в JSON/XML

Даже если бы метод Запрос.Выполнить() успешно отработал и вернул результат в память, веб-сервер неизбежно столкнулся бы со вторым барьером — сериализацией данных. Конвертация огромной таблицы значений в формат JSON или XML с помощью встроенных механизмов (например, ЗаписатьJSON()) требует создания в оперативной памяти гигантского текстового буфера.

Процесс сериализации объектов увеличивает пиковое потребление памяти в 3–5 раз по сравнению с исходным размером данных в СУБД. Это гарантированно привело бы к падению процесса Apache на этапе формирования HTTP-ответа.

Решение №1: Реализация постраничной выборки (пагинации)

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

Рассмотрим пример реализации метода HTTP-сервиса, который возвращает данные порциями по 10 000 записей, используя уникальный идентификатор или код маркировки в качестве курсора:


Функция ПолучитьПорциюМарок(ЗапросHTTP)
    
    ПараметрыЗапроса = ЗапросHTTP.ПараметрыЗапроса;
    ПоследнийКод = ПараметрыЗапроса.Получить("ПоследнийКод");
    РазмерПорции = 10000;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПорции, "ЧГ=0") + "
        |	Маркировка.КодМаркировки КАК КодМаркировки,
        |	Маркировка.Номенклатура.Наименование КАК НоменклатураНаименование
        |ИЗ
        |	РегистрСведений.АкцизныеМарки КАК Маркировка
        |ГДЕ
        |	Маркировка.КодМаркировки > &ПоследнийКод
        |УПОРЯДОЧИТЬ ПО
        |	КодМаркировки";
        
    Запрос.УстановитьПараметр("ПоследнийКод", ?(ЗначениеЗаполнено(ПоследнийКод), ПоследнийКод, ""));
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    МассивДанных = Новый Массив;
    ТекущийПоследнийКод = "";
    
    Пока Выборка.Следующий() Цикл
        СтруктураСтроки = Новый Структура("Код, Номенклатура", Выборка.КодМаркировки, Выборка.НоменклатураНаименование);
        МассивДанных.Добавить(СтруктураСтроки);
        ТекущийПоследнийКод = Выборка.КодМаркировки;
    КонецЦикла;
    
    // Формируем структуру ответа с метаданными пагинации
    СтруктураОтвета = Новый Структура;
    СтруктураОтвета.Вставить("Данные", МассивДанных);
    СтруктураОтвета.Вставить("ПоследнийКод", ТекущийПоследнийКод);
    СтруктураОтвета.Вставить("ЕстьЕщеСтроки", МассивДанных.Количество() = РазмерПорции);
    
    // Сериализация результата
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, СтруктураОтвета);
    СтрокаJSON = ЗаписьJSON.Закрыть();
    
    Ответ = Новый HTTPСервисОтвет(200);
    Ответ.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8);
    Возврат Ответ;
    
КонецФункции

Преимущества данного подхода:

Решение №2: Инфраструктурные изменения (переход на 64-битные версии)

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

  1. Переход на 64-битный веб-сервер Apache. Вам необходимо полностью удалить 32-разрядную версию Apache и установить 64-разрядную версию веб-сервера.
  2. Использование 64-битной платформы 1С. Убедитесь, что на сервере установлена 64-битная версия платформы 1С:Предприятие. При публикации баз данных используйте 64-битный модуль сопряжения wsap24.dll из каталога установленной 64-битной платформы.

Переход на 64-разрядную архитектуру снимает ограничение в 2 ГБ на адресное пространство процесса. Рабочий процесс Apache сможет адресовать терабайты оперативной памяти, что полностью решит проблему падения веб-сервера при нехватке памяти.

Решение №3: Переход на клиент-серверный вариант работы (SQL)

Использование файлового режима работы для высоконагруженных HTTP-сервисов и мобильных обменов является критической уязвимостью архитектуры. Мы настоятельно рекомендуем перевести базу данных на работу под управлением СУБД (PostgreSQL, MS SQL Server, Oracle или IBM DB2):

Решение №4: Оптимизация сериализации через временные файлы

Для минимизации накладных расходов памяти при сериализации больших объемов данных можно использовать потоковую запись во временные файлы на диске вместо удержания всей строки JSON в оперативной памяти сервера:


// Пример потоковой записи во временный файл
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("json");

ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ИмяВременногоФайла, КодировкаТекста.UTF8);

ЗаписатьJSON(ЗаписьJSON, МассивДанных); // Массив данных пишется напрямую на диск
ЗаписьJSON.Закрыть();

// Чтение файла в двоичные данные для отправки клиенту
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайла);

Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеФайла);

// Удаление временного файла после отправки
УдалитьФайлы(ИмяВременногоФайла);

Этот прием позволяет избежать дублирования данных в оперативной памяти на этапе вызова метода УстановитьТелоИзСтроки(), так как данные передаются клиенту в виде бинарного потока непосредственно с диска.

Решение №5: Настройка перезапуска рабочих процессов Apache

Для предотвращения накопления фрагментации памяти в конфигурационном файле веб-сервера Apache httpd.conf рекомендуется настроить лимиты на время жизни рабочих потоков. Для этого используется директива MaxConnectionsPerChild (в старых версиях Apache — MaxRequestsPerChild):


<IfModule mpm_winnt_module>
    ThreadsPerChild        150
    MaxConnectionsPerChild 1000
</IfModule>

Установка параметра MaxConnectionsPerChild в значение, отличное от нуля (например, 1000), заставляет веб-сервер автоматически перезапускать дочерний процесс после обработки указанного количества запросов. Это гарантирует своевременное освобождение заблокированной оперативной памяти и предотвращает критические сбои при обработке тяжелых интеграционных пакетов.

← На главную