「艦これ」といえばDMMのゲームと言われるほどにまでなりましたが、「城コレ」が今度は控えているようです。約30万人の方が事前登録してくださったという嬉しい悲鳴がところどころで聞かれます。そしてツチノコ部隊も全体の安定稼働へ向けて色々と対応しています。ところで、こういうオンラインゲームは、可愛いキャラクターもさることながら、ブラウザの裏でインターネット越しに必死に動いている「黒子」のようなものたちがいます。いわゆる「ゲームサーバ」と呼ばれるものです。
ゲームサーバは、その基本構成としておおまかに「アプリ(フロント)サーバ」と「データベース(バックエンド)サーバ」に分かれます。今日はその中でも「データベースサーバ」についてのお話です。
DMMでは大抵のデータベースサーバにLinuxとMySQLを使っています。そのデータベースエンジンにInnoDBというのがありまして、設定項目としてメモリ上にバッファをするという設定を行うことができます。
innodb_buffer_pool_size = 100G
例えば、メモリを128GB搭載したサーバでバッファ用に100GBのメモリを割り当てるとします。さて、実際のメモリの割当はどのようになるでしょうか。最近のXeonを搭載したサーバとして考えてみます。実はCPUの個数により、そのメモリの割り当てられ方が変わってきます。というのも、昔と違い今はメモリのコントローラがCPUに内蔵されているからなのです。
- シングルCPU構成の場合
すべてのメモリがCPUと直接つながっているので、特に気にする事はありません。
- マルチCPU構成の場合
CPU0とCPU1…とにそれぞれメモリがつながっています。例えば先ほど128GBとしたメモリが2CPU環境にあるとしましょう。そうするとCPUにごとに64GBのメモリがつながっている事になります。標準ではLinuxはプロセス毎にそのプロセスが動いているCPUにつながったメモリを利用しようとします。つまり、先ほどの100GB割り当てが、MySQLのプロセスが動いている片方のCPUに偏ってしまうのです。その結果、片方のメモリがMySQLに食いつぶされ、そこにもともといたデータがSWAPに吐き出されます。もう片方のCPUにつながっているメモリに空きがあっても、です。
少し詳しい話にしましょう。最近の共有型のマルチプロセッサシステムアーキテクチャは、NUMA(Non-Uniformed Memory Access)というものです。これは、CPUとメモリのまとまりを「ノード」と呼びます。先の例では、CPUと64GB分のメモリのまとまりが”ノード”です。
Linux OS上でプロセスが起動すると、そのプロセスはCPUに割り当てられます。CPUとメモリがまとまっているので、プロセス自体のデータは同じNUMAノードのメモリに置かれます。
先の例の2CPUの環境MySQLが起動したあとメインスレッドがCPU0に割り当てられたとして、そのCPUにぶら下がる64GBのメモリにMySQLのデータが置かれます。しかし、バッファデータのサイズだけで100GBあるので、NUMAノードの64GBをいずれ食いつぶします。そうなると、同じNUMAノード上にある他プロセスのデータがメモリから溢れます。他NUMAノードのメモリはデフォルトではOSが割り当てをしない状態になっているため、あふれたものはスワップへ送られます。もともと64GBのメモリ上にあるものなのでスワップアウトするデータ量も大きくなる傾向があります。なので実際のところスワップが埋まってしまう可能性が非常に高いです。
では、どうすればこの偏りやスワップアウトを抑えることができるのか。
numactl --interleave=all ${CMD}
NUMAをOSからコントロールするnumactlというコマンドがあります。
–interleaveオプションは割り当てるNUMAノードを指定します。上記の例では”all”となっているため、すべてのNUMAノードに割り当てを行うことを意味します。
cat /proc/`pidof ${PROCESS}`/numa_maps
こちらのコマンドで、NUMAに対するプロセスの状況が見られます。
とはいえ、見るのは大変なので、下記のページを参考に、Perlスクリプトを使ってみましょう。
http://blog.jcole.us/2010/09/28/mysql-swap-insanity-and-the-numa-architecture/
numa-maps-summary.pl というスクリプトです。って、実はこれ2010年の記事なんですよね。知りませんでした。
実際にスワップアウトしてスワップを喰い潰してしまったサーバがこちら
[root@srv3053 ~]# perl numa-maps-summary.pl < /proc/`pidof mysqld`/numa_maps;free
N0 : 16132628 ( 61.53 GB)
N1 : 12288264 ( 46.88 GB)
active : 26922666 (102.70 GB)
anon : 28420892 (108.42 GB)
dirty : 28420892 (108.42 GB)
mapmax : 157 ( 0.00 GB)
mapped : 1373 ( 0.01 GB)
total used free shared buffers cached
Mem: 132112264 126602604 5509660 0 142040 10854736
-/+ buffers/cache: 115605828 16506436
Swap: 2097136 2097136 0
同じサーバで、numactlを設定したのがこちら
[root@srv3053 ~]# perl numa-maps-summary.pl < /proc/`pidof mysqld`/numa_maps;free
N0 : 14679063 ( 56.00 GB)
N1 : 13763886 ( 52.51 GB)
active : 26950881 (102.81 GB)
anon : 28441601 (108.50 GB)
dirty : 28441601 (108.50 GB)
mapmax : 158 ( 0.00 GB)
mapped : 1410 ( 0.01 GB)
total used free shared buffers cached
Mem: 132112264 125522400 6589864 0 141764 9805148
-/+ buffers/cache: 115575488 16536776
Swap: 2097136 0 2097136
設定前はNUMAノード分の64GBに近く偏っていたものが、設定後は分散されているのがわかります。
上記のようにプロセスがどかっと大きなメモリを割り当てることでスワップアウトしてしまう現象を”Swap Insanity”といいます。
そして先のブログ記事にもありますが、mysqldのinitスクリプトを改造して、numactlを埋め込んでしまうという手を使うことで、回避ができます。