AWS i4i.8xlarge x4 (08/17) -- reflink checkpoint/defragment + livelock fixes, without-rivals
この4並列ラウンドは --without-rivals モードで VMemKV の4バリアント(Baseline/+BF/+Inline/+Prefault)のみを実測し、RocksDB/RocksDB-BlobDB/LMDBの数値は直近の08/13フルランから再利用してマージした(rivalコードは無変更のため数値は不変という前提)。本ラウンドの主な検証対象は、checkpoint()/defragment()をreflink clone + hole punchで統一した再設計と、実測で発見・修正した3件の独立したライブロック(run_reorganize()の自己競合ループ、acquire_write_handle()のバックオフ無しリトライストーム、Wal::drain_pending()のLSN追いかけっこ)を、32並列スレッドという実際の高並行度の下で正しく機能することを検証すること。既存のIn-Memory/LTM×8B/1KB/64KBマトリクスに加え、新たに2つの実験を追加: (1) Churn-Ratio Scaling — 固定コーパス(8M件, in_memory/1KB)に対する churn 率(直近世代からの変更割合)を振って定常状態の defragment() 所要時間を測定したところ、churn_ratio >= 0.05(8M件中わずか40万件)で60秒のハードキャップに到達(タイムアウト)することが判明——O(diff)設計でも、変更量そのものが大きければコストは避けられないという妥当な結果。(2) Corpus-Size Invariance across Generations — 既存チェックポイントが存在する状態(2回目以降の呼び出し)でのコーパスサイズを振って定常状態のt1t2_steady所要時間を、T1-only/T1+T2ブートストラップ(直前チェックポイントが無い初回呼び出し。同じ現行reflink+punch実装をコールドで計測しているだけで別実装ではない)と比較(Reorg Scalingタブのチャートに緑の第3系列として表示、Summaryタブにもテーブルで掲載)。また、YCSB-Eの強制トリガースケジュールをt=15秒の単発から t=5秒(reorganize)/t=10秒・t=25秒(defragment)の3段階へ改訂——将棋倒し(スケジュールされた秒に対する実発火の遅延)へのガードを意図的に入れておらず、書き込み負荷次第で実発火が数十秒遅延したり、8B In-Memoryのように挿入スループットが極端に高いケースではreorganize()自体が20秒以上かかり後続のdefragment()トリガーが30秒間に一度も発火しないまま終わる、という挙動も観測された(YCSB-Eタブのチャートはバリアントごとに独立した色・マーカー形状で表示、ホバーで詳細を確認可能)。
Stacking Variants
各最適化の詳細は Design Document を参照。
- RocksDB: LSM-Tree のベースライン比較
- LMDB: B+Tree / mmap ベースの比較対象
- RocksDB-BlobDB: RocksDB の Blob 分離ストレージ変種(大きな Value 向け)
- Baseline: vmemkv 最適化なし
- +BF: + T1 Bloom Filter
- +Inline: +BF + T1 Inline Value(<8B のValueをT1のみで処理)
- +Prefault: +Inline + T2 Async Prefaulting(全部盛り、= VMemKVStore)。旧
ScanBaseSequentialアブレーションは実測の結果撤去され、その最適化(base領域専用mmap経路)はGet/Scan双方の標準パスへ無条件で統合済み。
Environment
- Instance: AWS i4i.8xlarge (ap-northeast-1) x4 (4並列独立インスタンス、run_4parallel_bench.sh --without-rivals)
- CPU: Intel Xeon Platinum 8375C @ 2.90GHz (32 cores)
- RAM: 247.7 GiB
- RAM (Larger-Than-Memory): cgroup で DRAM を 1GiB に制限、target_ratio=8.0でコーパスを約8倍の規模に構築後、cgroup内で計測(ストアごとに独立したcgroupスコープ + drop_caches)
- Disk: Local NVMe SSD (Swap Media for LTM)
- Kernel: 6.17.0-1019-aws
- Git Rev: 6421d3b76d32 (clean -- checkpoint/defragment reflink統一 + 3件のライブロック修正を含む)
Workloads
- Insert: 順次挿入(1/4/16/32 スレッド)
- Get Hit/Miss (Zipf/Uniform): ポイントルックアップ
- Update / Delete: Zipf 分布での更新・削除
- Scan (Zipf/Uniform): レンジスキャン(旧レポートにあった reorganize モード分割は撤去済み)
- YCSB-E: 95% Scan + 5% Insert 混合(32スレッド / 30秒タイムライン)
Overall Notes & Annotations
Workload Winners & Relative Speedup
Best VMemKV variant vs. whichever rival (RocksDB, LMDB, RocksDB-BlobDB) is FASTEST at that cell, at 32 threads (max concurrency tested) -- not the best-of-any-thread-count-or-rival figure, since both axes are known to shrink (sometimes invert) the ratio if picked favorably. The rival actually being compared against is named in small text under each badge.
| Workload | 8B In-Memory | 1KB In-Memory | 1KB LTM | 64KB LTM |
|---|---|---|---|---|
| Insert |
✅ WIN (1.36x)
vmemkv (+Inline)
vs RocksDB-BlobDB
|
✅ WIN (1.46x)
vmemkv (+Inline)
vs RocksDB-BlobDB
|
✅ WIN (1.45x)
vmemkv (+Prefault)
vs RocksDB-BlobDB
|
✅ WIN (2.47x)
vmemkv (+BF)
vs RocksDB
|
| Update (Zipf) |
✅ WIN (1.29x)
vmemkv (Baseline)
vs RocksDB
|
✅ WIN (1.45x)
vmemkv (Baseline)
vs RocksDB-BlobDB
|
✅ WIN (1.97x)
vmemkv (+Inline)
vs RocksDB-BlobDB
|
✅ WIN (3.19x)
vmemkv (+BF)
vs LMDB
|
| Delete |
✅ WIN (1.39x)
vmemkv (+Prefault)
vs RocksDB-BlobDB
|
✅ WIN (1.47x)
vmemkv (+BF)
vs RocksDB
|
✅ WIN (1.52x)
vmemkv (Baseline)
vs RocksDB
|
✅ WIN (3.14x)
vmemkv (+Inline)
vs RocksDB-BlobDB
|
| Get (Miss) |
✅ WIN (2.71x)
vmemkv (+Inline)
vs RocksDB
|
✅ WIN (3.06x)
vmemkv (+Inline)
vs LMDB
|
✅ WIN (2.80x)
vmemkv (+Inline)
vs LMDB
|
✅ WIN (2.56x)
vmemkv (+Inline)
vs LMDB
|
| Get (Hit, Zipf) |
✅ WIN (1.39x)
vmemkv (+Inline)
vs LMDB
|
✅ WIN (1.19x)
vmemkv (+Prefault)
vs LMDB
|
❌ LOSE (0.59x)
vmemkv (+Inline)
vs RocksDB-BlobDB
|
✅ WIN (1.27x)
vmemkv (+Inline)
vs RocksDB
|
| Get (Hit, Uniform) |
WIN (1.11x)
vmemkv (+Inline)
vs LMDB
|
WIN (1.14x)
vmemkv (Baseline)
vs LMDB
|
❌ LOSE (0.80x)
vmemkv (+Prefault)
vs RocksDB-BlobDB
|
≈ EVEN (1.02x)
vmemkv (Baseline)
vs RocksDB-BlobDB
|
| Scan (Zipf) |
❌ LOSE (0.83x)
vmemkv (+Prefault)
vs LMDB
|
≈ EVEN (1.04x)
vmemkv (+Inline)
vs LMDB
|
≈ EVEN (0.99x)
vmemkv (Baseline)
vs RocksDB
|
WIN (1.09x)
vmemkv (+Inline)
vs LMDB
|
| Scan (Uniform) |
LOSE (0.86x)
vmemkv (+Prefault)
vs LMDB
|
WIN (1.11x)
vmemkv (+Inline)
vs LMDB
|
✅ WIN (1.38x)
vmemkv (+Prefault)
vs RocksDB
|
≈ EVEN (0.96x)
vmemkv (+Inline)
vs RocksDB-BlobDB
|
Corpus-Size Invariance across Generations (new experiment)
Steady-state checkpoint_and_defragment() duration (reflink clone + hole punch, O(diff), 2nd-or-later call against an existing prior generation) at fixed churn ratio (0.01), swept across corpus size. Scoped to ltm/1KB only. Same series also plotted (green) on the Reorg Scaling chart in the 1KB LTM tab, alongside the T1-only/T1+T2 bootstrap sweep (1st-ever call, no prior generation to reflink from) for comparison.
| Corpus Ratio | Corpus (keys) | Steady-state defragment() (churn=0.01) |
|---|---|---|
| 25% | 2,064,888 | 9.78s |
| 50% | 4,129,776 | 20.85s |
| 75% | 6,194,664 | 27.12s |
| 100% | 8,259,552 | 46.94s |
Churn-Ratio Scaling (new experiment)
Steady-state checkpoint_and_defragment() duration at fixed corpus size (8M keys, in_memory/1KB), swept across the fraction of the corpus mutated since the last generation (churn ratio). 60s hard cap per point. One additional ltm/1KB spot check at churn_ratio=0.01. Notably, churn_ratio=0.05 -- just 400,000 of the 8M keys (5%) touched since the last checkpoint -- is already enough to blow the 60s cap; the O(diff) design's cost still scales with how much actually changed, it isn't a fixed cheap constant.
| Scenario/Value | Churn Ratio | Corpus (keys) | defragment() steady-state |
|---|---|---|---|
| in_memory_1KB | 0 | 8,000,000 | 8.21s |
| in_memory_1KB | 0.01 | 8,000,000 | 22.73s |
| in_memory_1KB | 0.05 | 8,000,000 | ≥60s (timeout) |
| in_memory_1KB | 0.1 | 8,000,000 | ≥60s (timeout) |
| in_memory_1KB | 0.25 | 8,000,000 | ≥60s (timeout) |
| in_memory_1KB | 0.5 | 8,000,000 | ≥60s (timeout) |
| in_memory_1KB | 1 | 8,000,000 | ≥60s (timeout) |
| ltm_1KB | 0.01 | 8,259,552 | 41.92s |
Insert Workload
Update (Zipf) Workload
Delete (Zipf) Workload
Get Miss (Zipf) Workload
Get Hit (Zipf) Workload
Get Hit (Uniform) Workload
Scan (Zipf) Workload
Scan (Uniform) Workload
YCSB-E — Scan + Insert 混合スループット(30秒タイムライン)
YCSB-E ワークロード: 95% Scan(100件レンジスキャン)+ 5% Insert を 32スレッドで 30秒間実行。
藍色の点線: 自然発生のT1 Reorganize(Adaptive Soft Limitによる自動トリガー、全バリアント共通)。
強制トリガーはバリアントごとに独立集計(全バリアント共通の1本の線には集約しない): t=5秒予定のreorganize()(細かい点線)、t=10秒・t=25秒予定のdefragment()(粗い点線)を、そのバリアント自身の色+マーカー形状(スキャンQPS線をホバーした際の点と同じ形。凡例のポイント形状も対応)で描画。
実際に発火する秒(線の位置)は予定秒とは限らない(将棋倒しに対するガード無し、書き込み負荷次第で数十秒遅延することがある)。8B In-Memoryのように挿入スループットが極端に高いワークロードでは、t=5秒予定のreorganize()自体が20秒以上かかり、後続のdefragment()トリガー(t=10s/25s)がこの30秒間に一度も発火しないまま終わることもある(この場合、線は1本しか出ない)。カーソルを線に合わせると、その秒に発火したバリアント・種類・予定秒・実発火秒・所要時間を表示。
Scan QPS タイムライン
32 threads / 30 secondsReorganize Duration vs. Corpus Size (T1-only vs T1+T2)
単発のreorganize()呼び出し1回の所要時間を、コーパスサイズ(件数)を横軸にスイープ計測。
藍色 = T1-only(T2は一切触らない)。赤色 = T1+T2、ただしブートストラップ(直前チェックポイントが存在しない新規コーパスへの初回defragment()。reflink元が無いのでO(N)。現行のreflink+punch実装そのものを、コールド状態で計測した数値であって別実装ではない)。緑色 = T1+T2 steady(既存世代からのreflink clone + hole punch、O(diff)の本来の定常状態。churn_ratio=0.01でのコーパスサイズ不変性実験、1KB LTMタブのみ)。
▲マーカーはタイムアウトを示す。
Reorganize Duration
single-shot, threads:1Insert Workload
Update (Zipf) Workload
Delete (Zipf) Workload
Get Miss (Zipf) Workload
Get Hit (Zipf) Workload
Get Hit (Uniform) Workload
Scan (Zipf) Workload
Scan (Uniform) Workload
YCSB-E — Scan + Insert 混合スループット(30秒タイムライン)
YCSB-E ワークロード: 95% Scan(100件レンジスキャン)+ 5% Insert を 32スレッドで 30秒間実行。
藍色の点線: 自然発生のT1 Reorganize(Adaptive Soft Limitによる自動トリガー、全バリアント共通)。
強制トリガーはバリアントごとに独立集計(全バリアント共通の1本の線には集約しない): t=5秒予定のreorganize()(細かい点線)、t=10秒・t=25秒予定のdefragment()(粗い点線)を、そのバリアント自身の色+マーカー形状(スキャンQPS線をホバーした際の点と同じ形。凡例のポイント形状も対応)で描画。
実際に発火する秒(線の位置)は予定秒とは限らない(将棋倒しに対するガード無し、書き込み負荷次第で数十秒遅延することがある)。8B In-Memoryのように挿入スループットが極端に高いワークロードでは、t=5秒予定のreorganize()自体が20秒以上かかり、後続のdefragment()トリガー(t=10s/25s)がこの30秒間に一度も発火しないまま終わることもある(この場合、線は1本しか出ない)。カーソルを線に合わせると、その秒に発火したバリアント・種類・予定秒・実発火秒・所要時間を表示。
Scan QPS タイムライン
32 threads / 30 secondsReorganize Duration vs. Corpus Size (T1-only vs T1+T2)
単発のreorganize()呼び出し1回の所要時間を、コーパスサイズ(件数)を横軸にスイープ計測。
藍色 = T1-only(T2は一切触らない)。赤色 = T1+T2、ただしブートストラップ(直前チェックポイントが存在しない新規コーパスへの初回defragment()。reflink元が無いのでO(N)。現行のreflink+punch実装そのものを、コールド状態で計測した数値であって別実装ではない)。緑色 = T1+T2 steady(既存世代からのreflink clone + hole punch、O(diff)の本来の定常状態。churn_ratio=0.01でのコーパスサイズ不変性実験、1KB LTMタブのみ)。
▲マーカーはタイムアウトを示す。
Reorganize Duration
single-shot, threads:1Insert Workload
Update (Zipf) Workload
Delete (Zipf) Workload
Get Miss (Zipf) Workload
Get Hit (Zipf) Workload
Get Hit (Uniform) Workload
Scan (Zipf) Workload
Scan (Uniform) Workload
YCSB-E — Scan + Insert 混合スループット(30秒タイムライン)
YCSB-E ワークロード: 95% Scan(100件レンジスキャン)+ 5% Insert を 32スレッドで 30秒間実行。
藍色の点線: 自然発生のT1 Reorganize(Adaptive Soft Limitによる自動トリガー、全バリアント共通)。
強制トリガーはバリアントごとに独立集計(全バリアント共通の1本の線には集約しない): t=5秒予定のreorganize()(細かい点線)、t=10秒・t=25秒予定のdefragment()(粗い点線)を、そのバリアント自身の色+マーカー形状(スキャンQPS線をホバーした際の点と同じ形。凡例のポイント形状も対応)で描画。
実際に発火する秒(線の位置)は予定秒とは限らない(将棋倒しに対するガード無し、書き込み負荷次第で数十秒遅延することがある)。8B In-Memoryのように挿入スループットが極端に高いワークロードでは、t=5秒予定のreorganize()自体が20秒以上かかり、後続のdefragment()トリガー(t=10s/25s)がこの30秒間に一度も発火しないまま終わることもある(この場合、線は1本しか出ない)。カーソルを線に合わせると、その秒に発火したバリアント・種類・予定秒・実発火秒・所要時間を表示。
Scan QPS タイムライン
32 threads / 30 secondsReorganize Duration vs. Corpus Size (T1-only vs T1+T2)
単発のreorganize()呼び出し1回の所要時間を、コーパスサイズ(件数)を横軸にスイープ計測。
藍色 = T1-only(T2は一切触らない)。赤色 = T1+T2、ただしブートストラップ(直前チェックポイントが存在しない新規コーパスへの初回defragment()。reflink元が無いのでO(N)。現行のreflink+punch実装そのものを、コールド状態で計測した数値であって別実装ではない)。緑色 = T1+T2 steady(既存世代からのreflink clone + hole punch、O(diff)の本来の定常状態。churn_ratio=0.01でのコーパスサイズ不変性実験、1KB LTMタブのみ)。
▲マーカーはタイムアウトを示す。
Reorganize Duration
single-shot, threads:1Insert Workload
Update (Zipf) Workload
Delete (Zipf) Workload
Get Miss (Zipf) Workload
Get Hit (Zipf) Workload
Get Hit (Uniform) Workload
Scan (Zipf) Workload
Scan (Uniform) Workload
YCSB-E — Scan + Insert 混合スループット(30秒タイムライン)
YCSB-E ワークロード: 95% Scan(100件レンジスキャン)+ 5% Insert を 32スレッドで 30秒間実行。
藍色の点線: 自然発生のT1 Reorganize(Adaptive Soft Limitによる自動トリガー、全バリアント共通)。
強制トリガーはバリアントごとに独立集計(全バリアント共通の1本の線には集約しない): t=5秒予定のreorganize()(細かい点線)、t=10秒・t=25秒予定のdefragment()(粗い点線)を、そのバリアント自身の色+マーカー形状(スキャンQPS線をホバーした際の点と同じ形。凡例のポイント形状も対応)で描画。
実際に発火する秒(線の位置)は予定秒とは限らない(将棋倒しに対するガード無し、書き込み負荷次第で数十秒遅延することがある)。8B In-Memoryのように挿入スループットが極端に高いワークロードでは、t=5秒予定のreorganize()自体が20秒以上かかり、後続のdefragment()トリガー(t=10s/25s)がこの30秒間に一度も発火しないまま終わることもある(この場合、線は1本しか出ない)。カーソルを線に合わせると、その秒に発火したバリアント・種類・予定秒・実発火秒・所要時間を表示。
Scan QPS タイムライン
32 threads / 30 secondsReorganize Duration vs. Corpus Size (T1-only vs T1+T2)
単発のreorganize()呼び出し1回の所要時間を、コーパスサイズ(件数)を横軸にスイープ計測。
藍色 = T1-only(T2は一切触らない)。赤色 = T1+T2、ただしブートストラップ(直前チェックポイントが存在しない新規コーパスへの初回defragment()。reflink元が無いのでO(N)。現行のreflink+punch実装そのものを、コールド状態で計測した数値であって別実装ではない)。緑色 = T1+T2 steady(既存世代からのreflink clone + hole punch、O(diff)の本来の定常状態。churn_ratio=0.01でのコーパスサイズ不変性実験、1KB LTMタブのみ)。
▲マーカーはタイムアウトを示す。