Каждый разработчик на платформе «1С:Предприятие» рано или поздно сталкивается с задачами кастомизации интерфейса под требования бизнеса. Одно из частых пожеланий пользователей — видеть дополнительную аналитическую информацию непосредственно в представлении ссылки (например, выводить сегмент клиента рядом с его наименованием в полях выбора, списках и отчетах). Первая и самая очевидная мысль, которая приходит в голову начинающему программисту — написать запрос к регистру сведений прямо в обработчике модуля менеджера. Однако это решение таит в себе серьезную угрозу для производительности всей системы.
В этой статье мы подробно разберем, почему запросы в обработчиках получения представления категорически запрещены стандартами разработки «1С», к каким последствиям они приводят под нагрузкой, и как решить задачу вывода динамических данных архитектурно правильно и максимально быстро.
Проанализируем ситуацию, описанную в исходном обсуждении. Автор пытается динамически получать сегмент клиента из периодического регистра сведений РегистрСведений.СегментыКлиентов.СрезПоследних непосредственно в обработчике получения представления справочника Клиенты. На небольших базах это может работать незаметно. Но как только объем данных вырастает, а пользователи начинают активно открывать формы списков документов, строить отчеты или выполнять поиск, система начинает «зависать» на 0.5–0.8 секунд при каждом выводе строки.
Давайте разберемся, почему так происходит:
ОбработкаПолученияПредставления колоссальное количество раз. Он срабатывает при визуализации каждой ссылки на форме, при генерации отчетов (если они строятся не через СКД или выводятся вручную), при записи и проведении документов, а также во время фоновых процессов обмена данными (например, при формировании журнала регистрации).СрезПоследних к СУБД, платформа выполнит 50 отдельных микрозапросов к базе данных вместо одного пакетного. Это лавинообразно нагружает процессор сервера приложений и СУБД.Согласно официальным стандартам разработки фирмы «1С», в обработчиках модуля менеджера ОбработкаПолученияПредставления и ОбработкаПолученияПолейПредставления категорически запрещено:
Запрос).Данные.Ссылка.Реквизит приводят к неявному считыванию всего объекта БД в память, что крайне непроизводительно).Эти обработчики должны работать исключительно «в памяти», оперируя уже готовыми данными, которые им передает платформа.
Для оптимизации получения представлений «под капотом» платформы реализован эффективный двухэтапный механизм. Разберем его по шагам:
Шаг 1. Определение необходимых данных. В обработчике ОбработкаПолученияПолейПредставления мы сообщаем платформе, какие именно реквизиты объекта нам понадобятся для формирования представления. Платформа собирает эти требования и автоматически модифицирует свои внутренние запросы к СУБД, выбирая нужные поля одним пакетом вместе с основными данными списка.
Шаг 2. Формирование строки представления. В обработчик ОбработкаПолученияПредставления в параметр Данные прилетает готовая структура, содержащая значения запрошенных на первом шаге реквизитов. Нам остается лишь «склеить» их в нужную строку без единого обращения к базе данных.
Но как быть, если нужные данные (сегмент клиента) лежат во внешнем регистре сведений, а не внутри самого справочника? Напрямую пробросить поля регистра через этот механизм невозможно. Здесь нам на помощь приходит архитектурный паттерн кэширования данных.
Чтобы обеспечить максимальную скорость работы интерфейса и соблюсти стандарты разработки, мы применим стратегию кэширования. Мы добавим в справочник Клиенты вспомогательный реквизит, куда будем записывать актуальный сегмент клиента. Получение представления будет происходить мгновенно из этого реквизита, а его актуализацию мы настроим в фоновом режиме.
Выполним реализацию этого подхода по шагам.
Добавим в справочник Клиенты новый реквизит:
СегментКэшСправочникСсылка.Сегменты (или Строка, если нужно хранить только текстовое представление сегмента для экономии ресурсов).Откроем модуль менеджера справочника Клиенты и пропишем стандартные обработчики платформы. Сначала укажем платформе, что нам нужен наш новый реквизит:
Процедура ОбработкаПолученияПолейПредставления(Поля, СтандартнаяОбработка)
// Добавляем наш кэширующий реквизит в массив полей, которые платформа прочитает из БД
Поля.Добавить("СегментКэш");
// Отключаем стандартную обработку, чтобы платформа знала, что мы сами управляем составом полей
СтандартнаяОбработка = Ложь;
КонецПроцедуры
Теперь напишем сам алгоритм формирования представления в памяти без использования запросов:
Процедура ОбработкаПолученияПредставления(Данные, Представление, СтандартнаяОбработка)
// Проверяем, что платформа передала нам структуру с нужным полем
Если Данные.Свойство("СегментКэш") Тогда
НаименованиеКлиента = ?(Данные.Свойство("Наименование"), Данные.Наименование, "");
Если ЗначениеЗаполнено(Данные.СегментКэш) Тогда
// Формируем красивое представление: "Иван Иванов (VIP)"
Представление = Строка(НаименованиеКлиента) + " (" + Строка(Данные.СегментКэш) + ")";
Иначе
Представление = Строка(НаименованиеКлиента);
КонецЕсли;
// Сигнализируем платформе, что представление успешно сформировано нами
СтандартнаяОбработка = Ложь;
КонецЕсли;
КонецПроцедуры
Данные в периодическом регистре сведений СегментыКлиентов могут меняться. Нам необходимо своевременно обновлять реквизит СегментКэш в справочнике Клиенты. Использовать регламентное задание, запускаемое раз в сутки — рабочий вариант, но пользователи могут жаловаться на неактуальность информации в течение дня.
Наиболее надежный и оперативный способ — использование механизма подписок на события. Создадим новую подписку на событие для регистра сведений.
Настройки подписки на событие:
ОбновитьКэшСегментовПриЗаписиРегистраРегистрСведенийНаборЗаписей.СегментыКлиентовПриЗаписиОбщегоНазначенияСервер), где будет располагаться наша процедура.Проанализируем и напишем код обработчика в общем модуле:
Процедура ОбновитьКэшСегментовПриЗаписиРегистраПриЗаписи(Источник, Отказ, Замещение) Экспорт
// Если это обмен данными, пропускаем обработку во избежание зацикливания или замедления импорта
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Собираем список клиентов, по которым изменились записи в регистре
ТаблицаИзменений = Источник.Выгрузить();
СписокКлиентов = Новый Массив;
Для Каждого СтрокаТаблицы Из ТаблицаИзменений Цикл
Если СписокКлиентов.Найти(СтрокаТаблицы.Клиент) = Неопределено Тогда
СписокКлиентов.Добавить(СтрокаТаблицы.Клиент);
КонецЕсли;
КонецЦикла;
Если СписокКлиентов.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Запросом получаем актуальные срезы последних для измененных клиентов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СегментыКлиентовСрезПоследних.Клиент КАК Клиент,
| СегментыКлиентовСрезПоследних.Сегмент КАК Сегмент
|ИЗ
| РегистрСведений.СегментыКлиентов.СрезПоследних(, Клиент В (&СписокКлиентов)) КАК СегментыКлиентовСрезПоследних";
Запрос.УстановитьПараметр("СписокКлиентов", СписокКлиентов);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Блокируем и обновляем реквизит в справочнике Клиенты
ОбъектКлиент = Выборка.Клиент.ПолучитьОбъект();
Если ОбъектКлиент <> Неопределено Тогда
// Записываем новое значение сегмента в кэш
ОбъектКлиент.СегментКэш = Выборка.Segment;
// Отключаем проверки при записи для ускорения процесса
ОбъектКлиент.ОбменДанными.Загрузка = Истина;
ОбъектКлиент.Записать();
// Оповещаем платформу о необходимости сбросить клиентский кэш представлений
ОповеститьОбИзменении(Выборка.Клиент);
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Платформа «1С» в целях экономии трафика кэширует представления ссылок на стороне клиента (особенно в Тонком клиенте). Если вы обновили реквизит СегментКэш в базе данных на сервере, клиентское приложение может еще какое-то время отображать старое представление ссылки.
Чтобы принудительно заставить платформу обновить визуальное представление конкретного элемента на клиенте, используется встроенный метод ОповеститьОбИзменении(Ссылка). Вызов этого метода мгновенно очищает кэш представлений для переданного объекта, гарантируя, что пользователи увидят актуальные данные без перезапуска сеанса.
В процессе обсуждения на форуме прозвучал важный вопрос: почему в типовых отчетах производительность представлений не страдает так сильно?
Дело в том, что СКД — это высокооптимизированный движок. При компоновке макета и выводе данных СКД самостоятельно анализирует структуру схемы компоновки и получает все необходимые представления скопом, упаковывая их в единый пакетный запрос к СУБД.
Однако, если вы формируете отчеты вручную, заполняя области табличного документа через код, или используете сложные самописные алгоритмы вывода списков, платформа лишается преимуществ автоматической оптимизации СКД. В таких случаях предложенный нами метод кэширования через реквизит справочника является единственным способом сохранить быстродействие системы на высоком уровне.
Мы спроектировали надежную архитектуру вывода связанных данных в представление объектов «1С»:
Применение данных паттернов программирования гарантирует высокую отзывчивость интерфейса вашей конфигурации даже при работе со сверхбольшими объемами данных!