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 の場合
--userland-proxy=true
の場合
--userland-proxy=false
の場合(Ver 1.7.0 以降可能)
ベンチマーク
ベンチマーク環境
- CPU: Core i5-5250U
- docker 1.7.0
- Debian GNU/Linux 8.1 (jessie)
※ サバフェス の景品で頂いた 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_depth
と innodb_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=2500
と innodb_buffer_pool_instances=8
としましたがもう少し試す余地がありそうです。
効果のなかったパラメータ
- innodb_io_capacity
- innodb_read_io_threads/innodb_write_io_threads
- innodb_purge_batch_size
- innodb_purge_threads
- innodb_concurrency_tickets
innodb_io_capacity
や innodb_read_io_threads/innodb_write_io_threads
が効果がなかったのは、最初からそれなりに大きい値を設定していたためだと考えています(裏付けはとっていません)。
ウォームアップ
ベンチマークの立ち上がりでスコアを稼ぐために、ウォームアップを行っていました。 これは上位の方々はみんな実施されていたようで、本番環境などでもメンテナンス後などによくやることです。
ウォームアップの内容は多くの場合読まれる可能性のあるテーブルを事前に select しておくという方法だと思います。
私もはじめはその方法でしたが、最終的には趣向を変えて innodb_buffer_pool_dump
を利用してみました。
innodb_buffer_pool_dump
は innodb_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 に割り当てるようにしました。
ioDrive の設定
触ったことがない HW なので後回しにしていたのですが、結局チューニングを行えませんでした。
個人的には、(おそらくレギュレーション違反ですが) Atomic Write を試したかったのですが、調べて見る限り NVMFs(DirectFS?) というファイルシステムにしないと行けないようでこちらに関してはオープンな情報がないように見受けられたので断念しました。
最後に
レギュレーション的にできることが少ないのでどうかな思っていましたが、実際にやってみると考えていたよりも非常に勉強になり楽しめました。 このようなイベントを企画してくださった運営の皆様、本当にありがとうございました。