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