perf(storage): derive history indices from in-memory state#21138
perf(storage): derive history indices from in-memory state#21138
Conversation
Based on #eng-perf Slack discussions identifying key bottlenecks: - update_history_indices: 26% of persist time - write_trie_updates: 25.4% - write_trie_changesets: 24.2% - Execution cache contention under high throughput New benchmarks: - execution_cache: cache hit rates, contention, TIP-20 patterns - heavy_persistence: accumulated blocks, history indices, state root - heavy_root: parallel vs sync at scale, large storage tries Includes runner script and optimization opportunities doc.
Previously, `update_history_indices` scanned the AccountChangeSets and StorageChangeSets database tables to build history indices after writing state. This was ~26% of persistence time per profiling in #eng-perf. This change collects account/storage transitions from the in-memory ExecutionOutcome during the block processing loop, avoiding the expensive DB cursor scans entirely. The history indices are then written directly from the pre-collected in-memory data. Optimization applies when: - save_mode.with_state() is true - History is not in RocksDB (MDBX path) Expected improvement: ~26% reduction in update_history_indices time.
Local Benchmark Results (containerized env, high variance)Ran To properly benchmark this change, it needs to run in Recommended next step: Run with profiling on a reth box: samply record -- cargo bench -p reth-engine-tree --bench heavy_persistenceOr test via |
Benchmark Results (Local)Ran benchmarks on local machine (not reth box - results are relative, not absolute perf): History Indices Insert Benchmark
The benchmark measures the insert operation itself. The optimization's impact is in avoiding the DB cursor scan to collect the data - that part isn't captured in this isolated benchmark. Full Impact AssessmentThe real-world impact will be visible in end-to-end Recommended next step: Run on a reth box with real mainnet state to measure actual DB scan elimination. |
Correctness Concerns to VerifyReviewed the approach — architecturally sound, but a few things to confirm before merging: 1. Slot key encodingPR uses 2. Storage map semanticsDoes 3. Net-noop modificationsAccount/slot modified then reverted to original value within a block — does Recommendation: Add an equivalence test that runs both old (DB-scan) and new (in-memory) paths on a fixture and asserts identical The 26% improvement claim is plausible since it eliminates cursor IO overhead. The direction is solid. |
Two correctness fixes for the in-memory history index collection: 1. Account history: Only collect accounts where info != original_info. Previously used is_not_modified() which includes accounts that only had storage changes (no account changeset written for those). 2. Storage history: Only collect slots where is_changed() returns true. Previously iterated all storage slots including just-accessed ones. These fixes ensure the in-memory optimization produces the same indices as the DB-scan approach would have.
Adds a test that verifies the in-memory history index collection (used in save_blocks optimization) produces identical results to the DB-scan approach. The test covers three account modification patterns: - Account with only info changes (balance) -> should have account changeset - Account with only storage changes -> should NOT have account changeset - Account with both info and storage changes -> should have both changesets This ensures the optimization that derives history indices from ExecutionOutcome matches what would have been produced by scanning AccountChangeSets and StorageChangeSets tables.
|
Hey! We're doing some spring cleaning on our PR backlog 🧹 Closing old PRs to keep things tidy. If this is still relevant, please feel free to re-open — we appreciate your contribution! |
Summary
Derives history indices directly from in-memory
ExecutionOutcomeinstead of scanning database tables.Problem
Per #eng-perf profiling,
update_history_indiceswas taking 26% of persistence time. The current implementation:AccountChangeSetsandStorageChangeSetstables via cursor to build history indicesSolution
Collect account/storage transitions from the in-memory
ExecutionOutcomeduring the block processing loop, avoiding the expensive DB cursor scans entirely.Expected Impact
update_history_indicestimeTesting
reth-providertests passcargo bench -p reth-engine-tree --bench heavy_persistence -- history_indicesRelated
georgios/heavy-benchmarksbranch for benchmark harness