uqa_storage/mvcc/memory/
reclamation.rs1use std::ops::Bound::{Excluded, Included, Unbounded};
8
9use uqa_core::memory::BudgetedVec;
10
11use crate::mvcc::{
12 TombstoneReclamationRequest, TombstoneReclamationStep, VersionError, VersionResult,
13 TOMBSTONE_RECLAMATION_PAGE,
14};
15use crate::read_control::StorageReadControl;
16
17use super::{MemoryReservation, MemoryVersionStore, RecordKey};
18
19impl MemoryVersionStore {
20 pub fn reclaim_tombstones(
22 &self,
23 request: &TombstoneReclamationRequest<'_>,
24 control: &StorageReadControl,
25 ) -> VersionResult<TombstoneReclamationStep> {
26 request.validate(control)?;
27 let mut state = self.database.state.lock();
28 if !state.snapshots.is_empty() {
29 return Ok(TombstoneReclamationStep::Retained);
30 }
31 if request.through > state.sequence {
32 return Err(VersionError::InvalidEncoding(
33 "tombstone cutoff exceeds committed visibility",
34 ));
35 }
36 let mut keys = BudgetedVec::new(control.memory());
37 keys.reserve(TOMBSTONE_RECLAMATION_PAGE)?;
38 let mut inspected = 0;
39 let mut after = BudgetedVec::new(control.memory());
40 let start = request.after.map_or(Included(request.prefix), Excluded);
41 for (key, entry) in state.records.range::<[u8], _>((start, Unbounded)) {
42 control.check()?;
43 if !key.bytes().starts_with(request.prefix) {
44 break;
45 }
46 let head = entry
47 .history
48 .head()
49 .ok_or(VersionError::InvalidEncoding("record head has no history"))?;
50 if head.sequence() > request.through {
51 continue;
52 }
53 inspected += 1;
54 after.clear();
55 after.extend_from_slice(key.bytes())?;
56 if head.value().is_none() && entry.history.len() == 1 {
57 keys.push(key.clone())?;
58 }
59 if inspected == TOMBSTONE_RECLAMATION_PAGE {
60 break;
61 }
62 }
63 let removed = keys.len();
64 if removed != 0 {
65 let epoch = state
66 .reclamation_epoch
67 .checked_add(1)
68 .ok_or(VersionError::IdentifiersExhausted)?;
69 let domain = if state.reclamation_domains.contains_key(request.prefix) {
70 None
71 } else {
72 Some((
73 RecordKey::new(request.prefix, &self.database.memory)?,
74 self.database
75 .memory
76 .reserve(std::mem::size_of::<(RecordKey, (u64, MemoryReservation))>())?,
77 ))
78 };
79 control.check()?;
80 if let Some((prefix, memory)) = domain {
81 state.reclamation_domains.insert(prefix, (epoch, memory));
82 } else {
83 state
84 .reclamation_domains
85 .get_mut(request.prefix)
86 .expect("existing domain")
87 .0 = epoch;
88 }
89 state.reclamation_epoch = epoch;
90 for key in keys.iter() {
91 state.records.remove(key.bytes());
92 }
93 }
94 Ok(if inspected == TOMBSTONE_RECLAMATION_PAGE {
95 TombstoneReclamationStep::More { after, removed }
96 } else {
97 TombstoneReclamationStep::Complete { removed }
98 })
99 }
100}