物理メモリ把握の目的

ミドルウェアの設定ファイルでは、そのミドルウェアが使用できる最大メモリサイズを指定するという場面が良くある。
例えばMySQLでは、データとインデックスをメモリーにキャッシュするためのバッファープールのサイズを決める設定値innodb_buffer_pool_sizeに、サーバの全メモリの50%から70%くらいを割り当てるのが良いとされている。

各プロジェクトでセットアップするMySQL搭載サーバのメモリが全て同じになることはないだろうし、同一プロジェクト内で役割ごとに複数DBがある場合などは、各DBによってメモリが違うこともあるだろう。Master/Slaveでは同一スペックを--少なくともメモリサイズは--用意するのが王道だが、予算の都合でSlaveの方がメモリが少ないこともあるかもしれない。
MySQLの/etc/my.cnfを編集するスクリプトを作成するとして、サーバ上で正確な物理メモリのサイズを取得して、innodb_buffer_pool_sizeに適切な値(今回は62.5%)を設定する方法を考えてみる。

物理メモリの把握方法にはfreeとdmidecodeの2通りが考えられる。

freeで把握する

メモリの状況を把握するfreeコマンドを使用して、物理メモリを把握する方法がまず考えられる。
8GBのメモリが搭載されているとして、MB単位でメモリ状況を表示するためにfree -mを使用し、メモリのトータルサイズが記載されている箇所をawkを使って抜き出してみる。

$ free -m
             total       used       free     shared    buffers     cached
Mem:          7869       6890        978         22        198       5094
-/+ buffers/cache:       1597       6271
Swap:         2099        748       1351
$ free -m | awk '/Mem:/ {printf("%d",$2 / 1000 + 0.5)}' | awk '{printf("%d\n",$1 * 1024)}'
8192

awkでは計算式$2 / 1000 + 0.5を実行しているが、この計算をしている理由は、freeで表示されるtotalの値が搭載したメモリより若干小さく表示されるためである。
total値はOSが認識している物理的なメモリサイズで、RAIDカードやNICなどを装着しているときは、それらのためにメモリが使われるので実際の搭載メモリサイズよりも少なくなる。
ミドルウェアに設定するメモリサイズを、OSが認識している物理メモリサイズから計算したものにした方が、正確性の面ではいいのかもしれないが、中途半端な値になり運用で把握しづらくなるため通常は搭載している物理メモリの値から計算したものにするだろう。
MB単位で出力された値を1000で割って0.5を足すことでGBの1桁目が繰り上がって整数部が物理メモリの値と同じになる。%dで整数部分のみ表示しているので、GB単位で物理メモリの正確な値が取得できた。これに最後、1024をかけて再度MB単位に戻している。

今回は物理メモリの62.5%を求め、my.cnfに設定するため、以下のようになる。

# 62.5%の値を確認する
$ free -m | awk '/Mem:/ {printf("%d",$2 / 1000 + 0.5)}' | awk '{printf("%d\n",$1 * 1024 * 0.625)}'
5120
# my.cnfをsedで書き換えた値を確認する
$ mem_computed=`free -m | awk '/Mem:/ {printf("%d",$2 / 1000 + 0.5)}' | awk '{printf("%d\n",$1 * 1024 * 0.625)}'` &&
  sed -n s/innodb_buffer_pool_size.*/innodb_buffer_pool_size=${mem_computed}M/p /etc/my.cnf
innodb_buffer_pool_size=5120M
# my.cnfをsedで書き換える
$ mem_computed=`free -m | awk '/Mem:/ {printf("%d",$2 / 1000 + 0.5)}' | awk '{printf("%d\n",$1 * 1024 * 0.625)}'` &&
  sed -i s/innodb_buffer_pool_size.*/innodb_buffer_pool_size=${mem_computed}M/ /etc/my.cnf

freeは有名なコマンドだし計算式も簡単で手軽だが、物理メモリの量がかなり小さい場合でも計算結果が常に正常な値を示してくれるか不安になる。
次は計算なしに物理メモリを取得できるdmidecodeも利用したい。

dmidecodeで把握する

ハードウェアの詳細情報が参照できるdmidecodeでメモリの情報も見ることができる。
1枚しかメモリが刺さっていなければ、以下で問題ない。

$ dmidecode -t memory | awk '$1=="Size:" && $3=="MB" {print $2}'
8192

複数枚刺さっている可能性もあるので、awkで合計を計算する。
※今回は1枚しか刺さっていないので、出力結果は変わらない。

$ dmidecode -t memory | awk '$1=="Size:" && $3=="MB" {sum+=$2} END{print sum}'
8192

freeのときと同じロジックでdmidecodeを使ってmy.cnfを書き換える。

# my.cnfをsedで書き換えた値を確認する
$ mem_computed=`dmidecode -t memory | awk '$1=="Size:" && $3=="MB" {sum+=$2} END{printf("%d",sum *0.625)}'` &&
  sed -n s/innodb_buffer_pool_size.*/innodb_buffer_pool_size=${mem_computed}M/p /etc/my.cnf
innodb_buffer_pool_size=5120M
# my.cnfをsedで書き換える
$ mem_computed=`dmidecode -t memory | awk '$1=="Size:" && $3=="MB" {sum+=$2} END{printf("%d",sum *0.625)}'` &&
  sed -i s/innodb_buffer_pool_size.*/innodb_buffer_pool_size=${mem_computed}M/ /etc/my.cnf

複数台のサーバをセットアップする

freeもしくはdmidecodeで設定ファイルを書き換える方法を説明してきたが、実践としてシェルで複数台のサーバを対象に書き換えてみる。

for i in `cat db_list.txt`
do
  echo "==========$i=========="
  mem_computed=`ssh $i "dmidecode -t memory" | awk '$1=="Size:" && $3=="MB" {sum+=$2} END{printf("%d", sum * 0.625)}'`
  echo "${mem=computed} MB"
  ssh $i "sed -i s/innodb_buffer_pool_size.*/innodb_buffer_pool_size=${mem_computed}M/ /etc/my.cnf"
done