全国1億2000万の Docker ファンの皆さんこんにちは。
MySQL の起動がとてつもなく遅いのは有名な話。 ところが Docker コンテナの起動はなかなか早いので、 MySQL を使っているようなテストを高速化するケースで有用性が認められるのではないかと思って PoC を書いてみた。
(宣伝)こういった話も含めて YAPC でトークしたいので SNS 等で upvote お願いします: ( ✌'ω')✌ 楽しいモデル層開発 - YAPC::Asia Tokyo 2014 (宣伝おわり)
MySQL を使ったテスト
MySQL を使ったテストをする場合、だいたい次の 2 パターンになる。
- MySQL をテストのたびに起動してクリーンな状態で使う
- ローカルにデーモンとして起動した MySQL に接続して
DROP TABLE
やTRUNCATE
でクリーンな状態にして使う
だけど、「起動が遅い」「setup/teardown に時間がかかる」「並列にテストできない」といった問題を抱え、最終的にはスローテスト問題に帰結する。
Test::Docker::MySQL
都度 MySQL を使うようなテストは MySQL の起動が高速化さえすれば既知の問題は解消でき、更にそのままテスト時間の短縮に繋がる。
そこで Test::Docker::MySQL を PoC として書いてみた。結果としては、
- 起動時間が 30 sec -> 4 sec に短縮
- MySQL を複数起動できるので並列にテストを実行できる
と、トータルのテスト時間は相当短縮できる結果になって今後の方針に影響を与える結果になった。
使い方
ドキュメントに書いてあるとおりに docker をセットアップすれば次のように使える。 Docker Hub 便利。
use Test::Docker::MySQL;
my $dm_guard = Test::Docker::MySQL->new;
my $port = $dm_guard->get_port;
my $dsn = "dbi:mysql:database=mysql;host=127.0.0.1;port=$port";
my $dbh = DBI->connect($dsn , 'root', '', { RaiseError => 1 });
ポイント
Test::Docker::MySQL#get_port
でdocker run ...
が走る- コンテナの起動は早い
Test::Docker::MySQL
のインスタンスがスコープを抜けるとdocker kill ...
を自動的に実行してくれる- スコープをベースとして MySQL インスタンスを管理できる
ベンチマーク
だいたい次のようになって、インスタンス起動数が増えるごとにどんどん差が広がる。
Test::Docker::MySQL
$ cat tdm.pl use Test::Docker::MySQL; my $dm_guard = Test::Docker::MySQL->new; my $port_1 = $dm_guard->get_port; my $port_2 = $dm_guard->get_port; $ time perl tdm.pl real 0m4.020s user 0m0.134s sys 0m0.059s
Test::mysqld
$ cat tm.pl use Test::mysqld; my $mysqld_1 = Test::mysqld->new( my_cnf => { 'skip-networking' => '' } ); my $mysqld_2 = Test::mysqld->new( my_cnf => { 'skip-networking' => '' } ); $ time perl tm.pl real 0m30.291s user 0m1.002s sys 0m1.200s
既知の問題
docker run ...
コマンドが戻った直後には実は MySQL に接続できなくて、 1.2 秒後くらいに接続できるようになる。
なので、ライブラリの内部では 0.2 秒ごとに接続をリトライしている。
原因がわかっていなくて、 port forwarding か何かがネックになっているのかとか邪推しているけど、理由知っている人がいたら教えて欲しい。