uqa_storage/key_value/diskann/
reclamation.rs1use crate::diskann_index::format::DiskANNGeneration;
10use crate::key_value::KeyValueRead;
11use crate::read_control::StorageReadControl;
12use crate::{KeyValueBatch, StorageBackendResult};
13
14use super::keys::{database_key, Keys, Kind};
15use super::source::{key_page, KEY_PAGE_LIMIT};
16use super::staging::load_state;
17use super::state::{StageOwner, State};
18use super::{invalid, DiskANNStageStatus, KeyValueDiskANNStore};
19
20#[derive(Clone, Copy, PartialEq, Eq)]
21pub(super) enum Reclamation {
22 Complete,
23 More,
24 Retained,
25}
26
27#[derive(Clone, Copy, PartialEq, Eq)]
28pub(super) enum ReclamationMode {
29 Explicit,
30 Maintenance,
31}
32
33impl KeyValueDiskANNStore {
34 pub fn reclaim_abandoned_step(
36 &self,
37 generation: DiskANNGeneration,
38 max_records: usize,
39 control: &StorageReadControl,
40 ) -> StorageBackendResult<bool> {
41 self.reclaim_generation(generation, max_records, ReclamationMode::Explicit, control)
42 .map(|result| result == Reclamation::Complete)
43 }
44
45 pub(super) fn reclaim_generation(
46 &self,
47 generation: DiskANNGeneration,
48 max_records: usize,
49 mode: ReclamationMode,
50 control: &StorageReadControl,
51 ) -> StorageBackendResult<Reclamation> {
52 control.check()?;
53 if max_records == 0 {
54 return Err(invalid("reclamation requires a positive record limit"));
55 }
56 let mut observed = None;
57 {
58 let _writer = self.owner.writer.lock();
59 self.idle(control)?;
60 self.owner.store.with_read_view(&mut |read| {
61 observed = load_state(read, generation, control)?;
62 Ok(())
63 })?;
64 }
65 let Some(state) = observed else {
66 return self
67 .reclaim_retired_step(generation, max_records, control)
68 .map(Reclamation::from);
69 };
70 match state.status {
71 DiskANNStageStatus::Published => {
72 if mode == ReclamationMode::Maintenance {
73 return Ok(Reclamation::Retained);
74 }
75 return Err(invalid("published generation cannot be abandoned"));
76 }
77 DiskANNStageStatus::Retired | DiskANNStageStatus::Discarding => {
78 return self
79 .reclaim_retired_step(generation, max_records, control)
80 .map(Reclamation::from);
81 }
82 DiskANNStageStatus::Writing
83 | DiskANNStageStatus::Frozen
84 | DiskANNStageStatus::Sealed => {}
85 }
86 let lease = match state.owner {
87 StageOwner::Legacy(_) => {
88 let Some(legacy) = self.legacy_guard(control)? else {
89 return Ok(Reclamation::Retained);
90 };
91 let lease = self.reserve_owner(control)?;
92 Self::retain_transition(&lease, legacy, control)?
93 }
94 StageOwner::Leased(_) => {
95 let Some(lease) = self.acquire_owner(
96 state.owner,
97 crate::mvcc::ResourceLeaseRequest::Recover,
98 control,
99 )?
100 else {
101 return Ok(Reclamation::Retained);
102 };
103 lease
104 }
105 };
106 let keys = Keys::new(generation);
107 let mut result = Reclamation::Retained;
108 self.mutate_owned(Some(&lease), control, &mut |read, batch| {
109 if load_state(read, generation, control)? != Some(state) {
110 if mode == ReclamationMode::Maintenance {
111 return Ok(());
112 }
113 return Err(invalid(
114 "generation changed during abandoned-owner acquisition",
115 ));
116 }
117 if read
118 .record_revision(keys.key(Kind::State).as_ref())?
119 .and_then(|revision| revision.observed_commit(self.owner.database))
120 .is_none()
121 {
122 return Err(invalid("abandonment requires a committed state revision"));
123 }
124 result = Reclamation::from(delete_page(
125 read,
126 batch,
127 keys,
128 State {
129 owner: StageOwner::Leased(lease.id().allocation()),
130 ..state
131 },
132 max_records,
133 control,
134 )?);
135 Ok(())
136 })?;
137 Ok(result)
138 }
139
140 pub fn reclaim_retired_step(
142 &self,
143 generation: DiskANNGeneration,
144 max_records: usize,
145 control: &StorageReadControl,
146 ) -> StorageBackendResult<bool> {
147 control.check()?;
148 if max_records == 0 {
149 return Err(invalid("reclamation requires a positive record limit"));
150 }
151 let keys = Keys::new(generation);
152 let state_key = keys.key(Kind::State);
153 let mut complete = false;
154 self.mutate(control, &mut |read, batch| {
155 let Some(state) = load_state(read, generation, control)? else {
156 if read.contains_prefix_budgeted(keys.prefix(), control)? {
157 return Err(invalid("generation has orphaned records"));
158 }
159 complete = true;
160 return Ok(());
161 };
162 if !matches!(
163 state.status,
164 DiskANNStageStatus::Retired | DiskANNStageStatus::Discarding
165 ) {
166 return Err(invalid("generation has no committed reclamation authority"));
167 }
168 if read
169 .record_revision(state_key.as_ref())?
170 .and_then(|revision| revision.observed_commit(self.owner.database))
171 .is_none()
172 {
173 return Err(invalid("reclamation requires a committed state revision"));
174 }
175 complete = delete_page(read, batch, keys, state, max_records, control)?;
176 Ok(())
177 })?;
178 Ok(complete)
179 }
180}
181
182impl From<bool> for Reclamation {
183 fn from(complete: bool) -> Self {
184 if complete {
185 Self::Complete
186 } else {
187 Self::More
188 }
189 }
190}
191
192pub(super) fn delete_page(
194 read: &dyn KeyValueRead,
195 batch: &mut dyn KeyValueBatch,
196 keys: Keys,
197 state: State,
198 max_records: usize,
199 control: &StorageReadControl,
200) -> StorageBackendResult<bool> {
201 let limit = max_records.min(KEY_PAGE_LIMIT);
202 let state_key = keys.key(Kind::State);
203 let page = key_page(read, keys, Some(state_key.as_ref()), limit, control)?;
204 batch.require_unchanged(&database_key())?;
205 batch.require_unchanged(state_key.as_ref())?;
206 for (key, _) in page.iter() {
207 control.check()?;
208 batch.delete(key.as_ref())?;
209 }
210 let complete = page.len() < limit;
211 if complete {
212 batch.delete(state_key.as_ref())?;
213 } else {
214 batch.put(
215 state_key.as_ref(),
216 &State {
217 status: DiskANNStageStatus::Discarding,
218 ..state
219 }
220 .encode(),
221 )?;
222 }
223 control.check()?;
224 Ok(complete)
225}