RailsのRSpecにとってDocker for Macはどれぐらい遅いのか？

Railsを開発しているとRSpecの実行時間が遅くて開発効率が悪くなることがあります。
これは開発環境に[Docker For Mac](https://www.docker.com/docker-mac)を使っているからでしょうか？

# 背景

Docker For Macは遅いと言われます。

- [hanhan's blog - Docker for Macのmount遅い問題まとめ](https://blog.hanhans.net/2017/05/23/docker-for-mac-slow/)
- [Docker for Mac の volumesマウントが異常に遅い（半分解決） - Qiita](https://qiita.com/kenji-toforone/items/70136840e3f0eb61ae9c)

これは現在のDocker For Macでも遅いのでしょうか？
 

## rawフォーマットは試していません
また、最新のDocker For Macでは速くなっているようです。

[Docker for Macのディスクスループットを約2倍にする - Qiita](https://qiita.com/shinespark/items/51b6b145da4772764dfb)

> Docker Community Edition 17.12.0-ce-mac46 2018-01-09 (Stable)の更新で、ディスクスループットが320MiB/secから600MiB/secになるらしいので適用する。
>
> APFS, SSD, High Sierraで有効。

現在Sierraを使っているので、試していません。

# 実行環境
- MacBook Pro Late 2013
- macOS Sierra 10.12.6
- Docker Community Edition 17.12.0-ce-mac49 (21995)


# 手段
AWS EC2のAmazon Linux2インスタンスを使って試します。

Dockerの起動時間を抜いて計測するために

```sh
docker-compose run --service-ports web bash
```

を実行し、Docker上で

```sh
time bundle exec rspec spec/models/hoge_spec.rb
```

を実行します。

```sh
bash-4.3# time bundle exec rspec spec/models/hoge_spec.rb
No DRb server is running. Running in local process instead ...
Rack::File headers parameter replaces cache_control after Rack 1.5.
....................................................................................................................................................................................

Finished in 5.15 seconds
180 examples, 0 failures

real	0m23.581s
user	0m0.010s
sys	0m0.000s
```

`real`の値がtimeコマンドの結果です。
`rspec`コマンドの起動から終了までの実時間です。
これは「Rails環境の読み込み」時間を含みます。

`Finished in 5.15 seconds`がRSpecのテストの実行時間です。
差の18.4秒は「Rails環境の読み込み時間」です。

# 結果
以下の環境で試しました。

- Docker For Mac
- t2.micro
- c4.large

いずれもDockerとdocker-composeでRailsアプリケーションを構成しています。

|環境           |real      |rspec        |real比率 |rspec比率 |Rails環境読み込み比率|
|---------------|----------|-------------|--------|---------|--------------------|
|Docker For Mac |0m23.581s |5.15 seconds |-       |-        |-                   |
|t2.micro       |0m14.258s |2.9 seconds  |60%     |56%      |62%                 |
|c4.large       |0m11.456s |2.52 seconds |49%     |49%      |48%                 |

EC2上では40〜50%の時間短縮が計測されました。
単純にDocker for Macを使うとLinux上でDockerを使う場合の、２倍の時間が掛かると考えて良いでしょう。

## Docker For Mac

```sh
bash-4.3# time bundle exec rspec spec/models/hoge_spec.rb
No DRb server is running. Running in local process instead ...
Rack::File headers parameter replaces cache_control after Rack 1.5.
....................................................................................................................................................................................

Finished in 5.15 seconds
180 examples, 0 failures

real	0m23.581s
user	0m0.010s
sys	0m0.000s
```

## t2.micro

```sh
bash-4.3# time bundle exec rspec spec/models/hoge_spec.rb
No DRb server is running. Running in local process instead ...
Rack::File headers parameter replaces cache_control after Rack 1.5.
....................................................................................................................................................................................

Finished in 2.9 seconds
180 examples, 0 failures

real	0m14.258s
user	0m0.000s
sys	0m0.000s
```

## c4.large

```sh
bash-4.3# time bundle exec rspec spec/models/hoge_spec.rb
No DRb server is running. Running in local process instead ...
Rack::File headers parameter replaces cache_control after Rack 1.5.
....................................................................................................................................................................................

Finished in 2.52 seconds
180 examples, 0 failures

real	0m11.456s
user	0m0.000s
sys	0m0.000s
```

# 考察

## EC２の方が２倍近く速い

Docker For MacのDisk I/Oの遅さが原因に思えます。

[Docker for Macのディスクスループットを約2倍にする - Qiita](https://qiita.com/shinespark/items/51b6b145da4772764dfb) の効果とも一致します。
ファイルシステムのraw format化で、解消されることが期待できます。

## c4.largeがt2.microより15%速い

他のインスタンスタイプ、例えば

- t2.small
- m4.large
- c3.large

を使っても、t2.microとは差がありません。
これはc4.largeが（標準で）**EBS 最適化インスタンス**だからです。

https://aws.amazon.com/jp/ec2/instance-types/

> C5、C4、M5、M4、P3、P2、G3、および D2 の各インスタンスの場合、この機能はデフォルトで有効になっており、追加料金は発生しません。

やはり、RailsのRSpecでは、Disk I/Oがボトルネックのようです。

## その他の高速化
### Rails application preloader 
例えば、[Spring](https://github.com/rails/spring)を使うと「Rails環境のロード時間」をスキップできます。

今回のテストのように、Rspecの実実行時間の8割が「Rails環境のロード時間」に使われている場合は、Rails application preloaderを使うと、テストの繰り返しやすさに効果的です。

####　参考
- [Railsの開発効率をあげる - Springを使ってRailsのコンソールコマンドの実行を早くする - Rails Webook](http://ruby-rails.hatenadiary.com/entry/20141026/1414289421)
- [Spork/Zeus/Spring によるテスト実行時間の短縮 - RSpec/Capybara入門 - Ruby on Rails with OIAX](https://www.oiax.jp/rails/rspec_capybara_primer/spork_zeus_spring.html)
- [rspecを高速化するsporkとその仕組み - ITエンジニアとして生きる](http://d.hatena.ne.jp/ogin_s57/touch/20130730/1375177978)

### スローテストを解消する

RailsのRSpecは、Rails環境のロード時間を除いても、遅いことが多いです。
特にモデルのテストの場合は、it毎にDBにテスト用のレコードを用意するための`insert`文を発行するためです。
必要なレコードが20件ある場合は、体感でわかるぐらい遅くなりました。

テスト数が多いと線形に遅くなります。
簡単に20秒掛かるテストセットが作れます。
Mac上で20秒掛かるテストが、Linux上で10秒になっても、テスト待ちのストレスはあまり解消されません。
ローカル環境とリモート環境を切り替える手間を考えると、テストの繰り返しやすさは上がりません。
地道にスローテストを解消した方が、テストの繰り返しやすさには効果が高いです。

`before :all`で複数のテストでまとめてテストデータを用意したり、`FactoryGirl.create`の代わりに`FactoryGirl.build`を使ったりして、DBのレコード追加削除を減らすのが効果的です。

#### 参考
- [Railsのrspecが遅いので対応した結果ｗｗｗｗｗｗｗｗｗｗｗｗｗｗ - リア充爆発日記](http://ria10.hatenablog.com/entry/20130622/1371907522)