Hyper-V и NUMA. Балансировка виртуальных машин в системах NUMA

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

Использование многопроцессорных и многоядерных систем подразумевает еще больше проблем с доступом к памяти. Разделение шины памяти между несколькими процессорами еще сильнее снижает скорость получения данных из памяти. Кроме того, возникают новые сложности, связанные с обеспечением когерентности кэша процессоров. С инженерной точки зрения одним из простейших подходов к ускорению пропускной способности доступа к памяти в многопроцессорных системах является технология NUMA («Non-Uniform Memory Access» или «Non-Uniform Memory Architecture»). В этом случае у каждого процессора есть «своя часть» оперативной памяти, к которой он получает доступ через выделенный контроллер. При этом к памяти, привязанной к другим процессорам, можно получить доступ через «чужой» контроллер. Естественно, скорость получения данных при обращении к «чужой» памяти будет значительно ниже, чем при обращении к «своей». Накладные расходы, связанные с обеспечением когерентности кэшей, также повышают задержку при доступе к памяти другого процессора.

Современные серверные платформы на базе процессоров Intel и AMD используют NUMA с помощью технологий QPI и HyperTransport соответственно. Кроме того, существует технология IBM Multinode, которая позволяет задействовать NUMA на более высоком уровне путем объединения нескольких серверов (уже использующих NUMA) на коммутаторе. Примером систем, реализующих NUMA, являются HP ProLiant DL785 G5/G6 и IBM x3850 M2.

Серверные операционные системы, поддерживающие NUMA, — такие, как Windows Sever 2008 и Windows Server 2008 R2 — должны оптимизировать использование процессорами памяти, доступной для них наиболее быстро. Что происходит в данном случае с виртуальными машинами?

Мы знаем, что Hyper-V не использует жесткую привязку к физическому процессору (Processor affinity). Виртуальная машина использует те логические процессоры, которые в настоящее время свободны и предоставляются гипервизору постановщиком ресурсов (Scheduler). Но при этом каждая виртуальная машина имеет специальную характеристику для задания «узла NUMA». Она позволяет гипервизору указать, с каких процессоров и с какой шины памяти будут использоваться ресурсы для данной ВМ.

Этот параметр задаётся при каждом включении виртуальной машины, получая ссылку на наиболее свободный на момет её запуска узел. В случае недостатка ресурсов в «своём» узле NUMA, гипервизор начинает предоставлять виртуальной машине ресурсы другого узла. Поэтому технически могут возникать ситуации, в которых виртуальные машины используют ресурсы нескольких узлов NUMA одновременно. Чтобы предотвратить такое неэффективное использование ресурсов, администратор может в явном виде задать предпочитаемый узел NUMA для каждой виртуальной машины. При этом вы можете в любой момент проверить, какой узел использует виртуальная машина.

В моём примере созданы четыре виртуальным машины.

  • Test-1 с двумя виртуальными процессорами и 3 ГБ памяти;
  • Test-2 с четырьмя виртуальными процессорами и 4 ГБ памяти;
  • Test-3 с двумя виртуальными процессорами и 3 ГБ памяти;
  • Test-4 с одним виртуальным процессором и 1 ГБ памяти.

Если я запущу все виртуальные машины и открою Performance Monitor, то в строке «Preferred NUMA Node Index» я увижу, на каком из узлов NUMA запущена виртуальная машина. В нашем случае гипервизор сбалансировано распределил четыре виртуальных машины между двумя узлами NUMA.

Узлы NUMA нумеруются с нуля, две виртуальных машины разместились на узле 0, а две на узле 1. Причем используемое количество логических процессоров и памяти суммарно на каждом из узлов NUMA максимально схоже — так работает балансировка.

Теперь предположим, что виртуальная машина Test-2, которой мы предоставили наибольшее количество процессоров и памяти, в действительности требует большей производительности чем остальные. И что мы бы хотели разместить её на отдельном узле NUMA, оставив все прочием машины на другом.

Мой коллега из Windows Perfomance Team разместил в своём блоге сценарий Powershell, который позволяет явно указывать узел NUMA для виртуальной машины. На всякий случай также прикладываю файл со сценарием к этой статье. Используя этот сценарий, я в явном виде укажу виртуальной машине Test-2 всегда запускаться на узле 0: numa.ps1 /set test-2 0. А виртуальные машины Test-1, Test-3 и Test-4 будут запускаться на узле 1: numa.ps1 /set test-1 1, numa.ps1 /set test-3 1, numa.ps1 /set test-4 1. Изменение привязки к узлу NUMA работает только после включения виртуальной машины. Если виртуальная машина была включена, вам потребуется перезапустить её.

Теперь виртуальная машина Test-2 исключительно занимает узел 0. Заданное предпочтение узла NUMA совместимо с Live Migration и Quick Migration. Переместив такую виртуальную машину на другой узел кластера, вы увидите, что привязка к конкретному узлу NUMA «переехала» вместе с машиной.

К описанной настройке следует подходить осторожно. Надо понимать, что если вы жёстко задали всем виртуальным машинам использовать конкретный узел NUMA, и они используют всю его память или полностью загружают ресурсы процессора, то уже никакой механизм автоматически не перенесет эти машины на другой узел NUMA.

numa.ps1