Почему движки уходят в переполнение (Overrun)

Почему движки уходят в переполнение (Overrun)

      Введение. 

      В статье рассмотрены понятия Engines, Overrun, Scan Cycle. Выявление состояния переполнения, возможные причины и как избежать переполнения движков.

      Что такое Engine? 

      Application Server (aaEngine.exe) - это 32-битный процесс System Platform, который поочередно выполняет объекты, расположенные на нем.

      Application Engine (aaEngine.exe) процесс может быть:

      1. Встроен в объект WinPlatform,

      2. Приложением AppEngine - движком для планирования и выполнения прикладных объектов,

      3. Приложением ViewEngine - движком для нужд визуализации.

      Каждый engine процесс имеет уникальное имя в Galaxy и уникальный engine ID в платформе, на которой запущен. Engine, встроенный в WinPlatform всегда имеет ID 1. На рисунке 1 показаны разные типа движков в IDE.

 

Рисунок 1 - Типы движков в IDE

      Просмотр детальной информации по движкам доступен в разделе Platform Manager в SMC, как показано на рисунке 2.

 


Рисунок 2 - Информация по движкам в Platform Manager

      Все движки запускают процесс aaEngine.exe в Task Manager (диспетчер задач). Для идентификации процесса воспользуйтесь вкладкой Details Task Manager. Для этого:

      1. Откройте Task Manager и перейдите во вкладку Details.

      2. Нажмите правой кнопкой мыши по заголовку колонки и выберите Select Columns.

      3. Найдите и выберите Command Line.

      4. Нажмите OK. Теперь Command Line будет отображаться, как показано на рисунке 3.

 


Рисунок 3 - Процессы aaEngine.exe в Task Manager

В этом примере показан ID = 1 для движка, встроенного в объект WinPlatform.

 

      Поведение Application Engine в среде Runtime.


      Scan Cycle (цикл сканирования).

     Движки работают циклично. Время, которое движок затрачивает на выполнение всей работы называется периодом сканирования (Scan period)

      Назначить Scan period для разных движков (WinPlatform, AppEngine или ViewEngine) можно с помощью свойства Scan Period. Значение задается в мс. На рисунке 4 показан редактор объекта AppEngine.

 

 Рисунок 4 - Конфигурирование Scan period

      На рисунке 4 изображен нормальный скан цикл (Scan cycle)  движка. 

      Голубые и фиолетовые секции отображают время выполнения всех объектов, расположенных на движке (включая скрипты этих объектов). Остаток времени (секции серого цвета) посвящается объектам связи (DIObjects) и далее время простоя. Дополнительно небольшое окно отведено под обновление данных checkpoint (Runtime значения, которые хранятся на диске, которые запрашиваются при деплое или при переключении между резервными движками).

      Время выполнения движка (Execution time) может варьироваться от одного цикла к другому в зависимости от загруженности объектов и производительности системных ресурсов. Рекомендуется закладывать среднее время простоя (встроенный атрибут Scheduler.TimeIdleAvg) 50% и более от скан периода движка. Это гарантирует, что у движка достаточно времени, чтобы выполнить все запланированные задачи вовремя.

 


Рисунок 4 - Scan Cycle движка 

      Если движок не успевает выполнить все задачи внутри одного скан периода, он уйдет в переполнение (overrun) в следующем скан периоде, как показано на рисунке 5.

 


Рисунок 5 - Overrun движка

Примечание: Хотя объекты WinPlatform и ViewEngine не могут содержать прикладные объекты, они могут иметь IO атрибуты и скрипты, приводящие к переполнению.


      Порядок выполнения объектов.

      Порядок выполнения объектов, расположенных на Application Engine показан на рисунке 6.

 


Рисунок 6 - Порядок выполнения объектов на движке

      Объекты выполняются в алфавитном порядке. Однако, вы можете изменить порядок выполнения для Area и объектов автоматизации, настроив порядок выполнения объектов (рисунок 7).



Рисунок 7 - Изменение порядка выполнения объектов

Изменение порядка выполнения недоступно для Device Integration объектов, Engine объектов (App и View) и для объектов WinPlatform. Эти объекты всегда выполняются в алфавитном порядке.

 

      Основные причины переполнения движков.

 

      Есть множество причин, по которым движоки могут уйти в переполнение.

      Три основные причины это:

      1. Скрипты объектов (Object scripting),

      2. Перегрузка (Overloading),

      3. Окружение (Environment).


      Скрипты объектов


      Масштабируемость - преимущество Application Server. Это достигается за счет управления единым пространством имен .NET Framework. Слишком просто написать скрипт, который будет нагружать движок. Сотни и тысячи экземпляров объектов могут привести к этому. Многие проблемы скриптов могут быть исключены.


      Скрипты Non-Execute

      Application Server позволяет позволяет выбрать вариант выполнения скрипта. По умолчанию, большинство скриптов имеют тип Execute. 

      Использовании скриптов типа OnStartup и OnScan следует минимизировать по следующим причинам:

1. Эти скрипты имеют лимит выполнения по сравнению со скриптами Execute. Следовательно, они могут удерживать движок в процессе выполнения.

2. Выполнение скриптовой логики может увеличивать время deploy/undeploy и даже приводить в ошибке.

3. Некоторые данные в скриптах недоступны. Атрибуты объектов запущены на других движках и недоступны в скриптах OnStartup.

В этих скриптах должны использоваться локальные атрибуты и переменные.

 

      Блокировка скриптов

 

      Используйте тип выполнения asynchronously для скриптов, которые могут быть блокированы. Это можно сделать через редактор объекта, как показано на рисунке 8.

 


Рисунок 8 - Выбор асинхронного типа выполнения скрипта

      Типичным примером скрипта, который может быть блокирован, является SQL скрипт, особенно когда происходит соединение с базой данных, запись/считывание в файл. Эти операции могут превышать скан период движка, особенно если скан период равен 500 мс (значение по умолчанию). Если тип запуска скрипта синхронный (synchronously), движок ожидает завершения скрипта и не продолжает выполнение пока скрипт не завершится. Выполнение осуществляется в основном потоке движка. Эти скрипты могут блокировать движок и приводить движок в Overrun

     В асинхронном скрипте Engine стартует новый поток и выполняет скрипт в нем. Следует обратить внимание на что:

      1.  Движок не контролирует выполнение скрипта в выделенном потоке.

      2. Когда скрипт запущен, движок продолжает выполнение следующего скрипта или задачи, движок не ждет пока скрипт выполнится, чтобы двигаться дальше. 

      3. Существует лимит по количеству одновременно выполняемых асинхронных скриптов. Это свойство Maximum Asynchronous Thread Count во вкладке General в редакторе Engine. По умолчанию значение равно 5. Это значение может быть увеличено, но с учетом того, что это приводит к созданию новых потоков. Если достигнуто максимальное значение по потокам, следующий скрипт будет ожидать завершение текущего скрипта.

 

      TRY: CATCH блокировка и сбросы триггеров 

      Любой скрипт, который может привести к исключению должен быть завернут в TRY:CATCH блок.

Пример скрипта без использования TRY:CATCH на рисунке 9.

 


Рисунок 9 - Псевдокод без использования TRY:CATCH блока

Здесь есть тонкие моменты, которые можно пропустить. В этом скрипте устанавливается связь с БД SQL из под AppDomain, проверяется может ли БД использоваться и если нет, пишет ошибку в лог. В конце скрипта происходит сброс триггера.

      В скрипте может произойти зацикливание: предполагается, что объект SQL Connection возвращен в методе GetData(), а метод возвращает NULL. Следующая строка кода никогда не проверяет этого и использует соединение. Так как возвращается значение NULL, возникает исключение NullReferenceException и скрипт останавливает выполнение в точке. На рисунке 10 показано сообщение, которое появится в логах:

 


Рисунок 10 - NullReferenceException

В результате, триггер скрипта cmd_Trigger никогда не возвращается в False и скрипт зацикливается.

Ключевой рекомендацией здесь является включение TRY:CATCH блока в код. Он обрабатывает подобные ситуации и осуществляет очистку (например, избавление от объектов, которые больше не нужны, сброс триггеров скриптов и пр.).

 

      Следует отметить, что хорошим тоном считается:

      1. Использование TRY:CATCH блока для кодов, которые могут вызвать исключения (.NET Framework вызовы). Встроенные функции QuickScript, такие как StringToIntg() и т.п. не вызывают исключений.

      2. Сброс триггера скрипта вначале скрипта, если скрипт предназначен для однократного выполнения. Если предполагается выполнение скрипта каждый цикл сканирования, убедитесь, что триггер скрипта был сброшен в блоке CATCH, чтобы скрипт не зацикливался в случае возникновения ошибки.

      3. Проверка кода. Всегда проверяйте значение NULL при использовании .NET Framework типов в коде. Используйте онлайн документацию, чтобы проверить может ли конкретный метод вызывать исключение и при каких обстоятельствах.

      4. Очищение объектов в блоке CATCH при необходимости. Некоторые объекты должны быть освобождены после завершения работы, обычно путем вызова метода Dispose() этого объекта. Поскольку выполнение основной части скрипта останавливается при возникновении исключения и переходит к блоку CATCH, очистку объектов можно случайно пропустить.

      5. Использование нескольких блоков TRY:CATCH для сложных сценариев. Это позволяет лучше контролировать ход выполнения скрипта (только той части скрипта, которая невыполнима) и обеспечивает более интуитивный поиск и устранение неисправностей.

      Следуя приведенным выше пунктам, ранее упомянутый код можно переписать как показано на рисунке 11:

 


Рисунок 11 - Псевдокод с использованием TRY:CATCH блока


      Косвенные ссылки (Indirect References)

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

      Оценим эту нагрузку в контексте скрипта, в котором используется одна косвенная ссылка на локальный атрибут (рисунок 12).




Рисунок 12 - Пример ссылки Indirect


      В таблице 1 показано общее количество тактов (TotalTicks) за три выполнения вышеуказанного скрипта:
Таблица 1 - Выполнения скрипта в ссылкой Indirect

 

      Ниже тот же сценарий, но с использованием прямой ссылки на рисунке 13.

 


Рисунок 13 - Пример с прямой ссылкой

В таблице 2 показано общее количество тактов (TotalTicks) для сценария с прямой ссылкой.

Таблица 2 - Выполнения скрипта с прямой ссылкой


      Как видно из таблиц, прямые ссылки в ~ 19 раз быстрее, чем косвенные (Indirect). Оба скрипта выполняются менее чем за 1 мс. Однако, как показывает этот простой пример, прямая ссылка масштабируется лучше, чем косвенная.

 

      Перегрузка

      Скан период и период проверки (Scan Period & Checkpoint Period)


      Значение скан периода движка по умолчанию 500 мс. Необходимо оценить значение скан периода. Оно будет зависеть от количества объектов на движке и от того, какие это объекты. Не существует жестких требований по количеству объектов на движке и определение этого количества часто требует тестирования. Увеличивая значение скан периода, вы уже можете предотвратить состояние переполнения движка.

      Если приложение работает медленно, можно предположить, то уменьшение скан периода движка решит проблему. Однако это не так, так как вы предоставляете движку меньше времени для выполнения всей рабочей нагрузки перед следующим циклом сканирования. И если движок еле справляется с нагрузкой за 500 мс, изменение цикла сканирования до 200 мс только ухудшит положение.

      Когда вы видите медленный обмен с полевыми устройствами, рассмотрите лучше изменение прерываний чтения/записи (read/write interrupts), чем уменьшение скан периода для повышения скорости связи с полевыми устройствами. Прерывания чтения/записи возникают в точках цикла сканирования и могут увеличить общую скорость обмена данными. 

      Значения скан периодов для движков на одной и той же платформе должны быть разными. Эта практика не позволяет движкам запускать выполнение одновременно. 

      Значение Checkpoint period связано со скан периодом. Это значение задается во вкладке General редактора Engine объекта (рисунок 14).

 


Рисунок 14 - Конфигурирование Checkpoint period

 

      Процедура Checkpoint в Application Server — это процесс сохранения Runtime значений на диск. Это обеспечивает сохранение последних данных сервера приложения в случае потери связи источниками данных или передеплоя объектов. По умолчанию значение Checkpoint period равно 10 000 мс. Этот параметр не должен быть равен 0, в этом случае, сохранение данных будет выполняться при каждом цикле сканирования. Это необоснованно увеличит нагрузку на Engine. Это значение должно быть правильно установлено, так как значение по умолчанию может быть недостаточным для движков с большой нагрузкой. 


      Частота выполнения скриптов

      Частота выполнения скриптов — это важный момент в управлении нагрузкой движка. Выполнение любого скрипта требует ресурсов (ЦП, ОЗУ и т. д.). Довольно безобидный скрипт, запускаемый каждые 5 секунд, может вызвать проблемы с движком.

      Задайте себе несколько вопросов, чтобы исключить эти проблемы:

1.     Должен ли скрипт выполняться периодически? Может ли он выполняться по событию?

2.     Если он должен быть периодическим, должен ли он выполняться каждые N секунд. Можно ли увеличить эту частоту?

3.     Должен ли каждый раз выполняться весь скрипт? Могут ли каждый раз выполняться только части скрипта? Сокращение времени выполнения скрипта снижает нагрузку на движок.

4.     Подумайте, где работает скрипт. Если этот сценарий является частью базового шаблона и запускается во всех производных экземплярах, обоснованно ли это? Можно ли вместо этого запустить скрипт только в требуемых объектах.

 

      Окружение (Enviroment)


      Строго рекомендуется корректно рассчитывать размер серверов приложений (Application Object Servers - AOS). Эта информация дана в документе System Platform Installation Guide

      Рекомендуется заложить 25% для собственных нужд ОС. Недостаточное количество системных ресурсов может быть причиной переполнения движка. Один Engine выполняется на одном ядре процессора. Соотношение движков и ядер 2:1 является максимально рекомендуемым (если включена гиперпоточноть), хотя это зависит от того, насколько сильно загружен каждый движок. Здесь ключевое значение имеет тестирование.

      Следует исключить влияние стороннего ПО на систему. Для антивирусного ПО должны быть настроены исключения.

Примечание. Если вы используете нестандартные пути сохранения/пересылки или месторасположение для файлов Checkpoint, их также следует исключить из сканирования.

      Не рекомендуется создавать резервные копии узлов AOS, это может влиять на систему. Резервные копии используют технологию Volume Shadow copy Service (VSS). Application Server не поддерживает VSS и это может привести к нестабильности и переполнению движков. 

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

 

      Виртуализация

      Нередко можно увидеть перегруженные системы при использовании гипервизора (VMWare, HyperV и т. д.). Настройка виртуального кластера, как правило, передается ИТ-отделам для настройки.

Очень легко попасть в ловушку: "«Ой, машина работает вяло, я просто добавлю больше vCPU (ядер) в систему»"

      Причины, по которым возникает эта мысль:

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

      2. Простота изменения конфигурации машины в гипервизоре: очень просто добавить vCPU, RAM и т.д.

      Все не так, как кажется: виртуальный процессор (vCPU) — это не ядро, это поток и он не сопоставляется напрямую с физическим процессором (pCPU). Добавление дополнительных виртуальных ЦП к виртуальной машине (ВМ) не обязательно улучшит ее работу, может даже ухудшить ее работу.

      vCPU  это программное представление потока, которое можно использовать для выполнения команд на CPU. Это не представление физического ядра на ЦП. Это еще больше усиливается технологией Hyper-Threading. Технологию Hyper-Threading можно использовать для увеличения количества ядер, но она влияет на производительность. 8-ядерный ЦП будет работать лучше, чем 4-ядерный ЦП с технологией Hyper-Threading.

      Рассмотрим следующий пример показанный на риснке 15:



Рисунок 15 - Единая очередь

где круг синего цвета — это покупатель в очереди, оранжевого - кассир.

      В первом сценарии доступен только один кассир, чтобы расплатиться за покупки. 

      Как можно ускорить процесс? Добавить больше контрольно-пропускных пунктов (рисунок 16)!

 



Рисунок 16 - Несколько Очередей - увеличенное время ожидания

, где круг синего цвета — это покупатель в очереди, оранжевого - кассир.

Теперь есть 4 контрольно-пропускных пункта и кассир вынужден бегать от одной кассы к другой, что приводит к увеличению времени ожидания.

      Здесь:

       ·       Контрольно-пропускной пункт - это поток CPU (vCPU).

·       Кассир - это ядро CPU (pCPU).

·       Люди, ожидающие оплаты - это инструкции для выполнения на процессоре (CPU).

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

      Для критически важных машин соотношение vCPU:pCPU должно быть 1:1,25 или 1:1. Это можно установить в гипервизоре статически в конфигурации виртуальной машины, либо динамически через DRS :

https://www.vmware.com/uk/products/vsphere/drs-dpm.html .

 

      Атрибуты для мониторинга состояния движка (Engine)

      Атрибуты для мониторинга состояния движка можно отслеживать с помощью ObjectViewer, чтобы понять, как работает каждый из ваших Application Engine:

·       Engine.AsyncScriptsWaitingCnt - общее количество асинхронных скриптов, которые в настоящее время поставлены в очередь для выполнения на объекте, но еще не выполняются, поскольку они ожидают свободного потока.

·       Engine.AsyncScriptThreadMax - максимальное количество потоков асинхронного скрипта, которые могут выполняться одновременно.

      ·        Engine.Folding.Conditionзначение TRUE, если в данный момент активна функция свертывания   обмена сообщениями, что означает, что этот объект перегружен с точки зрения пропускной способности. Сворачивание происходит, когда объект обнаруживает, что целевой объект получает больше данных, чем он может обработать своевременно.

·       Scheduler.ExecutionTimeAvg - среднее время в миллисекундах, необходимое для выполнения всех объектов за цикл.

·       Scheduler.ScanCyclesCnt - количество циклов выполнения с момента последнего сброса статистики.

·       Scheduler.ScanOverrunsCnt - количество циклов в Overrun с момента последнего сброса статистики.

·       Scheduler.ScanOverrunsConsecCnt - количество последовательных Overrun циклов сканирования. Это значение увеличивается на 1, когда происходит переполнение сканирования. Это значение устанавливается равным 0, когда объект выходит из сканирования.

·       Scheduler.TimeIdleAvg - среднее время в миллисекундах за период сканирования, в течение которого объект простаивает.