perf(storage): prune old block signatures with a range delete - #548
perf(storage): prune old block signatures with a range delete#548MegaRedHand wants to merge 2 commits into
Conversation
The per-import prune walked `BlockSignatures` from the head of the table on every call to collect the keys below the cutoff. Since the cutoff advances one slot at a time, each pass re-seeks past every tombstone left by the previous ones, so the work grows with chain height rather than with the handful of keys actually leaving the window. Delete the whole `[slot 0, cutoff)` range in one operation instead: the keys are `slot||root` in big-endian slot order, so the cutoff's bare slot prefix is an exact upper bound and RocksDB can drop the range with a single tombstone, never reading the table. The return value becomes the exclusive slot pruned below rather than a key count, which a range delete cannot report without reading back.
🤖 Kimi Code ReviewThe PR introduces a Code Quality & Correctness
Minor Observations
Security & Consensus
Verdict Approved. The implementation correctly models RocksDB range tombstones in the in-memory backend and safely optimizes the signature pruning path. Ensure the performance characteristics of range tombstones are monitored under heavy load. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code Review
The rest of the change looks mechanically sound: the in-memory backend now preserves write-batch call order, which is the right model for matching RocksDB semantics, and the slot-prefix range bound is correct for I couldn’t run the test suite in this environment because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR replaces per-key block-signature pruning with an ordered, half-open range-delete operation.
Confidence Score: 5/5The PR appears safe to merge, with no concrete correctness or security failures identified. The production and in-memory backends implement consistent half-open, call-ordered range deletion, and the slot-prefix bounds delete exactly the signatures below the finalized cutoff while retaining cutoff-slot entries.
|
| Filename | Overview |
|---|---|
| crates/storage/src/api/traits.rs | Adds a clearly documented half-open range-delete API with ordered batch semantics. |
| crates/storage/src/backend/in_memory.rs | Reworks pending operations into an ordered vector and correctly replays point and range operations in call order. |
| crates/storage/src/backend/rocksdb.rs | Implements range deletion through the corresponding RocksDB column-family write-batch operation. |
| crates/storage/src/backend/tests.rs | Adds cross-backend coverage for half-open bounds and a point put following a range deletion. |
| crates/storage/src/store.rs | Replaces signature-table scanning and point deletion with a correctly bounded range deletion while preserving the finalized retention window. |
Sequence Diagram
sequenceDiagram
participant Import as Block import
participant Store
participant Batch as StorageWriteBatch
participant Backend as RocksDB / In-memory
Import->>Store: prune_old_data(finalized_slot)
Store->>Store: "cutoff = tip_slot - pruning range"
alt cutoff is zero or above finalized slot
Store-->>Import: 0 (nothing pruned)
else eligible finalized range
Store->>Batch: delete_range(BlockSignatures, slot 0, cutoff)
Batch->>Backend: apply [0, cutoff)
Backend-->>Batch: range deletion recorded
Batch->>Backend: commit()
Store-->>Import: cutoff slot
end
Reviews (1): Last reviewed commit: "perf(storage): prune old block signature..." | Re-trigger Greptile
The helper wrapped a single `to_be_bytes()` call used in one place; fold its doc comment into the surrounding comment at the call site instead.
🤖 Claude Code ReviewReview:
|
Motivation
Store::prune_old_block_signaturesruns on every block import. It collected the keys to remove by walkingBlockSignaturesfrom the head of the table, taking entries belowcutoff = tip_slot - SIGNATURE_PRUNING_RANGE.The cutoff advances one slot at a time, so each pass deletes roughly one block's signatures while re-seeking past every tombstone the previous passes left behind. The scan's cost tracks chain height, not the handful of keys actually leaving the retention window.
Description
Adds
StorageWriteBatch::delete_range(table, from, to)(half-open[from, to)) and uses it to drop the whole finalized range in one operation:WriteBatch::delete_range_cf— one range tombstone, the table is never read.Vec<(Table, PendingOp)>instead of a per-keyHashMap, so a range delete interleaves with point operations the way RocksDB applies aWriteBatch. The range itself is applied withretain.BlockSignatureskeys areslot||rootin big-endian slot order, so the cutoff's bare 8-byte slot prefix is an exact upper bound: every key below the cutoff sorts before it, and every key at the cutoff sorts after it (they extend it with a root).Behavior change
prune_old_block_signaturesnow returns the exclusive slot it pruned below (0= nothing pruned) instead of a key count, which a range delete cannot report without reading the table back. The log field inprune_old_datais nowpruned_below_slot. Also returns early when the cutoff saturates to 0, so no empty tombstone is written on a young chain.How to test
cargo test -p ethlambda-storage— the three existing pruning tests still assert the surviving entries; two new cases were added to the shared backend suite (run against both backends): half-open range bounds, and aput_batchafter adelete_rangein the same batch winning.make lint+cargo test --workspace(including fork choice / signature / STF spec tests) pass.