При проектировании интеграционных решений на платформе 1С:Предприятие разработчики часто сталкиваются со специфическим поведением HTTP-сервисов. Типичный сценарий: один и тот же запрос, возвращающий около 200 тысяч строк данных, успешно отрабатывает в веб-сервисе, но при увеличении объема выборки до 400 тысяч строк метод Запрос.Выполнить() вызывает аварийное завершение процесса или возвращает ошибку. При этом запуск аналогичного запроса через внешнюю обработку в обычном сеансе проходит без каких-либо затруднений.
В этой статье мы подробно проанализируем внутренние механизмы работы платформы 1С и веб-сервера Apache, выясним точные причины такого поведения системы и рассмотрим эффективные архитектурные и инфраструктурные способы решения этой проблемы.
Основная причина, по которой Запрос.Выполнить() падает в контексте веб-сервиса, кроется в ограничениях виртуальной памяти 32-разрядных процессов. Проанализируем, как именно распределяются ресурсы в данной связке:
httpd.exe. В адресное пространство этого процесса загружается модуль расширения веб-сервера 1С (wsap24.dll), программное ядро самой платформы, а также движок файловой СУБД 1С (dbeng8.dll).httpd.exe. Выборка в 400 тысяч строк, содержащих длинные текстовые значения (например, 150-символьные акцизные марки), создает колоссальную нагрузку на оперативную память. Платформа 1С расходует память не только на сами строковые данные, но и на служебные метаданные каждого объекта и строки таблицы значений.0xc0000005).Разберем, почему аналогичный код работает во внешней обработке в рамках обычного сеанса пользователя. При запуске внешней обработки выполнение происходит в контексте изолированного клиентского процесса (1cv8.exe или 1cv8c.exe). Этот процесс:
Даже если бы метод Запрос.Выполнить() успешно отработал и вернул результат в память, веб-сервер неизбежно столкнулся бы со вторым барьером — сериализацией данных. Конвертация огромной таблицы значений в формат JSON или XML с помощью встроенных механизмов (например, ЗаписатьJSON()) требует создания в оперативной памяти гигантского текстового буфера.
Процесс сериализации объектов увеличивает пиковое потребление памяти в 3–5 раз по сравнению с исходным размером данных в СУБД. Это гарантированно привело бы к падению процесса Apache на этапе формирования HTTP-ответа.
Самым надежным архитектурным решением является отказ от единовременной выгрузки больших массивов данных. Нам необходимо переписать логику взаимодействия мобильного приложения (или ТСД) с HTTP-сервисом так, чтобы данные запрашивались порциями (страницами).
Рассмотрим пример реализации метода HTTP-сервиса, который возвращает данные порциями по 10 000 записей, используя уникальный идентификатор или код маркировки в качестве курсора:
Функция ПолучитьПорциюМарок(ЗапросHTTP)
ПараметрыЗапроса = ЗапросHTTP.ПараметрыЗапроса;
ПоследнийКод = ПараметрыЗапроса.Получить("ПоследнийКод");
РазмерПорции = 10000;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПорции, "ЧГ=0") + "
| Маркировка.КодМаркировки КАК КодМаркировки,
| Маркировка.Номенклатура.Наименование КАК НоменклатураНаименование
|ИЗ
| РегистрСведений.АкцизныеМарки КАК Маркировка
|ГДЕ
| Маркировка.КодМаркировки > &ПоследнийКод
|УПОРЯДОЧИТЬ ПО
| КодМаркировки";
Запрос.УстановитьПараметр("ПоследнийКод", ?(ЗначениеЗаполнено(ПоследнийКод), ПоследнийКод, ""));
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
МассивДанных = Новый Массив;
ТекущийПоследнийКод = "";
Пока Выборка.Следующий() Цикл
СтруктураСтроки = Новый Структура("Код, Номенклатура", Выборка.КодМаркировки, Выборка.НоменклатураНаименование);
МассивДанных.Добавить(СтруктураСтроки);
ТекущийПоследнийКод = Выборка.КодМаркировки;
КонецЦикла;
// Формируем структуру ответа с метаданными пагинации
СтруктураОтвета = Новый Структура;
СтруктураОтвета.Вставить("Данные", МассивДанных);
СтруктураОтвета.Вставить("ПоследнийКод", ТекущийПоследнийКод);
СтруктураОтвета.Вставить("ЕстьЕщеСтроки", МассивДанных.Количество() = РазмерПорции);
// Сериализация результата
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураОтвета);
СтрокаJSON = ЗаписьJSON.Закрыть();
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8);
Возврат Ответ;
КонецФункции
Преимущества данного подхода:
Если изменить бизнес-логику обмена данными невозможно или необходимо временно сохранить текущую монолитную схему выгрузки, следует провести модернизацию инфраструктуры:
wsap24.dll из каталога установленной 64-битной платформы.Переход на 64-разрядную архитектуру снимает ограничение в 2 ГБ на адресное пространство процесса. Рабочий процесс Apache сможет адресовать терабайты оперативной памяти, что полностью решит проблему падения веб-сервера при нехватке памяти.
Использование файлового режима работы для высоконагруженных HTTP-сервисов и мобильных обменов является критической уязвимостью архитектуры. Мы настоятельно рекомендуем перевести базу данных на работу под управлением СУБД (PostgreSQL, MS SQL Server, Oracle или IBM DB2):
Для минимизации накладных расходов памяти при сериализации больших объемов данных можно использовать потоковую запись во временные файлы на диске вместо удержания всей строки JSON в оперативной памяти сервера:
// Пример потоковой записи во временный файл
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("json");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ИмяВременногоФайла, КодировкаТекста.UTF8);
ЗаписатьJSON(ЗаписьJSON, МассивДанных); // Массив данных пишется напрямую на диск
ЗаписьJSON.Закрыть();
// Чтение файла в двоичные данные для отправки клиенту
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайла);
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеФайла);
// Удаление временного файла после отправки
УдалитьФайлы(ИмяВременногоФайла);
Этот прием позволяет избежать дублирования данных в оперативной памяти на этапе вызова метода УстановитьТелоИзСтроки(), так как данные передаются клиенту в виде бинарного потока непосредственно с диска.
Для предотвращения накопления фрагментации памяти в конфигурационном файле веб-сервера Apache httpd.conf рекомендуется настроить лимиты на время жизни рабочих потоков. Для этого используется директива MaxConnectionsPerChild (в старых версиях Apache — MaxRequestsPerChild):
<IfModule mpm_winnt_module>
ThreadsPerChild 150
MaxConnectionsPerChild 1000
</IfModule>
Установка параметра MaxConnectionsPerChild в значение, отличное от нуля (например, 1000), заставляет веб-сервер автоматически перезапускать дочерний процесс после обработки указанного количества запросов. Это гарантирует своевременное освобождение заблокированной оперативной памяти и предотвращает критические сбои при обработке тяжелых интеграционных пакетов.