Docker のコンテナ間通信のベンチマーク

Docker 1.7.0 でヘアピンNAT対応が入った。そこで、link の代わりに Publish を使った場合にどの程度パフォーマンスが落ちるのかベンチマークしてみる。

まず、Docker の link機能を使う際には例えば link 元コンテナが再起動した場合に破綻するなど扱いが難しい面がいくつかある。そこで、代わりに Publish でコンテナ間を繋ぐという方法が考えられるが、この方法はポートの重複が許容されないなどとともに転送用の Proxy ソフトウェア(docker-proxy)を経由するのでパフォーマンスが低かった。 今回のアップデートで daemon 起動時のオプションに--userland-proxy(デフォルトは true なので今までどおり)が追加され、上記転送用 Proxy ソフトウェアを利用するか、iptables + route_localnet を使うか選択できるようになった。後者の場合、転送処理は kernel 空間で完結し TCP セッションも管理しなくていいのでパフォーマンスが上がるだろうと考えている。

構成

今回の memcached コンテナに対して、memslapベンチマークを行うが、その際の構成は次のようになる。

link の場合

f:id:mapk0y:20150620122637p:plain

--userland-proxy=true の場合

f:id:mapk0y:20150620122646p:plain

--userland-proxy=false の場合(Ver 1.7.0 以降可能)

f:id:mapk0y:20150620122651p:plain

ベンチマーク

ベンチマーク環境

サバフェス の景品で頂いた Intel NUC。静かで小さくて使いやすい。ありがとうございます!!!!!

ベンチマーク方法

memcached のコンテナに対して、memslap の入ったコンテナでアタックをしてみる。

実行するコマンドは以下。

  • memched コンテナの起動コマンド
$ docker run -d -p 11211:11211 memcached
  • memslap コンテナの起動コマンド
$ # docker ホスト経由
$ docker run --rm mapk0y/memslap \
  --servers=172.17.42.1:11211
  --flush --test set
  --concurrency=4
  --execute-number=100000
$ # link 経由の場合
$ docker run --rm --link=memcached mapk0y/memslap
  --servers=memcached:11211
  --flush --test set
  --concurrency=4
  --execute-number=100000

172.17.42.1 は docker0 のデフォルトのIPアドレス。これはホストマシンのホスト名や外側のIPアドレスなどでも良いが、link の代わりにこれらをエンドポイントして使う。

ベンチマーク結果

各1回だけ

タイプ 結果
link 4.466 sec
userland-proxy=true(今まで) 8.481 sec
userland-proxy=false 4.690 sec

期待通り今までより性能の劣化が圧倒的に少ない。また、今回ベンチマークは取っていないがコンテナ間だけでなくホストから 127.0.0.1:11211 などへの通信のような場合も性能の劣化が少なくなるはず。

ただし、kernel のバージョン(3.5以前?)によっては設定できないので注意が必要。

サバフェスに参加してきました

先日行われた「サバフェス!」に参加してきました。

かなり遅くなってしまいましたが、その際に行ったいくつかの施策を紹介しようと思います。

サバフェスとは

ioDrive2搭載のベアメタルサーバ上でチューニングを頑張って、だれが MySQLベンチマークスコアを一番出せるか競うイベントです。

公式ページ
募集ページ

IDCフロンティアさんが主催で、SanDisk さん(ioDrive の製造/販売元)と mackerelパソナテックさんの協賛で 3/5 〜 3/22 まで行われました。

サバフェスの環境

用意していただいたチューニングを行うサーバは以下の様な感じでした

CPU Intel(R) Xeon E5-2650 (8core/16thread 2.0 GHz) x 2
Memory 32 G
HDD OS boot用
ioDrive HP 365GB MLC PCIe ioDrive2
OS CentOS 6 系だったはず
kernel 2.6.32-358.el6.x86_64

テスト内容

tpcc-mysql ベンチマークを Remote から実施します。

その結果、1分あたりのトランザクション数がスコアとなります。

チューニング

ここから本題のチューニングのお話になります。

MySQL のチューニングについて

私は、普段は MySQL 5.1 で HDD という古い環境をメインで見ています。 ですので、まずは知識として知っている範囲で SSD に合わせた設定を行い、細かなところは実際にベンチマークを実施してチューニングをしていきました。

MySQL のバージョン

今回 MySQL のバージョンはレギュレーションで以下のように決められています。

・DBはMySQL, MariaDB, PerconaDBのstable版のいずれかとします。
・MySQLのバージョンは5.5か5.6とします。
・MariaDBのバージョンは5.5か10.0とします。
・PerconaDBのバージョンは5.5か5.6とします。
・DBのストレージエンジンはinnodbとその互換を利用するものとします。

どれを使うかは少し悩みました。 もともと、SSD 向けパラーメータが多い 5.6 系で、せっかくなので普段使っていない MariaDB か PerconaServer を使う予定にはしていました。 最終的には MySQL Performance Blog 含めきちんとした情報が多かった PerconaSerer 5.6 を採用しました。

MySQL のパラメータ調整

今回のイベントのチューニングにおいて大きなウェイトを占めると思われるところです。

ある程度スコアが出た my.cnf からパラメータ一つづつ変更してベンチマークを取って有効なパラメータを探していきました。

最終的な my.cnf はこちらになりますが、各パラメータでベンチマークを取って効果があったものとなかったものを紹介しようと思います。

効果のあったパラメータ

ioDrive ぐらい早ければ、innodb_buffer_pool が少なくてもどうにかなるかと期待していましたが、現実はそれほど甘くありませんでした。 innodb_buffer_pool を半分にすると、スコアは半分以下となり innodb_buffer_pool がどれだけ重要かを改めて感じました。

query_cache_size は 128Mでも1G でもヒット率は 20%程度で変わらず、スコアは query_cache_size=0 の場合10%前後良い値が出ました。

innodb_lru_scan_depthinnodb_buffer_pool_instances に関しては、少し面白いことに以下のような関係でスコアが変動しました。

lru_scan_depth buffer_pool_instances Score
500 8 45619
1000 8 46954
1000 16 48428
1500 8 47186
2000 12 48678
3000 6 48727
2500 4 47765
2500 8 48913
2500 24 46774
4000 4 48753

最終的には innodb_lru_scan_depth=2500innodb_buffer_pool_instances=8 としましたがもう少し試す余地がありそうです。

効果のなかったパラメータ

innodb_io_capacityinnodb_read_io_threads/innodb_write_io_threads が効果がなかったのは、最初からそれなりに大きい値を設定していたためだと考えています(裏付けはとっていません)。

ウォームアップ

ベンチマークの立ち上がりでスコアを稼ぐために、ウォームアップを行っていました。 これは上位の方々はみんな実施されていたようで、本番環境などでもメンテナンス後などによくやることです。

ウォームアップの内容は多くの場合読まれる可能性のあるテーブルを事前に select しておくという方法だと思います。 私もはじめはその方法でしたが、最終的には趣向を変えて innodb_buffer_pool_dump を利用してみました。

innodb_buffer_pool_dumpinnodb_buffer_pool の内容をファイルに書き出しておき、起動時にそれを読み込むことでウォームアップを行えるものです。 この時、ファイルに書き出される内容は実際の innodb_buffer_pool の内容そのものではなく、tablespace ID と page NO となります。 そのため dump した時と、読み込むときのテーブルのデータが異なっていてもなんとかなるのではと考えて試してみましたが、特にエラーもなくうまく動きましたし、スコアも少し上がりました。

参考サイト: 日々の覚書: InnoDB buffer pool dumpで遊ぶ

OS 側のチューニング

個人的にはメインのチューニングはこちらです。

NUMA

NUMA構成で、MySQL を動かす場合メモリの利用が偏ってしまうのでメモリの確保ポリシーを interleave にすべきのようですが、今回は以下のようにそれぞれのCPUに近いメモリ量が偏っていたのでデフォルト値から変更はしませんでした。

Team-K:~# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
node 0 size: 24541 MB
node 0 free: 20366 MB
node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
node 1 size: 8191 MB
node 1 free: 6072 MB
node distances:
node   0   1
  0:  10  20
  1:  20  10

ベンチマークしてみた限りでも特に差はありませんでした。

ちなみに、yum でインストールした PerconaServer 5.6 の mysqld_safe では numa_interleave=0 となっており、こちらを numa_interleave=1 に修正すれば起動時に numactl --interleave=all を実行してくれます。 実際に有効になっているかどうかは以下の方法で確認が出来ます。

  • numa_interleave=0 の場合は 2つめのフィールドが default となる
# head -1 /proc/$(pidof mysqld)/numa_maps
00400000 default file=/usr/sbin/mysqld mapped=848 N0=848
  • numa_interleave=1 の場合は 2つめのフィールドが interleave となる
# head -1 /proc/$(pidof mysqld)/numa_maps
00400000 interleave:0 file=/usr/sbin/mysqld mapped=848 active=1 N0=848

参考サイト: MySQL と NUMA アーキテクチャと Swap Insanity | COLOPL Engineers' Blog | 株式会社コロプラ【スマートフォンゲーム&位置ゲー】

FileSystem

ファイルシステムに関して当初は、MySQLベンチマークなどでよく使われている xfs を使うほうが良いのではないかと考えて、一度 xfs に変更して(noatime,nobarrier)ベンチマークをしてみました。 しかし、効果はなく逆に ext4 の7割ぐらいになったと記憶しています。

そのため最終的には ext4 を採用し以下のマウントオプションに設定しました。 ただし、各パラメータを変更してベンチマークは行っていないのでパラメータ自体にどこまで効果があったかは不明です。

# mount -o data=writeback,noatime,discard,barrier=0 -t ext4 /dev/fioa /fioa

IRQ

初期状態では、割り込みの多い ioDrive と NIC の割り込みの処理がすべて CPU0 で行われるようになっていました。 この状態では、CPU0 がネックになる可能性が高くなります。

また、この状態を回避し、各CPUへの再割当てを行ってくれるはずの irqbalance も動いていましたがうまく機能していないようでした(この理由は調べていません)。 そのため、smp_affinity を利用し手動で割り当てを行いました。 最初は ioDrive を CPU1 にして、NIC(queueが5つ)には CPU2-6 を割り当てしましたが、top をみると以下(当時の状況を再現)のように妙なことになっていました。

Tasks: 843 total,   1 running, 842 sleeping,   0 stopped,   0 zombie
Cpu0  : 48.1%us,  7.5%sy,  0.0%ni, 23.1%id, 21.4%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  : 40.6%us, 12.0%sy,  0.0%ni, 35.3%id, 12.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 35.3%us,  7.1%sy,  0.0%ni, 41.7%id, 15.9%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  : 32.0%us,  6.4%sy,  0.0%ni, 49.8%id, 11.8%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  : 28.2%us,  5.4%sy,  0.0%ni, 53.1%id, 11.6%wa,  0.0%hi,  1.7%si,  0.0%st
Cpu5  : 24.2%us,  3.4%sy,  0.0%ni, 62.3%id, 10.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  : 63.3%us,  9.4%sy,  0.0%ni, 10.1%id, 11.4%wa,  0.0%hi,  5.7%si,  0.0%st
Cpu7  : 18.6%us,  3.7%sy,  0.0%ni, 69.8%id,  8.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu8  :  1.0%us,  0.0%sy,  0.0%ni, 98.5%id,  0.5%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu9  :  0.7%us,  0.0%sy,  0.0%ni, 99.0%id,  0.2%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu10 :  0.4%us,  0.0%sy,  0.0%ni, 99.4%id,  0.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu11 :  0.1%us,  0.0%sy,  0.0%ni, 99.9%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu12 :  0.5%us,  0.0%sy,  0.0%ni, 99.3%id,  0.2%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu13 :  0.2%us,  0.0%sy,  0.0%ni, 99.7%id,  0.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu14 :  0.8%us,  0.0%sy,  0.0%ni, 98.9%id,  0.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu15 :  1.1%us,  0.0%sy,  0.0%ni, 98.5%id,  0.4%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu16 : 57.8%us,  9.2%sy,  0.0%ni, 13.3%id, 13.6%wa,  0.0%hi,  6.1%si,  0.0%st
Cpu17 : 39.3%us,  5.4%sy,  0.0%ni, 40.9%id, 14.4%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu18 : 60.5%us,  9.7%sy,  0.0%ni,  6.4%id,  9.4%wa,  0.0%hi, 14.0%si,  0.0%st
Cpu19 : 31.3%us,  4.7%sy,  0.0%ni, 52.9%id, 11.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu20 : 26.4%us,  5.0%sy,  0.0%ni, 58.9%id,  9.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu21 : 20.9%us,  4.0%sy,  0.0%ni, 68.8%id,  6.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu22 : 15.0%us,  2.7%sy,  0.0%ni, 77.0%id,  5.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu23 : 11.2%us,  2.6%sy,  0.0%ni, 81.9%id,  4.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu24 :  0.1%us,  0.0%sy,  0.0%ni, 99.4%id,  0.5%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu25 :  0.4%us,  0.0%sy,  0.0%ni, 99.6%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu26 :  0.1%us,  0.0%sy,  0.0%ni, 99.9%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu27 :  0.3%us,  0.0%sy,  0.0%ni, 99.6%id,  0.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu28 :  0.1%us,  0.0%sy,  0.0%ni, 99.2%id,  0.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu29 :  0.2%us,  0.0%sy,  0.0%ni, 99.5%id,  0.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu30 :  1.0%us,  0.0%sy,  0.0%ni, 98.7%id,  0.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu31 :  0.1%us,  0.0%sy,  0.0%ni, 98.9%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32838468k total, 32626120k used,   212348k free,   131252k buffers
Swap:  4194296k total,  1840544k used,  2353752k free,   723032k cached

この top を見る限りにおいて、Node1 の CPU が全く使われてない事がわかります。 なぜこうなるのかは理解していないのですが、NIC のキューのうち一つを CPU9(Node1のCPU)に割り当ててみると以下のようになりました。

Tasks: 843 total,   2 running, 841 sleeping,   0 stopped,   0 zombie
Cpu0  : 42.5%us,  6.8%sy,  0.0%ni, 19.4%id, 31.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  : 36.4%us, 11.3%sy,  0.0%ni, 36.4%id, 15.9%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 30.0%us,  6.5%sy,  0.0%ni, 44.4%id, 19.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  : 26.1%us,  4.7%sy,  0.0%ni, 52.2%id, 16.9%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  : 23.1%us,  4.5%sy,  0.0%ni, 57.2%id, 14.1%wa,  0.0%hi,  1.0%si,  0.0%st
Cpu5  : 19.5%us,  3.4%sy,  0.0%ni, 65.1%id, 12.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  : 58.6%us, 10.2%sy,  0.0%ni, 10.8%id, 15.3%wa,  0.0%hi,  5.1%si,  0.0%st
Cpu7  : 16.3%us,  3.0%sy,  0.0%ni, 67.7%id, 13.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu8  : 52.4%us,  7.5%sy,  0.0%ni, 15.3%id, 20.1%wa,  0.0%hi,  4.8%si,  0.0%st
Cpu9  : 33.1%us,  5.5%sy,  0.0%ni, 44.4%id, 17.1%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu10 : 56.3%us,  9.6%sy,  0.0%ni,  7.2%id, 14.0%wa,  0.0%hi, 13.0%si,  0.0%st
Cpu11 : 27.1%us,  4.7%sy,  0.0%ni, 54.6%id, 13.6%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu12 : 22.0%us,  4.0%sy,  0.0%ni, 63.3%id, 10.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu13 : 17.9%us,  3.3%sy,  0.0%ni, 70.8%id,  8.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu14 : 15.5%us,  2.6%sy,  0.0%ni, 76.0%id,  5.9%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu15 :  9.2%us,  2.3%sy,  0.0%ni, 82.9%id,  5.6%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu16 : 16.4%us,  4.3%sy,  0.0%ni, 25.1%id, 54.2%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu17 : 16.2%us,  6.4%sy,  0.0%ni, 57.2%id, 20.2%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu18 : 10.6%us,  3.0%sy,  0.0%ni, 76.1%id, 10.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu19 :  8.3%us,  1.3%sy,  0.0%ni, 83.7%id,  6.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu20 :  6.7%us,  1.0%sy,  0.0%ni, 87.7%id,  4.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu21 :  5.3%us,  1.3%sy,  0.0%ni, 89.0%id,  4.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu22 : 59.1%us,  9.1%sy,  0.0%ni,  4.7%id, 11.7%wa,  0.0%hi, 15.4%si,  0.0%st
Cpu23 :  4.0%us,  1.7%sy,  0.0%ni, 91.1%id,  3.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu24 : 31.1%us,  6.3%sy,  0.0%ni, 42.7%id, 19.9%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu25 : 12.3%us,  4.0%sy,  0.0%ni, 75.2%id,  8.6%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu26 : 44.1%us,  7.7%sy,  0.0%ni, 28.6%id, 19.5%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu27 :  6.3%us,  1.7%sy,  0.0%ni, 86.4%id,  5.6%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu28 :  5.3%us,  1.0%sy,  0.0%ni, 90.4%id,  3.3%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu29 :  3.3%us,  1.0%sy,  0.0%ni, 93.0%id,  2.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu30 :  2.7%us,  0.7%sy,  0.0%ni, 94.0%id,  2.7%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu31 :  1.7%us,  0.7%sy,  0.0%ni, 96.4%id,  1.3%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32838468k total, 32617528k used,   220940k free,   131240k buffers
Swap:  4194296k total,  1843964k used,  2350332k free,   718732k cached

見ての通り、各CPU にうまく分散できていると思います。

これは、Linuxでロードバランサやキャッシュサーバをマルチコアスケールさせるためのカーネルチューニング - ゆううきブログ

実環境では、割り込み負荷に加えて、アプリケーション処理の CPU負荷 (%user) も割り込みがかかっている CPU に偏っています。 これは、accept(2) で待ち状態のアプリケーションプロセスが、データ到着時にプロセススケジューラにより、プロトコル処理を行った CPU と同じ CPU に優先してアプリケーションプロセスを割り当てているような気がしています。 これも、L1, L2キャッシュの効率利用のためだと思いますが、ちゃんと確認できていません。

と言及されている挙動と一致します。

最終的には以下の dstat で確認した各キューの割り込みの量と、各 Node に近いメモリ量、最終的なCPU使用状況から、IRQ92 が割り当てられているNIC のキューだけを Node1 に割り当てるようにしました。

f:id:mapk0y:20150413065131p:plain

ioDrive の設定

触ったことがない HW なので後回しにしていたのですが、結局チューニングを行えませんでした。

個人的には、(おそらくレギュレーション違反ですが) Atomic Write を試したかったのですが、調べて見る限り NVMFs(DirectFS?) というファイルシステムにしないと行けないようでこちらに関してはオープンな情報がないように見受けられたので断念しました。

最後に

レギュレーション的にできることが少ないのでどうかな思っていましたが、実際にやってみると考えていたよりも非常に勉強になり楽しめました。 このようなイベントを企画してくださった運営の皆様、本当にありがとうございました。