レッスン2 — Reth のパイプライン: 10 ステージ、順番付き
問い
ノードがネットワーク参加 1 分後、10M ブロック分のヘッダーを引っ張った。1 ブロックずつ処理 = 数週間 vs Reth の Staged Sync = 数時間 — トリックは「1 つの 操作 を数千ブロックにまたがって処理」。10 ステージ、固定順序、順序は恣意的ではない — どの制約がどの順序を強いるか?
原理(最小モデル)
- 10 ステージのパイプライン. Header → Body → SenderRecovery → Execution → AccountHashing → StorageHashing → Merkle → TransactionLookup → IndexHistory → Finish。
- 3 制約が順序を規定. Merkle はハッシング後 + AccountHashing/StorageHashing は理論並列だが順次実行(MDBX 競合 + DAG 複雑性)+ SenderRecovery が並列化の最大勝ち。
- MerkleStage がハッシング後である理由. ソート済みハッシュ化キーを消費 → 全ソート集合が必要 → インターリーブ不可。
- AccountHashing と StorageHashing が並列実行されない理由. MDBX 書き込み競合 + パイプライン単純性(DAG スケジューラ複雑化)。
- SenderRecoveryStage が並列化の勝ち. 巨大バッチサイズ(10-30M 署名)+ データ依存なし + 純粋計算。Rayon で全 CPU コア展開。
- ExecutionStage は順次の状態依存. ブロック N のストレージ書き込みがブロック N+1 の読み込みに影響 → Optimistic execution(Block-STM)なしでは並列化困難。
- 100× 高速化の 3 要因. バッチ化(~10×)+ ステージ内並列化(~10×)+ I/O 償却(~3×)。
具体例
10 ステージのフロー:
flowchart LR
H[HeaderStage] --> B[BodyStage]
B --> S[SenderRecoveryStage]
S --> E[ExecutionStage]
E --> AH[AccountHashingStage]
AH --> SH[StorageHashingStage]
SH --> M[MerkleStage]
M --> T[TransactionLookupStage]
T --> I[IndexHistoryStages]
I --> F[FinishStage]
| # | ステージ | 何をする | ホットループ |
|---|---|---|---|
| 1 | HeaderStage | ブロックヘッダーをダウンロード | ネットワーク I/O |
| 2 | BodyStage | tx 本体 + uncle をダウンロード | ネットワーク I/O |
| 3 | SenderRecoveryStage | 各 tx の sender を ECDSA で復元 | CPU(並列) |
| 4 | ExecutionStage | Revm を走らせ、状態差分を蓄積 | CPU(Revm) |
| 5 | AccountHashingStage | アカウント変更をハッシュ化キーでソート | sort + write |
| 6 | StorageHashingStage | ストレージ変更をハッシュ化キーでソート | sort + write |
| 7 | MerkleStage | MPT ルートを更新 | tree compute |
| 8 | TransactionLookupStage | tx_hash → (block, index) インデックス | sort + write |
| 9 | IndexAccount/StorageHistoryStage | 履歴アクセスインデックス | sort + write |
| 10 | FinishStage | 帳簿付け、確定 | なし |
3 順序制約:
制約 1: MerkleStage はハッシング後:
- Merkle はソート済みハッシュ化キーを消費
- AccountHashing/StorageHashing が Merkle 開始前にソート完了 + commit 必要
- インターリーブ不可 = Merkle が処理するブロック範囲について全ソート集合が必要
制約 2: AccountHashing と StorageHashing は順次実行(理論並列だが):
- 両方 ExecutionStage 出力を消費、独立ソート済み変更集合を生成 → 理論並列可能
- なぜ順次? ① MDBX 書き込み競合(並列でロック争い、計算面の利得なし)+ ② パイプライン単純性(並列分岐 = DAG スケジューラ複雑化、利得限界的)
制約 3: SenderRecoveryStage が並列化の最大勝ち:
- ECDSA 復元 = 純粋 CPU、共有状態なし、embarrassingly parallel
- Rayon で全 CPU コア展開
- 巨大バッチサイズ: 各ブロック 100-300 tx × 100K+ ブロック = 1 呼び出しで 10-30M 署名
- データ依存なし + 純粋計算
ExecutionStageは順次状態依存(ブロック N のストレージ書き込み → ブロック N+1 の読み込み)→ Optimistic execution(Block-STM)なしでは並列化困難
100× 高速化の内訳:
- バッチ化 ~10×: Sender 復元 + ハッシング + Merkle ルート計算が 1 呼び出しで数千ブロックに償却
- ステージ内並列化 ~10×: 特に SenderRecoveryStage の Rayon 全コア展開
- I/O 償却 ~3×: ディスク書き込みがブロックごとではなくステージ境界の大きなソート済みバッチ
- 掛け合わせ ~300× 理論値、実際は 100-200× に着地(ハードウェア依存)
失敗例(誤解)
「Merkle ステージはハッシング中にインターリーブ可能」— 間違い。Merkle はソート済みハッシュ化キーを消費 → 全ソート集合が必要 → AccountHashing/StorageHashing が完全に commit 完了してから Merkle が始まる。
「並列化 = どこでも勝つ」— 間違い。AccountHashing/StorageHashing は理論並列だが MDBX 書き込み競合で利得なし + パイプライン複雑化。並列化は条件付きの勝ち:embarrassingly parallel + データ依存なし + 純粋計算が揃ったときだけ。
「ExecutionStage を Block-STM で並列化すれば 10×」— 可能だが複雑。ブロック N のストレージ書き込み → ブロック N+1 の読み込み = 順次状態依存。Block-STM は投機的並列実行 + 衝突再実行で並列化可能だが独自のコンセンサス的複雑性を伴う。
ステップで組み立てる
Step 1: 10 ステージを順に言える
Header → Body → SenderRecovery → Execution → AccountHashing → StorageHashing → Merkle → TransactionLookup → IndexHistory → Finish。
Step 2: 3 順序制約
Merkle はハッシング後 / AccountHashing と StorageHashing は順次(MDBX 競合 + DAG 複雑性)/ SenderRecovery が並列化の最大勝ち。
Step 3: なぜ SenderRecovery が勝つか
巨大バッチ + データ依存なし + 純粋計算 = Rayon で全コア展開。
Step 4: ExecutionStage が並列化困難な理由
順次状態依存(ブロック N → N+1)、Block-STM は可能だが複雑。
Step 5: 100× の 3 要因
バッチ化 ~10× + ステージ内並列化 ~10× + I/O 償却 ~3× = 掛けて ~300×、実際 100-200×。
答え合わせ
- AccountHashing と StorageHashing を並列実行しない理由: ① ディスク書き込み競合(両方 MDBX に書く → ロック争い → 計算面の利得を打ち消す)、② パイプライン単純性(並列分岐 = DAG スケジューラが必要 → 複雑性増 vs 利得限界的)。Frontiers 2025 トークがこのトレードオフを論じる。
- SenderRecovery がパイプラインで特別な理由: ① 巨大バッチ(100K+ ブロック × 100-300 tx = 10-30M 署名 / 呼び出し)、② データ依存なし(各復元独立)、③ 純粋計算(復元の合間に I/O なし)。Rayon で全コア展開できる embarrassingly parallel の教科書例。
- 100× 高速化の帰属: バッチ化(~10×)が ECDSA セットアップを 100K+ ブロックに償却 + Merkle ルートを範囲ごとに 1 回(ブロックごとではない)でさらに ~10× + MDBX が効率的に書ける大きなソート済みバッチで ~3× = 掛けて ~300× 理論、ハードウェア依存で実際 100-200×。
合格基準
- 10 ステージを順に即答できる。
- 3 順序制約を言える。
- SenderRecovery が並列化の勝ちである 3 理由を言える。
- ExecutionStage が並列化困難な順次状態依存を 1 文で説明できる。
- 100× 高速化を 3 要因に分解できる。
まとめ(3行)
- 10 ステージパイプライン(Header → Body → SenderRecovery → Execution → AccountHashing → StorageHashing → Merkle → TransactionLookup → IndexHistory → Finish)、固定順序、3 制約が順序を規定。
- SenderRecovery が並列化最大勝ち(embarrassingly parallel + Rayon 全コア)、AccountHashing/StorageHashing は理論並列だが順次(MDBX 競合 + DAG 複雑性)、ExecutionStage は順次状態依存。
- 100× 高速化 = バッチ化 ~10× × ステージ内並列化 ~10× × I/O 償却 ~3× = ~300× 理論、実際 100-200×。