【入門編】 NUMA(Non-Uniform Memory Access)アーキテクチャと仮想マシン(vCPU/メモリ)配置のパフォーマンス最適化 – クラウドインフラと仮想化ネットワーク実践ガイド

こんにちは!仮想化技術やクラウドインフラの世界へようこそ。第一線でシステムを支えるSREの私のブログへお越しいただき、ありがとうございます!

突然ですが、みなさんは「仮想マシン(VM)のスペックを上げたのに、なぜか思ったように処理速度が上がらない……」という不思議な現象に直面したことはありませんか?

「CPUもメモリも十分に割り当てたはずなのに、どうして?」と頭を抱えてしまうインフラエンジニアは、実はとても多いのです。

その謎を解く鍵こそが、今回ご紹介する「NUMA(Non-Uniform Memory Access:ヌーマ)」というアーキテクチャです。

一見すると難しそうな英語の並びですが、心配いりません!今回は、物理サーバーの中で起きている「データの郵便配達」のような仕組みに例えながら、初心者の方でも一歩ずつ、直感的に理解できるように優しく紐解いていきます。

物理的なハードウェアの仕組みを知ることは、クラウド(AWSやGCPなど)を使いこなす上でも非常に強力な武器になります。それでは、一緒に楽しく学んでいきましょう!

—

1. NUMAとは?現実世界の「オフィスのレイアウト」で例えてみよう

まずは「NUMAとは何か?」を、私たちの身近な世界に例えて考えてみましょう。

物理サーバーの中には、頭脳である「CPU(ソケット)」と、作業机である「メモリ」が搭載されています。最近の高性能なサーバーには、このCPUが2つ以上(マルチソケット)搭載されているのが一般的です。

ここで、サーバーの中の構造を「2つの部署があるオフィス」に例えてみます。

  • 開発部(NUMAノード0):
  • Aさん(CPU 0)が座っている。
  • Aさんの目の前には、専用の「書類キャビネット A(メモリ 0)」がある。
  • 営業部(NUMAノード1):
  • Bさん(CPU 1)が座っている。
  • Bさんの目の前には、専用の「書類キャビネット B(メモリ 1)」がある。

そして、開発部と営業部の間は、長い廊下(インターコネクトバス)で結ばれています。

[ NUMAノード0 (開発部) ]              [ NUMAノード1 (営業部) ]
+---------------------+              +---------------------+
|  [ CPU 0 (Aさん) ]   |              |  [ CPU 1 (Bさん) ]   |
|         |           |              |         |           |
|  [ メモリ 0 (棚A) ]  |<==== 廊下 ====>|  [ メモリ 1 (棚B) ]  |
+---------------------+   (長い通路)  +---------------------+

「ローカルアクセス」は一瞬!

開発部のAさんが、目の前にある「キャビネットA」から書類を取り出すのは一瞬ですよね。立ち上がる必要すらありません。これを、専門用語で「ローカルメモリアクセス」と呼びます。非常に高速で、遅延(レイテンシ)がほぼありません。

「リモートアクセス」は大仕事……

しかし、もしAさんが「キャビネットB」にある書類を読みたくなったらどうでしょう?
わざわざ席を立ち、長い廊下を渡って営業部まで歩き、書類を取って戻ってこなければなりません。これには時間がかかりますよね。これを「リモートメモリアクセス」と呼びます。

このように、「アクセスするメモリの場所によって、読み書きにかかる時間(速度)が均一ではない(Non-Uniform)仕組み」のことを、NUMA(Non-Uniform Memory Access)と呼ぶのです。

—

2. 仮想マシンの「遠距離恋愛」が引き起こすパフォーマンスの悲劇

では、この仕組みが「仮想マシン(VM)」のパフォーマンスにどう影響するのでしょうか。

仮想化ソフト(ハイパーバイザー)は、物理サーバーのCPUやメモリを切り分けて、仮想のPC(VM)を作ってくれます。

もし、私たちが何も考えずに「CPU:4コア、メモリ:16GB」の仮想マシンを作ったとします。このとき、ハイパーバイザーがうっかり以下のような配置をしてしまったらどうなるでしょう?

  • 仮想マシンの頭脳(vCPU):開発部(NUMAノード0)のCPU 0に配置
  • 仮想マシンの作業スペース(メモリ):営業部(NUMAノード1)のメモリ 1に配置
【悲劇のミスマッチ配置】
仮想マシン「私の頭脳はノード0にあるのに、使っているメモリはノード1にある...」
  ==> データを読み書きするたびに、毎回「長い廊下」を往復することに!

これこそが、物理的な距離が生み出す「遠距離恋愛」状態です。
仮想マシンが何か処理をするたびに、データは毎回、長い廊下(物理バス)を往復しなければなりません。

ネットワークのパケット詰まりのように目に見えるエラーは出ませんが、「なぜかデータベースの応答が数ミリ秒遅い」「Webサイトの表示がもっさりする」といった、原因の特定しづらいサイレントな性能低下を引き起こしてしまうのです。

—

3. 解決策は「同じ部屋に住まわせる」こと(アフィニティ設計)

この悲劇を防ぐための設計手法が、今回のテーマである「NUMAアフィニティ(親和性)設定」です。

アフィニティとは、簡単に言うと「この仮想マシンの頭脳(vCPU)と作業スペース(メモリ)は、必ず同じ部屋(NUMAノード)に配置してくださいね!」という、ハイパーバイザーへの強力なお願い(縛りルール)です。

【理想のNUMAアフィニティ配置】
仮想マシンの vCPU と メモリ を、同じ「NUMAノード0」の中にスッポリ収める。
  ==> データの往復がすべて「同じ部屋の中」で完結するため、爆速になる!

黄金の設計ルール

1. VMのサイズ設計:
1つの仮想マシンに割り当てるCPU数やメモリ容量は、可能な限り「物理サーバーの1つのNUMAノードが持つ上限」を超えないように設計するのが基本です。これを超えてしまうと、どうしても「隣の部屋」のメモリを使わざるを得なくなります。
2. アフィニティの固定:
仮想マシンが物理サーバー内で勝手にあちこち引っ越し(ライブマイグレーションなど)をしないよう、特定の物理NUMAノードに紐付け(ピン留め)します。

—

4. 実践!KVM/LibvirtでのNUMA設定例

ここからは、Linuxでよく使われる仮想化技術である「KVM(Libvirt)」を例に、具体的な設定方法を見ていきましょう。

仮想マシンの構成を定義するXMLファイル(設定シートのようなもの)に、少し設定を加えるだけで、CPUとメモリを同じ「部屋(NUMAノード)」にピタッと固定することができます。

以下の設定例を参考に、どのように記述するのか一歩ずつ見てみましょう!

<!-- 仮想マシンの設定ファイル(XMLの一部) -->
<domain type='kvm'>
  <name>my-optimized-vm</name>
  <memory unit='KiB'>8388608</memory> <!-- 8GBのメモリを割り当て -->
  <vcpu placement='static'>4</vcpu>     <!-- 4つの仮想CPU(vCPU)を割り当て -->

  <!-- CPUとメモリの配置(チューニング設定) -->
  <numatune>
    <!-- 
      メモリの確保ポリシーを設定します。
      nodeset='0' は「物理NUMAノード0」からのみメモリを確保することを指定し、
      mode='strict' は「絶対に他のノードからはメモリを確保しない(厳格に守る)」という意味になります。
    -->
    <memory mode='strict' nodeset='0'/>
  </numatune>

  <cputune>
    <!-- 
      仮想マシンの頭脳(vCPU)を、物理CPUのどこにピン留め(固定)するかを設定します。
      ここでは、物理NUMAノード0に属する物理コア(例:0, 1, 2, 3)に、
      仮想マシンのvCPU 0〜3 をそれぞれ固定しています。
    -->
    <vcpupin vcpu='0' cpuset='0'/>
    <vcpupin vcpu='1' cpuset='1'/>
    <vcpupin vcpu='2' cpuset='2'/>
    <vcpupin vcpu='3' cpuset='3'/>
    
    <!-- ハイパーバイザー自身の処理(エミュレータ)も物理NUMAノード0で動かすように指定します -->
    <emulatorpin cpuset='0-3'/>
  </cputune>

  <!-- (以下、その他の設定が続きます...) -->
</domain>

設定のポイント

  • <memory mode='strict' nodeset='0'/>:

「この仮想マシンのメモリは、絶対に物理NUMAノード0から取ってきてね!」という命令です。

  • <vcpupin vcpu='X' cpuset='Y'/>:

「仮想マシンの頭脳(vCPU X)は、物理CPU Yの上だけで動かしてね!」というピン留め設定です。

このように設定することで、仮想マシンは「物理NUMAノード0」という1つの部屋の中で、誰にも邪魔されずに最速でデータの読み書きができるようになります。

—

5. 現在のサーバー状態をコマンドで確認してみよう!

「自分の管理しているサーバーはどうなっているんだろう?」と気になりますよね。Linuxサーバーであれば、以下のコマンドを使うことで、物理サーバーのNUMA構造を簡単にのぞき見ることができます。

まずは、サーバーにSSHでログインして、以下のコマンドを実行してみましょう。

# NUMAのハードウェア構成を表示するコマンド
numactl --hardware

実行すると、以下のような出力が得られます。

# 出力結果のイメージ
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7
node 0 size: 32768 MB
node 0 free: 12450 MB
node 1 cpus: 8 9 10 11 12 13 14 15
node 1 size: 32768 MB
node 1 free: 8900 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

出力結果の読み解き方

  • available: 2 nodes (0-1):このサーバーには、2つの「部屋(NUMAノード)」があります。
  • node 0 cpus: 0 1 2 ...:物理CPUの「0〜7番」は、ノード0の部屋に座っています。
  • node distances:ここが面白いポイントです!
  • ノード0から自分の目の前(0)へのアクセス速度は 10(基準値)。
  • ノード0から隣の部屋(1)へのアクセス速度は 21。
  • つまり、隣の部屋のメモリにアクセスすると、2倍以上の時間がかかる(遅延が発生する)ことが一目でわかりますね!

—

まとめ:一歩ずつ、ハードウェアに優しいインフラ設計へ

今回は、仮想マシンのパフォーマンスを劇的に引き出す「NUMAアーキテクチャ」と「アフィニティ設計」について、身近な例えを交えて解説しました。

最後に、今回の大切なポイントをおさらいしておきましょう。

1. NUMAとは、CPUとメモリの物理的な距離によってアクセス速度が変わる仕組み。
2. 仮想マシンの頭脳(vCPU)と作業机(メモリ)が異なるNUMAノードにまたがると、「リモートアクセス」による遅延が発生する。
3. 対策として、1つのNUMAノードに収まるサイズでVMを設計し、アフィニティ設定(ピン留め)で同じ部屋に固定する。

クラウドや仮想化の技術は、物理ハードウェアの存在を私たちから隠してくれます。しかし、その「見えない壁」の裏側を知っておくことこそが、トラブルに強い一流のインフラエンジニアになるための秘訣です。

これからも、パケットやデータの気持ちに寄り添いながら、一歩ずつ楽しくインフラの深淵を学んでいきましょう!

何か分からないことがあれば、いつでもコメントで教えてくださいね。それでは、また次回の記事でお会いしましょう!

コメント

タイトルとURLをコピーしました