EC-CUBE4 on Docker開発環境をtmpfsで速くしたい

以前、[EC-CUBE4の快適な開発環境をDocker Composeで整える for Windows](https://qiita.com/m-pyon23/items/1c8523db67a7c757e2c7)にて、I/Oが多いフォルダをDocker Volumeに持たせる事で速度改善を試みたが、新たにtmpfsオプションなるものの存在を知ったため、それで更なる速度改善が出来ないか、検証してみた。

## TL;DR
- EC-CUBE4ならびに、Symfony3のDocker開発環境を作りたいときの話
- varフォルダはtmpfsでマウントするとちょっと速くなる
- vendorフォルダは毎回コンテナ起動時に`composer install`走らせるのは怠いので、Docker Volumeで保つのが良さそう
- var,vendorをホストのフォルダと直接マウントすると、やはりありえないレベルで遅い

## 背景
https://github.com/EC-CUBE/ec-cube/pull/4204
https://github.com/EC-CUBE/ec-cube/pull/4391

EC-CUBE4(Symfony3)のDocker開発環境を構築するにあたり、一部のフォルダのIOが激しく、ホスト-コンテナ間で共有すると使い物にならないレベルで遅いという課題があった。


対策として、I/Oが多く主な速度低下の原因となる以下のフォルダについて、ホスト-コンテナ間で共有しないように設定を行った。

- var
    - キャッシュ・ログ等
- vendor
    - composer install による生成(DL)物

Dockerのボリュームマウントには`.gitignore`のような一部のフォルダ・ファイルのみ除外する設定がない。
ただし除外したいフォルダのみを別途ボリュームマウント指定することで、ホストとの同期対象外となる。

```docker-compose.yml
  ec-cube:
    # ...
    volumes:
      - ".:/var/www/html:cached"
      ### 同期対象からコストの重いフォルダを除外 #####################
      - "var:/var/www/html/var"
      - "vendor:/var/www/html/vendor"
```

## 試したいこと
特にvarフォルダについてはキャッシュ・ログの役割を持つため、永続化する必要はあまりない。
開発環境(devモード)においては頻繁に更新されるため、後述のtmpfsによる揮発性メモリで動作させ、I/Oを更に改善することで開発環境のレスポンスが向上するのではないかと考えた。

vendorフォルダについてはcomposerの実行結果(ダウンロードしたライブラリ)を永続化して保持する利点が大きいため、ボリュームのマウントのままで良いと思われるが、せっかくなので一緒に検証する。

## tmpfsについて


> tmpfsはUnix系OSにおける一時ファイルのための仕組みの共通名。tmpfsはファイルシステムにマウントされることを意図しており、これによりHDDをはじめとする永続性をもつ記憶装置の代わりに揮発性メモリに保存されるようにできる 
 [出典: フリー百科事典『ウィキペディア（Wikipedia）](https://ja.wikipedia.org/wiki/Tmpfs)

tmpfsでマウントした領域についてはSSDやHDDではなく、高速なメモリ上でRead/Writeが行われるようになるらしい。代わりに永続性はなくなり、コンテナ削除と同時にデータはクリアされる。一時的なキャッシュファイルであればここでよさそう。


そしてtmpfsは以下のように、Dockerの機能で簡単にマウントできた。
[Qiita -- dockerのtmpfsオプション](https://qiita.com/Hiraku/items/4b3a243b07c2e8eae949) @Hiraku


```docker-compose.yml
  ec-cube:
    # ...
    volumes:
      - ".:/var/www/html:cached"
    tmpfs:
      - "/var/www/html/var"
      - "/var/www/html/vendor"
```

## 計測

### 計測方法

Docker for Windows環境で動かしているため、以下のようにPowerShellからコンテナのコマンドを実行し、`Measure-Command`にて処理時間を計測した。

```powershell
PS *> measure-command { docker-compose exec ec-cube composer install }

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 29
Milliseconds      : 681
Ticks             : 296816493
TotalDays         : 0.000343537607638889
TotalHours        : 0.00824490258333333
TotalMinutes      : 0.494694155
TotalSeconds      : 29.6816493
TotalMilliseconds : 29681.6493
```

コマンドは以下の3種を続けて実行し、それぞれの時間を計測した。

- composer install
    - vendorにライブラリインストール。また、後述2コマンドを内包
- bin/console cache:clear --no-warmup
    - var内のキャッシュクリア。最低限の生成も行っている？
- bin/console cache:warmup --no-optional-warmers
    - var内にキャッシュ生成。

※ docker-compose exec ec-cube [Command] の形式

また、コンテナのビルド 及び composer install実行前の段階でvar/vendorフォルダは存在しない状態に揃えた。

```
# ホストからvar,vendorを削除
PS *> rm ./var/
PS *> rm ./vendor/

# コンテナのvar,vendorボリュームを削除
PS *> docker-compose down -v
```

### 計測結果(単位 sec/秒)

※ 各1回ずつしか試行していない & `composer install` が回線影響を受けるため、誤差は多々あります。

<font color="Blue">青</font>…var⇒volume, vendor⇒volume (現行)
<font color="Orange">オレンジ</font>…var⇒tmpfs, vendor⇒volume

#### composer install 結果
| var \ vendor          | vendor Host |              vendor Docker Volume | vendor Tmpfs |
| --------------------- | ----------: | --------------------------------: | -----------: |
| **var Host**          |      845.53 |                            132.57 |       110.05 |
| **var Docker Volume** |      822.66 |   <font color="Blue">16.89</font> |        33.75 |
| **var Tmpfs**         |      未計測 | <font color="Orange">11.72</font> |        29.68 |


#### bin/console cache:clear --no-warmup (キャッシュクリア) 結果
| var \ vendor          | vendor Host |             vendor Docker Volume | vendor Tmpfs |
| --------------------- | ----------: | -------------------------------: | -----------: |
| **var Host**          |       85.79 |                            10.99 |         9.85 |
| **var Docker Volume** |       30.81 |  <font color="Blue"> 1.82</font> |         1.82 |
| **var Tmpfs**         |      未計測 | <font color="Orange">1.76</font> |         1.78 |

#### bin/console cache:warmup --no-optional-warmers  (キャッシュ生成) 結果
| var \ vendor          | vendor Host |             vendor Docker Volume | vendor Tmpfs |
| --------------------- | ----------: | -------------------------------: | -----------: |
| **var Host**          |       81.52 |                            41.18 |        43.40 |
| **var Docker Volume** |       43.93 |  <font color="Blue"> 3.64</font> |         3.43 |
| **var Tmpfs**         |      未計測 | <font color="Orange">3.32</font> |         3.25 |


### 結果所感
1. vendorをホストにマウントした状態でcomposer installを打つのは苦行。10分以上待たされる。varだけtmpfsにするパターンはもはや計測する意味がないのでやめた。
2. (<font color="Blue">青</font>)両方Docker Volumeはそこそこ速い。現行のこれでも問題はなさそう
3. (<font color="Orange">オレンジ</font>)両方tmpfsはやっぱり一番速い。が、vendorが永続化出来ないとdocker-composeをコンテナ起動時に毎回走らせなければならないので、起動が遅くなる
4. varだけtmpfsにするのがバランス良さそう

## 課題
- sqliteのファイルがvendor以下に配置されるため、varを揮発性にするとコンテナ再作成で消える。
- docker-compose up 時にキャッシュ作成する仕組みが必要。
- 画面ベースでの改善度合いはを~~途中から面倒になって~~確認していない

## 結論
varをtmpfs, vendorをDockerVolumeでマウントすることで、キャッシュ削除→作成で良くて0.5秒ほど動作改善が見込める（どんぶり勘定。正確なdevモードでのキャッシュ更新動作を把握していないので、的外れかも）
が、実際の開発環境でストレス軽減されるほど変わるかは、実際使ってみてこれから感触を確かめることにする。

varはブランチ切り替えた時などはリセットされた方がいいと思うし、tmpfsで揮発性にしてdocker-compose.ymlのcommand指定で毎回コンテナ起動時にキャッシュクリア/生成を実行する方がいい気がしている。

## 参考
- Measure-Command (Microsoft.PowerShell.Utility)  
    - https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/measure-command?view=powershell-6
-  tmpfs - Wikipedia
    -  https://ja.wikipedia.org/wiki/Tmpfs
-  dockerのtmpfsオプション - Qiita @Hiraku
    -  https://qiita.com/Hiraku/items/4b3a243b07c2e8eae949