При разработке управляемых форм в системе «1С:Предприятие 8» программисты часто сталкиваются с проблемой производительности интерфейса при работе с большими объемами данных. Типичный сценарий: на форме присутствуют две таблицы. Первая — относительно небольшая (до 100 строк), вторая — огромная (от 10 000 строк и более), содержащая детальную информацию из внешней системы или тяжелого запроса. Задача заключается в том, чтобы при выборе строки в первой таблице мгновенно находить и показывать связанные с ней 30–50 строк из второй таблицы.
Если решать эту задачу «в лоб» через стандартные реквизиты формы, то при каждом серверном вызове огромные массивы данных будут циркулировать между клиентом и сервером. Это приводит к колоссальным задержкам, перерасходу оперативной памяти на сервере приложений и зависанию веб-клиентов. В этой статье мы подробно разберем, как правильно организовать хранение тяжелых таблиц значений (ТЗ) исключительно на стороне сервера и как реализовать мгновенный поиск связей без перегрузки контекста формы.
Для начала проанализируем, почему стандартный подход с размещением больших таблиц в реквизитах формы работает медленно. Архитектура управляемого приложения построена на четком разделении клиента и сервера. Данные формы (то, что объявлено во вкладке «Реквизиты») составляют так называемый контекст формы.
При выполнении любого контекстного серверного вызова (процедуры с директивой &НаСервере) происходит автоматическая сериализация всего контекста формы в XML/JSON, передача его по сети на сервер, десериализация, выполнение серверного кода, повторная сериализация измененных данных и их отправка обратно на клиент. Если в реквизитах формы лежит таблица на 10 000 строк, этот массив данных постоянно гоняется туда-сюда, нагружая процессор и сеть.
Кроме того, на стороне клиента отсутствуют эффективные механизмы быстрого поиска по коллекциям (такие как индексированные таблицы значений с методом НайтиСтроки). Поиск на клиенте выполняется полным перебором, что делает работу интерфейса крайне медленной.
Для решения этой проблемы мы рассмотрим три эффективных архитектурных паттерна.
Рассмотрим первый и самый простой способ ограничить передачу данных на клиент. У каждого реквизита управляемой формы есть свойство Использовать всегда. Разберем по шагам, как работает этот механизм:
ТаблицаЗначений, заполняем его на сервере, но не выносим элементы этой таблицы на саму форму (в визуальное дерево элементов), платформа все равно по умолчанию передает эти данные на клиент.Важное ограничение данного метода: Несмотря на то, что данные не передаются на клиент, они все равно остаются частью контекста формы. При выполнении контекстных вызовов &НаСервере система тратит ресурсы на сериализацию и десериализацию этой таблицы на самом сервере приложений для восстановления состояния формы. Если ТЗ действительно огромная, это может создавать скрытую нагрузку на сервер. Поэтому перейдем к более профессиональному решению.
Помещение таблицы значений во временное хранилище — это лучший способ полностью изолировать тяжелые данные от контекста формы. В реквизитах формы мы сохраняем только одну короткую строку — адрес во временном хранилище (АдресВременногоХранилища). Сама таблица физически лежит в оперативной памяти сервера приложений 1С.
Выясним, как правильно управлять временем жизни данных во временном хранилище, чтобы избежать утечек памяти. По умолчанию данные в хранилище живут в течение сеанса пользователя. Но если мы свяжем их с уникальным идентификатором формы, платформа автоматически очистит память при закрытии этой формы.
Рассмотрим пример кода при инициализации формы на сервере:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем данные из внешнего источника или тяжелым запросом
ТЗ_Соответствий = ПолучитьДанныеСоответствийИзВнешнейСистемы();
// Критически важно проиндексировать колонку поиска для быстродействия!
ТЗ_Соответствий.Индексы.Добавить("КлючСвязи");
// Помещаем ТЗ во временное хранилище с привязкой к УИД формы
АдресХранилищаСоответствий = ПоместитьВоВременноеХранилище(ТЗ_Соответствий, ЭтаФорма.УникальныйИдентификатор);
КонецПроцедуры
В этом примере АдресХранилищаСоответствий — это строковый реквизит формы. Теперь наша огромная таблица надежно сохранена на сервере, она не участвует в контекстном обмене, а благодаря предварительной индексации поиск по ней будет занимать доли миллисекунды.
Выясним, как организовать вывод данных пользователю. Заказчики часто требуют: "Мы хотим видеть все 10 000 строк большой таблицы на экране!". На практике выводить такое количество строк на форму — интерфейсное преступление. Веб-браузер или тонкий клиент начнут сильно тормозить из-за отрисовки огромного дерева элементов (DOM).
Применим паттерн ленивой загрузки. Мы создадим на форме небольшую визуальную таблицу (назовем её ТаблицаПодчиненныхСтрок), в которой изначально будет пусто. Как только пользователь активизирует строку в малой таблице, мы будем динамически обращаться к серверу через быстрый внеконтекстный вызов, забирать из временного хранилища нужные 30–50 строк и мгновенно отображать их.
Проанализируем программную реализацию этого процесса. Для начала создадим функцию без контекста, которая будет выполнять быстрый поиск в памяти сервера:
&НаСервереБезКонтекста
Функция ПолучитьСвязанныеСтрокиНаСервере(Знач АдресХранилища, Знач КлючСвязи)
Результат = Новый Массив;
// Извлекаем таблицу из временного хранилища.
// Поскольку мы работаем в рамках одного рабочего процесса,
// десериализация не происходит, мы получаем объект напрямую из кэша!
ТЗ = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТЗ <> Неопределено Тогда
// Быстрый поиск благодаря созданному ранее индексу
Отбор = Новый Структура("КлючСвязи", КлючСвязи);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
// Передавать мутабельные типы (строки ТЗ) на клиент нельзя.
// Перекладываем данные в массив легковесных структур.
Для Каждого СтрокаТЗ Из НайденныеСтроки Цикл
СтруктураДанных = Новый Структура;
СтруктураДанных.Вставить("Идентификатор", СтрокаТЗ.Идентификатор);
СтруктураДанных.Вставить("Наименование", СтрокаТЗ.Наименование);
СтруктураДанных.Вставить("Количество", СтрокаТЗ.Количество);
Результат.Добавить(СтруктураДанных);
КонецЦикла;
КонецЕсли;
Возврат Результат;
КонецФункции
Теперь свяжем этот механизм с интерфейсом формы. Нам нужно обрабатывать событие активизации строки в малой таблице. Разберем клиентский код:
&НаКлиенте
Процедура МалаяТаблицаПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элементы.МалаяТаблица.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Очищаем подчиненную таблицу на форме перед загрузкой новых данных
ТаблицаПодчиненныхСтрок.Очистить();
// Выполняем сверхбыстрый внеконтекстный вызов сервера
НайденныеСтроки = ПолучитьСвязанныеСтрокиНаСервере(АдресХранилищаСоответствий, ТекущиеДанные.КлючСвязи);
// Заполняем визуальную таблицу на клиенте
Для Каждого СтрокаДанных Из НайденныеСтроки Цикл
НоваяСтрока = ТаблицаПодчиненныхСтрок.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
КонецЦикла;
КонецПроцедуры
Подведем итоги и проанализируем, почему данная связка механизмов является наиболее эффективной для оптимизации работы с большими таблицами:
Индексы.Добавить() поиск по таблице в 10 000 строк выполняется за миллисекунды.Используя предложенные паттерны проектирования, вы сможете создавать отзывчивые, высокопроизводительные и стабильные управляемые формы даже при интеграции со сложными внешними источниками данных.