Skip to content

Commit 15ecaf7

Browse files
committed
perf(storage): restore the range delete in prune_old_block_proofs
The merge with main resolved the BlockSignatures -> BlockProof rename by keeping the pre-lambdaclass#548 body: a full-table scan collecting keys below the cutoff plus a delete_batch. That scan's cost tracks chain height instead of the handful of keys leaving the retention window, since every pass re-seeks past the tombstones the previous passes left behind, which is what lambdaclass#548 removed. Reapply the range delete on the renamed table: the delete_range plumbing survived the merge, only its caller was lost. Prune again returns the exclusive slot it pruned below (0 = nothing pruned), which a range delete can report without reading the table back, and returns early when the cutoff saturates to 0 so a young chain writes no empty tombstone.
1 parent 37afe60 commit 15ecaf7

1 file changed

Lines changed: 30 additions & 34 deletions

File tree

crates/storage/src/store.rs

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -934,11 +934,11 @@ impl Store {
934934
.map_or(finalized_slot, |header| {
935935
header.expect("Failed to get block header").slot
936936
});
937-
let pruned_proofs = self
937+
let pruned_below_slot = self
938938
.prune_old_block_proofs(finalized_slot, tip_slot)
939939
.expect("prune old block proofs");
940-
if pruned_proofs > 0 {
941-
info!(pruned_proofs, "Pruned old finalized block proofs");
940+
if pruned_below_slot > 0 {
941+
info!(pruned_below_slot, "Pruned old finalized block proofs");
942942
}
943943
Ok(())
944944
}
@@ -1103,41 +1103,37 @@ impl Store {
11031103
/// reverted, so their proofs are not needed for fork choice, re-org
11041104
/// safety, or re-aggregation once outside the window.
11051105
///
1106-
/// Returns the number of proofs pruned.
1106+
/// Returns the exclusive slot below which proofs were dropped, or 0 when
1107+
/// nothing was pruned. This is a range delete, so the count of removed keys
1108+
/// is not known without reading the table back.
11071109
pub fn prune_old_block_proofs(
11081110
&mut self,
11091111
finalized_slot: u64,
11101112
tip_slot: u64,
1111-
) -> Result<usize, Error> {
1113+
) -> Result<u64, Error> {
11121114
let cutoff = tip_slot.saturating_sub(BLOCK_PROOF_PRUNING_RANGE);
11131115
// Only prune when the whole window is finalized; never touch
1114-
// non-finalized proofs.
1115-
if cutoff > finalized_slot {
1116+
// non-finalized proofs. A zero cutoff covers nothing.
1117+
if cutoff > finalized_slot || cutoff == 0 {
11161118
return Ok(0);
11171119
}
11181120

1119-
let view = self.backend.begin_read().expect("read view");
1120-
1121-
// Keys are slot||root in big-endian slot order, so iteration ascends by
1122-
// slot: take entries below the cutoff and stop at the first one past it.
1123-
let keys_to_delete: Vec<Vec<u8>> = view
1124-
.prefix_iterator(Table::BlockProof, &[])
1125-
.expect("iterator")
1126-
.filter_map(|res| res.ok())
1127-
.map(|(key, _)| key.to_vec())
1128-
.take_while(|key| decode_slot_root_key(key).0 < cutoff)
1129-
.collect();
1130-
drop(view);
1121+
// Keys are slot||root in big-endian slot order, so the cutoff's bare
1122+
// slot prefix is an exact upper bound: keys below the cutoff sort
1123+
// before it, and keys at the cutoff sort after it (they extend it with
1124+
// a root). A single range delete drops them all without reading the
1125+
// table (and without walking the tombstones left by earlier prunes).
1126+
let mut batch = self.backend.begin_write().expect("write batch");
1127+
batch
1128+
.delete_range(
1129+
Table::BlockProof,
1130+
&0u64.to_be_bytes(),
1131+
&cutoff.to_be_bytes(),
1132+
)
1133+
.expect("delete finalized block proofs");
1134+
batch.commit().expect("commit");
11311135

1132-
let count = keys_to_delete.len();
1133-
if count > 0 {
1134-
let mut batch = self.backend.begin_write().expect("write batch");
1135-
batch
1136-
.delete_batch(Table::BlockProof, keys_to_delete)
1137-
.expect("delete finalized block proofs");
1138-
batch.commit().expect("commit");
1139-
}
1140-
Ok(count)
1136+
Ok(cutoff)
11411137
}
11421138

11431139
/// Get the block header by root.
@@ -1980,12 +1976,12 @@ mod tests {
19801976
// tip = range + 10, finalized = range + 5, so cutoff = tip - range = 10.
19811977
let tip_slot = BLOCK_PROOF_PRUNING_RANGE + 10;
19821978
let finalized_slot = BLOCK_PROOF_PRUNING_RANGE + 5;
1983-
let pruned = store
1979+
let pruned_below_slot = store
19841980
.prune_old_block_proofs(finalized_slot, tip_slot)
19851981
.expect("prune");
19861982

19871983
// cutoff = 10: slots 0..9 pruned, slots 10..12 kept (within the window).
1988-
assert_eq!(pruned, 10);
1984+
assert_eq!(pruned_below_slot, 10);
19891985
assert_eq!(count_entries(backend.as_ref(), Table::BlockProof), 3);
19901986

19911987
// Oldest proofs are gone, but headers, bodies, and roots stay queryable.
@@ -2015,10 +2011,10 @@ mod tests {
20152011
// cutoff = tip - range > finalized → prune nothing.
20162012
let tip_slot = BLOCK_PROOF_PRUNING_RANGE + 100;
20172013
let finalized_slot = 5;
2018-
let pruned = store
2014+
let pruned_below_slot = store
20192015
.prune_old_block_proofs(finalized_slot, tip_slot)
20202016
.expect("prune");
2021-
assert_eq!(pruned, 0);
2017+
assert_eq!(pruned_below_slot, 0);
20222018
assert_eq!(count_entries(backend.as_ref(), Table::BlockProof), 10);
20232019
}
20242020

@@ -2033,8 +2029,8 @@ mod tests {
20332029

20342030
// Early chain: tip < BLOCK_PROOF_PRUNING_RANGE → cutoff saturates to 0,
20352031
// so nothing is old enough to prune even though slots are finalized.
2036-
let pruned = store.prune_old_block_proofs(9, 9).expect("prune");
2037-
assert_eq!(pruned, 0);
2032+
let pruned_below_slot = store.prune_old_block_proofs(9, 9).expect("prune");
2033+
assert_eq!(pruned_below_slot, 0);
20382034
assert_eq!(count_entries(backend.as_ref(), Table::BlockProof), 10);
20392035
}
20402036

0 commit comments

Comments
 (0)