顧客のデータベースを、1つずつ専用のmicroVMの中で動かしています。「それにいくらかかるのか」を知るために、4つのランタイムで同じPostgreSQLと同じnginxを、1台のマシンで、同じ制限・同じ負荷で動かして測りました。
4つのうちのcchvは、自作です。containerd-cloudhypervisorの略で、コンテナごとに専用のCloud HypervisorのmicroVMを作るランタイムです。設定も自分で決めています。その点を頭に置いて、数字を読んでください。測定のスクリプトは公開しています(KoedoDB/cchvの bench/runtime-comparison)。同じ手順で再現できます。
何を比べたか
| ランタイム | 中身 |
|---|---|
| runc | Dockerの既定。ホストのカーネルの上のコンテナ。基準。 |
gVisor(runsc、release 20261019.0) |
コンテナとホストの間に置くユーザー空間のカーネル。既定の設定。 |
| Kata Containers 4.3.1(Cloud Hypervisor) | コンテナごとに1つのmicroVM。Kata自身の設定(ファイルはvirtio-fs)。 |
| cchv v0.4.3(Cloud Hypervisor) | コンテナごとに1つのmicroVM。ルートファイルシステムは読み取り専用のイメージ、データはブロックデバイス。 |
マシンは、借りている専有サーバーです。Xeon E3-1270 v6(4コア8スレッド)、32 GB、NVMe 2本のRAID1。PostgreSQL 18.6とnginx 1.29を、それぞれコンテナで、マシン本来のDockerには触れない2つ目のDockerの上で動かしました。
公平にするために
最初の測定は公平ではなく、やり直しました。効いたのは2つです。
- CPUをそろえる。 PostgreSQLは3 CPU、nginxは2 CPU(quota)を、4つ全部に与えた。
-
VMの大きさ。 microVMは、コンテナの制限の上に、自分の分のオーバーヘッドを足します。実際のVMの大きさは、毎回、ハイパーバイザーのコマンドラインから確かめ、結果と一緒に記録しました。Kataは、制限に必ず1 vCPUを足し、外から変えられません。同じ3 CPUを与えると、KataのVMは4 vCPUで、cchvの3 vCPUより1つ多くなります。そこで、Kataは両方の形で載せます。
- CPUをそろえる(VMは1 vCPU大きい)。 コンテナ3 CPU、VMは4 vCPU。追加の1 vCPUはコンテナの制限の外にあり、ゲストのカーネルがネットワークとI/Oに使えます。Kataに有利です。
- VMの大きさをそろえる。 コンテナの制限を2 CPUに下げ、VMをcchvと同じ3 vCPUにする。VMは同じ大きさですが、Kataはコンテナが使えるCPUがcchvより1つ少なくなります。Kataに不利で、cchvがよく見えます。どちらの行も中立ではありません。
Kataの本当の値は、2つの行の間にあります。数字は、すべて3回の中央値です。
PostgreSQL
pgbench、16クライアント、スケール100。tpcb は書き込み(WALとデータファイル)、select は読み取りだけ、commit は1クライアントができるだけ速くコミットし続ける負荷です。
| tpcb tps | tpcb p99 | select tps | commit tps | |
|---|---|---|---|---|
| runc | 4,992 | 44.4 ms | 32,674 | 4,933 |
| gVisor | 1,995 | 37.1 ms | 12,454 | 3,449 |
| Kata、3 CPU(VMは4 vCPU) | 4,127 | 19.9 ms | 28,050 | 3,851 |
| Kata、2 CPU(VMは3 vCPU、cchvと同じ) | 2,825 | 35.5 ms | 18,790 | 3,712 |
| cchv | 3,966 | 8.8 ms | 27,282 | 3,550 |
- cchvは、データベースを専用のVMに入れたまま、runcのスループットの約80%に届きます。gVisorは、書き込みも読み取りも約40%です。
- VMの大きさをKataと同じにした場合(Kataは自分の分に1 vCPUを足すので、コンテナのCPUが1つ少なくなる。PostgreSQLでもnginxでもKataに不利な行です)で、かつKataのファイルがvirtio-fs(既定)のとき、cchvは約1.4倍速いです。この差の多くはファイルの経路の差で、次の節のとおり、Kataにブロックデバイスを渡すと縮みます。CPUをそろえた場合(VMはKataの方が1 vCPU大きい)と比べると、スループットはKataが数%上です(書き込み4%、読み取り3%、コミット8%)。一方、書き込みのp99は、cchvがKataの半分以下(8.8 msと19.9 ms)です。
- 書き込みテストのp99は、cchvが最も低いです。
- 残りの20%の行き先: コンテナの中でUnixソケットで読むと、cchvはruncと並びます。差は、VMに入るネットワークの経路にあります(読み取りで約12%)。キューの数、コアのスケジューリング、TAPのキューの規則を調べましたが、どれも動かせませんでした。
Kataにブロックデバイスを渡したとき
cchvはデータベースにブロックデバイスを渡します。Kataの既定はvirtio-fsです。差のどれだけが経路で、どれだけがVMかを見るために、Kataのデータをvirtio-blk(O_DIRECT)にして測り直しました。この記事の他の表と同じ条件(cchv v0.4.3、3回の中央値、nginxは50接続)で、Kataは両方の大きさです。コンテナのCPUをそろえた行(VMは1 vCPU大きい)と、VMの大きさをそろえた行(コンテナのCPUが1つ少ない)です。上と同じく、どちらの行も中立ではありません。VMの大きさは、毎回ハイパーバイザーのコマンドラインで確かめました。
| PostgreSQL tpcb tps | select tps | commit tps | nginx 1 KB rps | nginx 100 KB rps | nginx 10 MB rps | |
|---|---|---|---|---|---|---|
| Kata、virtio-blk、CPUをそろえる(コンテナはPostgreSQL 3 CPU・nginx 2 CPU、VMは4と3 vCPU) | 4,588 | 27,299 | 3,571 | 51,322 | 1,742 | 14 |
| Kata、virtio-blk、VMの大きさをそろえる(コンテナはCPUが1つ少なく、PostgreSQL 2 CPU・nginx 1 CPU、VMは3と2 vCPU) | 3,152 | 18,732 | 3,579 | 48,401 | 1,750 | 15 |
| cchv v0.4.3(コンテナはPostgreSQL 3 CPU・nginx 2 CPU、VMは3と2 vCPU) | 3,966 | 27,282 | 3,550 | 45,031 | 21,220 | 216 |
- PostgreSQL: ブロックデバイスにすると、Kataはvirtio-fsより書き込みが約11%速くなります(CPUをそろえて4,588と4,127、VMの大きさをそろえて3,152と2,825)。CPUをそろえた行では、Kataがcchvより書き込みで16%上で、読み取りとコミットは同等です。VMの大きさをそろえた行では、cchvが書き込みで26%、読み取りで46%上で、コミットは同等です。Kataの既定に対するcchvの約1.4倍のうち、ブロックデバイスで説明できるのは一部で、残りは、VMの大きさをそろえた行でKataのCPUが1つ減る分です。Kataの本当の値は2つの行の間で、cchvに近いです。
- nginxの小さいファイル: ブロックデバイスのKataは、1 KBでcchvより7〜14%速いです。どちらもrunc(31,920)より上ですが、runcはCPUのquotaに縛られ、microVMのvCPUはコンテナの制限の外にあるので、VMのランタイムは制限より多くのホストCPUを使えます。今回、ホストのCPU時間は測っていません。cchvの小さいファイルの速さはブロックデバイスのおかげで、ブロックデバイスを持つランタイムなら同じになります。
- 100 KBと10 MB: Kataは1,742と14、cchvは21,220と216です。ディスクの経路では説明できません。virtio-fsのKataも同じ水準(1,640〜1,670と15)だからです。VMに入るネットワークの経路に見えますが、原因は見つかっていません。
- メモリは、ディスクの経路では変わりません。差が出るのは、VM 1台の固定費です。
メモリの費用
アイドルのPostgreSQL 1個がホストに使わせるメモリと、アイドルのnginxを多数並べたときの1個あたりのメモリです。
| PostgreSQL、アイドル | nginx 1個あたり(16個並べる)、アイドル → 負荷中 | |
|---|---|---|
| runc | 60 MiB | 12 → 29 MiB |
| gVisor | 146 MiB | 39 → 62 MiB |
| Kata | 336 MiB | 154 → 172 MiB |
| cchv | 356 MiB | 72 → 88 MiB |
コンテナごとのmicroVMは、無料ではありません。データベースでは、cchvはruncより約300 MiB多く、Kataよりも少し多いです。nginxでは、cchvはKataの半分以下です。
nginx
wrk、2 CPU、60秒、1秒あたりのリクエスト数です。
| 1 KB、50接続 | 100 KB、50接続 | 10 MB、50接続 | |
|---|---|---|---|
| runc | 31,920 | 19,908 | 405 |
| Kata(どちらの大きさでも) | 6,678 / 7,131 | 1,640 / 1,670 | 15 |
| cchv | 45,031 | 21,220 | 216 |
cchvは、小さなファイルではruncより上で、10 MBのファイルでは下です(VMを通るコピーのコスト)。この表のKataはvirtio-fsで、ブロックデバイスを渡すと、1 KBではKataの方がcchvより速くなります(上の節)。gVisorの1 KBの数字は、この表から外しました。どの実行でも、ちょうど毎秒831リクエストで、接続を4倍にすると4倍になります。スループットの上限ではなく、小さな応答に対するTCPの経路の固定の遅延のように見えます。原因は見つかっていません。
cchvが負けるところ
- たくさん同時に起動する。 PostgreSQLを16個、全部が準備できるまで、cchvで65秒、Kataで26秒、gVisorで13秒、runcで8秒かかりました。PostgreSQL 1個の起動は、cchvで約3秒です。
- データベース1個あたりのメモリ。 上の表のとおりです。
- nginxで大きなファイルを返すとき。
- ブロックデバイスを渡したKataに対する、小さなファイル(1 KBでKataが7〜14%上。PostgreSQLの書き込みも、CPUをそろえるとKataが16%上)。
この数字が言っていないこと
マシンは1台で、CPUは古い世代です。PostgreSQLのバージョンは1つ、nginxのイメージも1つです。Kataは既定の設定のままで、チューニングしていません。チューニングしたKataは、もっと速いかもしれません。cchvは自分たちで作ったので、他のランタイムより設定をよく知っています。fsync なしで書いたファイルは、4つのランタイムのどれでも、コンテナの終了後に残りました。fsync 自体の費用も、どこでも同じ(約200 µs)でした。