この記事に出てくる HITSC 関連の用語、構成名、設計上の呼び方は、執筆時点の暫定的なものです。実装や設計の進行に伴い、現在は変更されている場合があります。
HITSC / hitto Troubleshooting Contest は、Firecracker microVM 上に実際の Linux 問題環境を用意するために作っている検証用基盤である。
この基盤では、ticket ごとに Firecracker microVM を起動する。
各 VM には専用の rootfs.ext4 が必要になる。
素朴には、問題テンプレートの rootfs を ticket ごとに full copy すればよい。
problem rootfs.base.ext4
-> ticket-a/rootfs.ext4
-> ticket-b/rootfs.ext4
-> ticket-c/rootfs.ext4
ただし、rootfs が大きくなると copy 時間とディスク使用量が問題になる。
特に Docker 入り rootfs では数 GiB 単位になる。
そこで HITSC の実装では、Storage CoW の入口として reflink backend を入れた。
backend 設計
現時点の RootFSProvisioner は 3 種類ある。
current:
既存互換 backend。
cp --reflink=always を試し、失敗したら cp --reflink=never、
さらに失敗したら Go copy へ fallback する。
copy:
明示的な full copy 比較用 backend。
cp --reflink=never を使う。
reflink:
明示的な CoW backend。
cp --reflink=always を使い、CoW clone できなければ失敗する。
重要なのは、current と reflink の違いである。
current は互換性重視なので、reflink できなければ copy へ fallback する。
一方、reflink は検証や運用判断のために、reflink できなければ明確に失敗させる。
つまり、Storage CoW を本当に使えているか確認したい時は、HITSC_ROOTFS_BACKEND=reflink を指定する。
sudo HITSC_ROOTFS_BACKEND=reflink hitsc ticket start <ticket-id>
最初のベンチで見えたこと
まず、docker-iptables-001 で current / copy / reflink を比較した。
run directory:
current:
/var/lib/hitsc-firecracker/metadata/benchmarks/bench-rootfs-current-20260602-194422
copy:
/var/lib/hitsc-firecracker/metadata/benchmarks/bench-rootfs-copy-20260602-194422
reflink:
/var/lib/hitsc-firecracker/metadata/benchmarks/bench-rootfs-reflink-20260602-194422
条件:
problem: docker-iptables-001
count: 3 tickets
nodes per ticket: server, client1, client2
rootfs operations: 9
parallel: 1
scenario: start
runtime_backend: direct
結果はこうだった。
rootfs_provision mean:
current: 29.060 ms
copy: 27.486 ms
reflink: 26.229 ms
一見すると、copy と reflink の差がほぼない。
events.jsonl の rootfs_method はこうだった。
current:
rootfs_method=reflink 9 回
copy:
rootfs_method=copy 9 回
reflink:
rootfs_method=reflink 9 回
ここで分かったことは 2 つある。
1. current backend は、この worker 上では実際に reflink へ落ちていた
2. しかし、このベンチだけでは Storage CoW の効果を強く主張できない
理由は、rootfs copy 時間だけでなく、page cache、sparse file、ファイルシステムの挙動、VM 起動処理などの影響が混ざるためである。
特に Docker rootfs は大きいが、ext4 raw image の内部の使われ方や host 側 copy の sparse 処理によって、単純な「見かけサイズ分の書き込み」にはならない。
したがって、Storage CoW を評価するには、起動時間だけでなく、ディスク使用量の増分を見る必要がある。
disk proof
そこで、smoke-001 を使って copy と reflink のディスク増分を比較した。
run directory:
copy:
/var/lib/hitsc-firecracker/metadata/benchmarks/bench-smoke-disk-copy-20260602-01
reflink:
/var/lib/hitsc-firecracker/metadata/benchmarks/bench-smoke-disk-reflink-20260602-01
disk proof:
/var/lib/hitsc-firecracker/metadata/benchmarks/rootfs-disk-proof-20260602-01/result.tsv
条件:
problem: smoke-001
count: 3 tickets
parallel: 1
scenario: start
runtime_backend: direct
rootfs_provision の結果:
copy:
mean: 433.569 ms
p50: 432.758 ms
p95: 443.119 ms
max: 444.270 ms
reflink:
mean: 13.707 ms
p50: 11.474 ms
p95: 18.054 ms
max: 18.785 ms
この条件では、reflink は copy より約 31.6 倍速かった。
ticket_start も短くなった。
copy ticket_start mean:
813.150 ms
reflink ticket_start mean:
396.740 ms
rootfs_provision だけでなく、ticket start 全体でも約 2 倍速くなっている。
ディスク使用量
rootfs-disk-proof-20260602-01/result.tsv には、ベンチ前後のディスク使用量と instances 配下の見かけ使用量を記録した。
copy:
before=6801301504
after=8478265344
delta=1676963840
du_instances=6442496818
reflink:
before=6801465344
after=6805004288
delta=3538944
du_instances=6442488237
見かけ上、instances 配下にはどちらも約 6.4 GB 相当の rootfs がある。
copy du_instances:
6442496818 bytes
reflink du_instances:
6442488237 bytes
しかし、実際の filesystem 使用量の増分はまったく違う。
copy delta:
1676963840 bytes
約 1599 MiB
reflink delta:
3538944 bytes
約 3.4 MiB
つまり、reflink では ticket ごとの rootfs file は存在しているが、実データ block の大部分は base rootfs と共有されている。
これが Storage CoW の効果である。
なぜ du_instances は同じように見えるのか
ここで少し紛らわしい点がある。
du_instances が copy でも reflink でも約 6.4 GB に見えるため、最初は「reflink でもディスクを食っているのでは」と疑いたくなる。
しかし、reflink clone されたファイルは、見かけ上は独立したファイルである。
ファイルサイズや単純な du の見方だけでは、block 共有の実態を読み違えることがある。
今回の判断では、ベンチ前後の filesystem 使用量の増分を見た。
copy:
filesystem usage +約 1599 MiB
reflink:
filesystem usage +約 3.4 MiB
この差により、reflink の Storage CoW が効いていると判断した。
current backend の意味
今回の current backend では、rootfs_method=reflink が記録されていた。
これは、worker の現在の filesystem では reflink が使えていることを意味する。
ただし、current は fallback を持つ。
別の worker や別の filesystem では、同じ current でも rootfs_method=copy や rootfs_method=go-copy になる可能性がある。
したがって、Storage CoW の有無を明確にしたい場合は、以下を見る。
events.jsonl:
rootfs_backend
rootfs_method
運用で CoW 必須にしたい場合は、current ではなく reflink を指定する。
sudo HITSC_ROOTFS_BACKEND=reflink hitsc ticket start <ticket-id>
これで reflink 不可の worker では失敗する。
失敗する方が、知らないうちに full copy へ fallback してディスクを食い尽くすより安全である。
Storage CoW と Memory CoW の関係
今回の話は Storage CoW である。
Storage CoW:
rootfs.ext4 の block 共有
HITSC_ROOTFS_BACKEND=reflink
Memory CoW:
snapshot.mem の page 共有
HITSC_RUNTIME_BACKEND=snapshot-restore
両方を合わせると、ticket ごとの VM 発行はかなり軽くなる。
rootfs:
base rootfs と ticket rootfs が block 共有
memory:
snapshot.mem 由来の page が VM 間で共有
network:
ticket ごとに namespace / bridge / TAP を分離
Memory CoW については、別の記事で PSS を使って確認している。
実験 backend の候補
reflink はファイルシステムが対応している場合には非常に扱いやすい。
ただし、全 worker で使えるとは限らない。
そのため、将来候補として以下もメモしている。
LVM thin:
block layer で thin snapshot を作る
dm-snapshot:
device mapper snapshot を使う
OverlayFS:
lowerdir/upperdir で差分管理する
ただし、MVP の主経路にはまだ入れない。
LVM thin や dm-snapshot は pool 管理や device cleanup が難しく、OverlayFS は Firecracker の block device rootfs 管理と相性が難しい。
現時点では、rootfs.base.ext4 + reflink が最も単純で、問題作成フローも変えずに済む。
まとめ
今回確認できたこと:
RootFSProvisioner に current / copy / reflink backend を実装した
current backend はこの worker では rootfs_method=reflink になっていた
copy backend は rootfs_method=copy として明示的に full copy できる
reflink backend は rootfs_method=reflink として明示的に CoW clone できる
smoke-001 では rootfs_provision が copy 平均 433.6 ms、reflink 平均 13.7 ms だった
disk delta は copy 約 1599 MiB、reflink 約 3.4 MiB だった
結論として、この worker では reflink による Storage CoW が効いている。
効果を見るときは、起動時間だけではなく、rootfs_provision の時間と filesystem 使用量の増分を見るのがよい。