Каждый разработчик 1С рано или поздно сталкивается с загадочной ситуацией: пользователь жалуется, что документ или отчет открывается мучительно долго (порой дольше минуты). Вы заходите в Конфигуратор, запускаете отладку, включаете стандартный Замер производительности, открываете форму, останавливаете замер и... видите поразительную картину. Суммарное время выполнения всех серверных и клиентских методов не превышает 2–3 секунд! Остальное время бесследно «исчезает» в неизвестности.
В этой статье мы подробно разберем, почему возникает этот невидимый пик нагрузки, куда девается время при переходе между сервером и клиентом, а также рассмотрим практические способы оптимизации тяжелых форм на примере документов бюджетирования (таких как в подсистеме БИТ.ФИНАНС).
Давайте проанализируем, что происходит на платформенном уровне, когда пользователь открывает управляемую форму. Встроенный замер производительности фиксирует исключительно время работы кода на встроенном языке 1С (то есть выполнение строк в процедурах ПриСозданииНаСервере, ПриЧтенииНаСервере, ПриОткрытии и так далее).
Однако между завершением работы серверного кода и началом работы клиентского кода происходит огромный пласт неявных низкоуровневых операций платформы, которые не содержат кода 1С, а потому принципиально не попадают в замер производительности:
Если на форме присутствует огромный объем сложных данных, эти четыре шага могут занимать до 95-98% от всего времени открытия формы. Именно поэтому стандартный замер производительности показывает всего 2 секунды, хотя пользователь ждет открытия целую минуту.
ТабличныйДокументТабличный документ (особенно сохраненный в реквизите типа ХранилищеЗначений базы данных) — один из самых тяжелых объектов для передачи между сервером и клиентом. Когда в документе содержится, например, всего 500 строк и 18 колонок, кажется, что данных совсем немного. Но давайте заглянем «под капот» табличного документа.
В отличие от плоской таблицы значений, табличный документ переносит с собой гигантский объем метаданных:
В результате сериализации XML-пакет такого документа раздувается до колоссальных размеров. И что самое неприятное — платформа вынуждена гонять этот тяжелый XML-пакет туда-обратно при каждом контекстном вызове сервера!
Важное замечание: Установка свойства элемента формы Видимость = Ложь никак не спасает ситуацию! Отключение видимости лишь скрывает элемент на экране на этапе отрисовки (шаг 4). Однако сам реквизит формы типа ТабличныйДокумент продолжает существовать в памяти, сериализоваться, передаваться по сети и десериализоваться на клиенте (шаги 1–3). Чтобы ускорить работу, нужно оптимизировать именно данные, а не интерфейс.
Если вы хотите увидеть реальные цифры накладных расходов платформы, воспользуйтесь настройкой Технологического журнала (ТЖ). Нас интересует сбор событий CALL (вызовы сервера с клиента). Проанализировав эти события, мы сможем увидеть разницу между временем отправки запроса с клиента и фактическим временем его выполнения на сервере.
Посмотрим на пример конфигурационного файла logcfg.xml для сбора статистики по вызовам:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\v8_logs\calls" history="24">
<event filter="eq" property="Name" value="CALL"/>
<property name="Duration"/>
<property name="CpuTime"/>
<property name="InBytes"/>
<property name="OutBytes"/>
</log>
</config>
Обратите внимание на параметры InBytes (принято байт) и OutBytes (отправлено байт). Если при открытии формы вы увидите там десятки мегабайт трафика, это послужит прямым доказательством того, что сеть и процессор перегружены сериализацией тяжелых реквизитов формы.
Рассмотрим по шагам четыре эффективных архитектурных сценария, как решить эту проблему и заставить форму открываться мгновенно.
Основная идея этого подхода — разделить процесс открытия формы и процесс загрузки в нее тяжелых данных. Мы открываем форму пустой (что происходит практически мгновенно), а затем в фоновом режиме загружаем данные. Пользователь сразу видит интерфейс и не испытывает психологического дискомфорта от «зависания» программы.
Реализуем этот механизм с помощью обработчика ожидания. Разберем шаблон кода для формы документа:
<p>&НаСервере<br>
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)<br>
// На сервере мы НЕ загружаем тяжелый табличный документ из базы данных<br>
// Оставляем реквизит формы "ТабличныйДокументДанных" пустым<br>
КонецПроцедуры</p>
<p>&НаКлиенте<br>
Процедура ПриОткрытии(Отказ)<br>
// Подключаем однократный обработчик ожидания с минимальной задержкой<br>
ПодключитьОбработчикОжидания("Подключаемый_ЗагрузитьДанныеОтложено", 0.1, Истина);<br>
КонецПроцедуры</p>
<p>&НаКлиенте<br>
Процедура Подключаемый_ЗагрузитьДанныеОтложено()<br>
// Показываем пользователю состояние ожидания<br>
Состояние("Пожалуйста, подождите. Выполняется загрузка данных бюджета...", , , БиблиотекаКартинок.ДлительнаяОперация16);<br>
<br>
// Вызываем серверную процедуру для загрузки<br>
ЗагрузитьДанныеНаСервере();<br>
<br>
// Очищаем состояние после успешного завершения<br>
ПоказатьОповещениеПользователя("Готово", , "Данные успешно загружены!", КартинкаШоу);<br>
КонецПроцедуры</p>
<p>&НаСервере<br>
Процедура ЗагрузитьДанныеНаСервере()<br>
// Извлекаем наш документ из базы данных<br>
ДокументОбъект = РеквизитФормыВЗначение("Объект");<br>
<br>
// Предположим, табличный документ хранится в реквизите "ХранилищеШаблона" типа ХранилищеЗначений<br>
ТабДокДанные = ДокументОбъект.ХранилищеШаблона.Получить();<br>
Если ТабДокДанные <> Неопределено Тогда<br>
// Помещаем полученный табличный документ в реквизит нашей управляемой формы<br>
ЗначениеВРеквизитФормы(ТабДокДанные, "ТабличныйДокументФормы");<br>
КонецЕсли;<br>
КонецПроцедуры</p>
Благодаря такой схеме форма отрисовывается сразу. Время ожидания загрузки самого табличного документа практически не уменьшилось, но визуально программа перестает раздражать пользователя «мертвым» зависанием.
Если мы оставим ТабличныйДокумент в реквизитах формы, то при каждом последующем нажатии любой кнопки на форме (которое вызывает контекстный серверный вызов) вся эта тяжелая таблица будет заново гоняться по сети.
Чтобы избежать этого оверхеда, мы можем передавать табличный документ на клиент через механизм Временного хранилища только в тот момент, когда это действительно нужно для отображения, а в самой форме хранить лишь уникальный строковый адрес во временном хранилище.
Проанализируем пример кода с использованием временного хранилища:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Генерируем уникальный адрес во временном хранилище для этой формы
АдресХранилищаТаблицы = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
КонецПроцедуры
&НаСервере
Процедура СформироватьТаблицуНаСервере()
// Создаем табличный документ в локальной серверной переменной
ТабДок = Новый ТабличныйДокумент;
// Заполняем его данными (например, строим дерево из 500 строк)
ЗаполнитьМакетБюджета(ТабДок);
// Помещаем готовый объект во временное хранилище по нашему адресу
ПоместитьВоВременноеХранилище(ТабДок, АдресХранилищаТаблицы);
КонецПроцедуры
&НаКлиенте
Процедура ОтобразитьТаблицуНаКлиенте()
// Вызываем серверный метод для генерации таблицы во временном хранилище
СформироватьТаблицуНаСервере();
// Получаем табличный документ на клиенте напрямую из хранилища
ТабличныйДокументНаФорме = ПолучитьИзВременногоХранилища(АдресХранилищаТаблицы);
КонецПроцедуры
Этот способ кардинально снижает объемы данных, пересылаемых при штатной работе пользователя с формой (при изменении других реквизитов, проведении документа или переключении вкладок).
Давайте честно признаем: во многих случаях использование объекта ТабличныйДокумент избыточно. Если вам нужно просто отобразить иерархическую структуру («дерево») данных бюджетирования, гораздо правильнее использовать стандартную Таблицу значений или Дерево значений на форме.
Почему дерево значений работает значительно быстрее табличного документа?
Поэтому хорошей практикой является хранение и обработка данных на сервере в плоской таблице или регистре, вывод на форму в виде ДереваЗначений, и лишь при необходимости вывода на печать или экспорта в Excel — генерация финального табличного документа.
Если специфика бизнес-логики (например, в БИТ.ФИНАНС) жестко требует вывода именно интерактивного табличного документа на форму для ручного ввода бюджета, необходимо провести ревизию макета и убрать из него все утяжеляющие элементы:
Применяя данные подходы в комплексе, мы сможем добиться комфортной и быстрой работы интерфейса системы даже на больших объемах данных планирования бюджетов.