ボリューム・マウント(共有ファイルシステム)のためのパフォーマンス・チューニング

Docker 17.03 CE Edge は docker run -v--volume オプションに、新しい2つのフラグ cacheddelegated のサポートを追加しました。これは Docker Desktop for Mac がマウントしたボリュームに対し、アクセス性能を著しく改善する可能性があります。これらオプションは パフォーマンス問題、ソリューション、ロードマップ における課題の議論と同じくして始まったものです。

ちなみに

Docker CE Edge 17.04 のリリースノートは こちら です。それと、 docker run -v フラグの追加に関連するプルリクエストは こちら です。

以下のトピックで説明するのは、 osxfs 上のバインド・マウント・ボリュームの変更に対するものと、パフォーマンス最適化をもたらすオプションについてです。

こちらの`[Docker on Mac Performance <https://stories.amazee.io/docker-on-mac-performance-docker-machine-vs-docker-for-mac-4c64c0afdf99>`_ ブログ記事に、簡単な要約があります。

Compose ファイル中における各オプション調整に関する情報は、Docker Compose トピックにある Caching options for volume mounts をご覧ください。

ホストコンテナのファイルシステム一貫性とパフォーマンスの密接な関係

macOS や Windows を含む、数々のプラットフォーム上で Docker が利用できるようになり、コンテナ実行時にマウントに関連するワークロード(作業負荷)を最適化する必要性が一般化しました。

現時点で Linux 上のマウントに関する実装とは、コンテナ内にホスト側と一貫性したディレクトリツリーを提供するものです。つまり、読み書き処理とは、ホスト上だけでなくコンテナ内でも行われますので、他の環境の影響を直ちに受けます。また、ファイルシステムイベント( inotifyFSEvents )は、両方(ホスト上およびコンテナ)のディレクトリで直ちに反映します。

Linux 上ではホストとコンテナ間で VFS を基盤に共有しているので、オーバーヘッドのない反映を保証します。しかしながら、 macOS (および他の Linux 以外のプラットフォーム)では、完全な一貫性を保つために著しいオーバーヘッドがあります。これは message describing ファイルシステムが動作するために、ホストとコンテナ間を同期的に通過(passed synchronously)する必要があるためです。現時点の実装は多くのタスクに対して十分なものですが、ある種のワークロードでは完全な一貫性を確保するためにオーバーヘッドが発生し、ネイティブな環境(Docker 以外)でなければ、著しいパフォーマンス悪化をもたらします。たとえば、

  • docker/docker ソースツリーに go list ./... をバインド・マウントして実行すると、約 26 秒かかる
  • バインド・マウントしたディレクトリに、100MB を 1k ブロックで書き込むと、約 23 秒かかる
  • 新しく作成した(空っぽの)アプリケーションで ember build を実行すると、コンテナとホスト間でリクエストと応答があるたびに、連続して 70000 syscall を引き起こす

スタックの遅延(latench)を減らすための最適化により、これらのワークロードを著しく改善しました。そして、さらにいくつかの最適化も施しています。しかしながら、それでも一貫性を維持するための制約によって、遅延は最小限あります。つまり、ある種の使い方によっては受け入れがたいワークロードの遅さがあるでしょう。

consistent、cached、delegated 設定のチューニング

幸いにも、ほパフォーマンス劣化は多くの場合において最も深刻な例であり、また、コンテナとホスト間における一貫性が完全である必要はありません 。特に多くのケースでは、コンテナ内に書き込んだファイルを、即時ホスト上に反映する必要がありません。たとえば、双方向(インタラクティブ)の開発を行っていると、コンテナ内でのファイルシステムイベントの発生が、ホスト上のバインド・マウントしたディレクトリに書き込む必要がある場合、コンテナ内で構築した成果物(build artifacts)を即座にホスト上のファイルシステムに反映する必要はありません。これら特徴的な2つのケースでは、著しいパフォーマンス改善が可能です。

ここでは必要となる一貫性のレベルに応じ、3つのシナリオを検討しました。各ケースは、いずれもコンテナ内にバインド・マウントしたディレクトリを持っていますが、2つのケースではコンテナとホスト間で一時的な矛盾の発生を許容しています。

  • consistent :完全な一貫性(常にホストとコンテナが完全に同じ表示)
  • cached :ホストの表示が信頼できる(ホスト上の更新がコンテナ上に反映するまで、遅延が発生するのを許容)
  • delegated :コンテナの表示が信頼できる(コンテナ上の更新がホスト上に反映するまで、遅延が発生するのを許容)

これら各設定( consistentcacheddelegated )は docker run-v オプションで指定できます。たとえば、 /Users/yallop/project をコンテナ内の /project パス以下にバインド・マウントするとき、次のようなコマンドを実行します。

docker run -v /Users/yallop/project:/project:cached alpine command

キャッシュ設定はバインド・マウントごとに独立しているため、マウントするディレクトリごとに異なるモードでマウントできます。

docker run -v /Users/yallop/project:/project:cached \
 -v /host/another-path:/mount/another-point:consistent
 alpine command

挙動の解説

以下にある各設定で説明が保証しているのは、ファイルシステム操作が効率的になるかどうかに関連しています。ここでは前提として、 host が指し示すのは、ユーザの Docker クライアント上にあるファイルシステムです。

delegated

delegated 設定では、一連の(一貫性に対する)保証が最も弱いものです。 delegated でディレクトリをマウントすると、コンテナのファイルシステム上の表示が信頼できるものとなり、コンテナ内での書き込み処理が、ホスト上のファイルシステムに即時反映しない場合があります。NFS非同期モードのような状況であれば、もしも delegated バインドマウントしたコンテナがクラッシュすると、書き込みが失われる可能性があります。

しかしながら、一貫性の放棄により、 delegated マウントは他の設定に比べて著しいパフォーマンスをもたらします。空っぽのスペースやビルド成果物のような、データの書き込みが一時的(ephemeral)または直ぐに再生成可能であれば、 delegated は正しい選択になるでしょう。

delegated マウントを担保するため、コンテナ実行中に以下の制約があります。

  1. もしもファイルシステムイベントに通知する実装であれば、イベントが生成されたとき、関連するファイルシステム状態に関連するコンテナの変更がなければ、コンテナの状態に関連する特定のイベントは、ホストファイルシステム状態にその時点で反映する 必要があります
  1. flush や sync 処理が行われると、関連するデータはホストファイルシステム上に反映(write back)する 必要があります 。flush から sync 処理をするまで、コンテナは データの書き込み、メタデータの変更、ディレクトリ階層の変更をキャッシュする 可能性があります
  1. 同じランタイムによってホストされている全てのコンテナは、マウントしているキャッシュの一貫性を共有する 必要があります
  1. delegated マウントで共有しているコンテナが終了すると、マウントに対する変更はホストファイルシステム上に反映する 必要があります 。反映が失敗すると、コンテナの処理が失敗 しなくてはならず 、終了コードや Docker event channel で通知します。
  1. delegated マウントしている場所を cachedconsistent マウントで共有すると、それぞれの場所は cachedconsistent マウント指定に従う 必要があります 。 これらの制約はありますが、 delegated 設定はコンテナ実行時に自由度をもたらします。
  1. コンテナはファイルデータとメタデータ(ディレクトリ構造、ノードの存在、等)を無期限に保持する 可能性があり 、このキャッシュによってホスト上のファイルシステム状態と同期しない 可能性があり ます。ホストファイルシステムで変更が発生すると、開発者はキャッシュを無効化すべきですが、プラットフォームの制約による時間枠(timeframe)の保証は難しいでしょう。
  1. もしもホストファイルシステム上でマウントしているソースディレクトリに変更を加えても、 delegated マウントしているホスト側ソース・ディレクトリの同期によって、それぞれの変更が失われる 可能性があります
  1. 挙動 6~7 はソケット、パイプ、デバイスに対しては 適用外 です。

cached

cached 設定は delegated 設定の全てを保証し、コンテナ内で書き込み処理の見え方に関連し、追加の保証をします。 cached は読み込みが重たいワークロードの性能を著しく改善しますが、ホストとコンテナ間で一時的に一貫性を失う犠牲を伴います。

cached としてマウントしたディレクトリは、ホスト側ファイルシステムが信頼できます。つまり、コンテナでの書き込み処理は即時ホスト側でも見えるようになりますが、ホスト上での書き込み処理がコンテナ内で見えるようになるには遅延が発生しうるでしょう。

ちなみに

cached について更に学ぶには、 User-guided caching in Docker Desktop for Mac をご覧ください。

  1. 実装は delegated 挙動の 1~5 に従う 必要があります
  1. 実装がファイスシステムイベントの提供時、イベントが生成された時点で、コンテナ状態をホストファイルシステムの状態に反映する 必要があります
  1. コンテナはホストファイルシステムのメタデータ変更、ディレクトリ階層の変更、データ書き込みの一貫性を処理する 必要があります が、データ書き込み、メタデータ変更、ディレクトリ階層の変更をキャッシュ する必要はありません
  1. cached マウントが consistent マウントとして共有される場合、重複する場所は consistent マウントの挙動で上書きする 必要がありますdelegeted 設定の柔軟さにより、状態を保ち続ける場合があります。つまり、
  1. 実装は delegated の挙動 6 を許容する 可能性があります

consistent

consistent 設定した場所は、コンテナ実行中に最も制約をうけます。コンテナとホストを consistent でマウントしたディレクトリは、常に同期します。つまり、コンテナ内での書き込み処理は即時ホスト上でも見えるようになり、ホスト上での書き込み処理は即時コンテナ内でも見えるようになります。

consistent 設定は最も Linux のバインド・マウントの挙動を反映しているものです。しかしながら、パフォーマンスの優先度が高く完全な一貫性の維持に対する優先度が低いような、いくつかの利用例にあたっては、強力な一貫性を確保するためにオーバーヘッドをもたらします。

  1. 実装は cached 挙動 1~4 に従う 必要があります
  1. コンテナのマウントは、ホストファイルシステム上のメタデータ変更、ディレクトリ階層の変更、データ書き込みを即時に反映する 必要があります

default

default 設定は、指定が無ければデフォルトで適用されるもので、 consistent 設定と同一です。重要なのは、重複したディレクトリを強化するのに必要な cached 挙動 4 と delegated 挙動 5 が、 default マウントには適用されません。もしも state フラグの指定が無ければ、これがデフォルト設定になります。

参考

Performance tuning for volume mounts (shared filesystems)
https://docs.docker.com/docker-for-mac/osxfs-caching/