Как оптимизировать поиск в управляемой форме с большим числом строк и хранить таблицу значений только на сервере?

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

При разработке управляемых форм в системе «1С:Предприятие 8» программисты часто сталкиваются с проблемой производительности интерфейса при работе с большими объемами данных. Типичный сценарий: на форме присутствуют две таблицы. Первая — относительно небольшая (до 100 строк), вторая — огромная (от 10 000 строк и более), содержащая детальную информацию из внешней системы или тяжелого запроса. Задача заключается в том, чтобы при выборе строки в первой таблице мгновенно находить и показывать связанные с ней 30–50 строк из второй таблицы.

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

В чем заключается проблема производительности управляемых форм?

Для начала проанализируем, почему стандартный подход с размещением больших таблиц в реквизитах формы работает медленно. Архитектура управляемого приложения построена на четком разделении клиента и сервера. Данные формы (то, что объявлено во вкладке «Реквизиты») составляют так называемый контекст формы.

При выполнении любого контекстного серверного вызова (процедуры с директивой &НаСервере) происходит автоматическая сериализация всего контекста формы в XML/JSON, передача его по сети на сервер, десериализация, выполнение серверного кода, повторная сериализация измененных данных и их отправка обратно на клиент. Если в реквизитах формы лежит таблица на 10 000 строк, этот массив данных постоянно гоняется туда-сюда, нагружая процессор и сеть.

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

Для решения этой проблемы мы рассмотрим три эффективных архитектурных паттерна.

Решение 1. Снятие флага «Использовать всегда» у реквизитов формы

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

  1. Если мы создаем реквизит формы с типом ТаблицаЗначений, заполняем его на сервере, но не выносим элементы этой таблицы на саму форму (в визуальное дерево элементов), платформа все равно по умолчанию передает эти данные на клиент.
  2. Если мы снимем флаг Использовать всегда в свойствах этого реквизита, платформа применит оптимизацию. Данные этого реквизита будут находиться на сервере и не будут отправляться на клиентский компьютер при открытии формы и последующих вызовах.
  3. Доступ к этой таблице значений на сервере останется свободным: мы сможем читать и изменять её внутри серверных процедур.

Важное ограничение данного метода: Несмотря на то, что данные не передаются на клиент, они все равно остаются частью контекста формы. При выполнении контекстных вызовов &НаСервере система тратит ресурсы на сериализацию и десериализацию этой таблицы на самом сервере приложений для восстановления состояния формы. Если ТЗ действительно огромная, это может создавать скрытую нагрузку на сервер. Поэтому перейдем к более профессиональному решению.

Решение 2. Хранение промежуточных таблиц во Временном хранилище

Помещение таблицы значений во временное хранилище — это лучший способ полностью изолировать тяжелые данные от контекста формы. В реквизитах формы мы сохраняем только одну короткую строку — адрес во временном хранилище (АдресВременногоХранилища). Сама таблица физически лежит в оперативной памяти сервера приложений 1С.

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

Рассмотрим пример кода при инициализации формы на сервере:


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

В этом примере АдресХранилищаСоответствий — это строковый реквизит формы. Теперь наша огромная таблица надежно сохранена на сервере, она не участвует в контекстном обмене, а благодаря предварительной индексации поиск по ней будет занимать доли миллисекунды.

Решение 3. Реализация паттерна «Ленивая загрузка» (Lazy Loading)

Выясним, как организовать вывод данных пользователю. Заказчики часто требуют: "Мы хотим видеть все 10 000 строк большой таблицы на экране!". На практике выводить такое количество строк на форму — интерфейсное преступление. Веб-браузер или тонкий клиент начнут сильно тормозить из-за отрисовки огромного дерева элементов (DOM).

Применим паттерн ленивой загрузки. Мы создадим на форме небольшую визуальную таблицу (назовем её ТаблицаПодчиненныхСтрок), в которой изначально будет пусто. Как только пользователь активизирует строку в малой таблице, мы будем динамически обращаться к серверу через быстрый внеконтекстный вызов, забирать из временного хранилища нужные 30–50 строк и мгновенно отображать их.

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


&НаСервереБезКонтекста
Функция ПолучитьСвязанныеСтрокиНаСервере(Знач АдресХранилища, Знач КлючСвязи)
    
    Результат = Новый Массив;
    
    // Извлекаем таблицу из временного хранилища. 
    // Поскольку мы работаем в рамках одного рабочего процесса, 
    // десериализация не происходит, мы получаем объект напрямую из кэша!
    ТЗ = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    Если ТЗ <> Неопределено Тогда
        
        // Быстрый поиск благодаря созданному ранее индексу
        Отбор = Новый Структура("КлючСвязи", КлючСвязи);
        НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
        
        // Передавать мутабельные типы (строки ТЗ) на клиент нельзя.
        // Перекладываем данные в массив легковесных структур.
        Для Каждого СтрокаТЗ Из НайденныеСтроки Цикл
            СтруктураДанных = Новый Структура;
            СтруктураДанных.Вставить("Идентификатор", СтрокаТЗ.Идентификатор);
            СтруктураДанных.Вставить("Наименование", СтрокаТЗ.Наименование);
            СтруктураДанных.Вставить("Количество", СтрокаТЗ.Количество);
            
            Результат.Добавить(СтруктураДанных);
        КонецЦикла;
        
    КонецЕсли;
    
    Возврат Результат;
    
КонецФункции

Теперь свяжем этот механизм с интерфейсом формы. Нам нужно обрабатывать событие активизации строки в малой таблице. Разберем клиентский код:


&НаКлиенте
Процедура МалаяТаблицаПриАктивизацииСтроки(Элемент)
    
    ТекущиеДанные = Элементы.МалаяТаблица.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Очищаем подчиненную таблицу на форме перед загрузкой новых данных
    ТаблицаПодчиненныхСтрок.Очистить();
    
    // Выполняем сверхбыстрый внеконтекстный вызов сервера
    НайденныеСтроки = ПолучитьСвязанныеСтрокиНаСервере(АдресХранилищаСоответствий, ТекущиеДанные.КлючСвязи);
    
    // Заполняем визуальную таблицу на клиенте
    Для Каждого СтрокаДанных Из НайденныеСтроки Цикл
        НоваяСтрока = ТаблицаПодчиненныхСтрок.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
    КонецЦикла;
    
КонецПроцедуры

Преимущества разработанной архитектуры

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

  1. Минимальный контекст формы: Форма остается легкой. При любых стандартных контекстных вызовах сервер не тратит ресурсы на сериализацию десятков тысяч строк.
  2. Отсутствие клиентских задержек: Данные подгружаются порциями (по 30-50 строк) только тогда, когда они действительно нужны пользователю.
  3. Быстрый поиск на сервере: Благодаря использованию метода Индексы.Добавить() поиск по таблице в 10 000 строк выполняется за миллисекунды.
  4. Экономия ресурсов сети: Между клиентом и сервером во внеконтекстном вызове передаются только параметры поиска и результирующий массив структур из нескольких десятков элементов.
  5. Стабильность сессий: Веб-клиент не зависает из-за нехватки оперативной памяти в виртуальной машине браузера, так как на клиенте никогда не находится более 100 строк одновременно.

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

← На главную