uqa_storage/diskann_index/
memory.rs1mod build;
10mod source;
11#[cfg(test)]
12mod tests;
13
14use super::{
15 build::DiskANNTemporaryBudget,
16 format::{DiskANNGeneration, DiskANNManifest, DiskANNVectorVersion},
17 RetainedDiskANNIndex,
18};
19use crate::{
20 mvcc::{DatabaseId, StorageTransactionId},
21 read_control::StorageReadControl,
22 StorageBackendError, StorageBackendResult, VectorIndex,
23};
24use source::Canonical;
25use std::sync::{
26 atomic::{AtomicU64, Ordering},
27 Arc,
28};
29use uqa_core::{
30 memory::{Budgeted, MemoryReservation},
31 DocId, PostingList,
32};
33
34pub type DiskANNMemoryOptions = super::DiskANNIndexOptions;
36
37struct Clock {
38 database: DatabaseId,
39 next: AtomicU64,
40}
41
42impl Clock {
43 fn allocate(&self) -> StorageBackendResult<u64> {
44 uqa_core::atomic::try_update_u64(&self.next, Ordering::Relaxed, Ordering::Relaxed, |next| {
45 next.checked_add(1)
46 })
47 .map_err(|_| StorageBackendError::Other("DiskANN memory identity exhausted".into()))
48 }
49 fn generation(&self) -> StorageBackendResult<DiskANNGeneration> {
50 DiskANNGeneration::new(self.database.as_bytes(), 1, 1, self.allocate()?)
51 }
52 fn version(&self) -> StorageBackendResult<DiskANNVectorVersion> {
53 let writer = StorageTransactionId::new(self.database, 1)
55 .map_err(crate::mvcc::VersionError::into_storage_error)?;
56 DiskANNVectorVersion::new(writer, self.allocate()?)
57 }
58}
59
60pub struct DiskANNMemoryIndex {
62 index: RetainedDiskANNIndex<Canonical>,
63 clock: Arc<Budgeted<Clock>>,
64 options: DiskANNMemoryOptions,
65 temporary: DiskANNTemporaryBudget,
66 control: StorageReadControl,
67 _memory: MemoryReservation,
68}
69
70impl DiskANNMemoryIndex {
71 pub fn new(
72 dimensions: u32,
73 options: DiskANNMemoryOptions,
74 temporary: &DiskANNTemporaryBudget,
75 control: &StorageReadControl,
76 ) -> StorageBackendResult<Self> {
77 control.check()?;
78 options.parameters.validate(dimensions)?;
79 let memory = control.memory().reserve(size_of::<Self>())?;
80 let database = DatabaseId::from_bytes(crate::catalog::new_nonzero_catalog_identity(
81 "DiskANN memory",
82 "incarnation",
83 )?);
84 let clock = Budgeted::new(
85 Clock {
86 database,
87 next: AtomicU64::new(1),
88 },
89 control.memory().empty_reservation(),
90 )
91 .into_shared()?;
92 let index = build::prepare(
93 Canonical::empty(dimensions, control),
94 clock.generation()?,
95 options,
96 temporary,
97 control,
98 )?;
99 Ok(Self {
100 index,
101 clock,
102 options,
103 temporary: temporary.clone(),
104 control: control.clone(),
105 _memory: memory,
106 })
107 }
108
109 pub fn manifest(&self) -> &DiskANNManifest {
110 self.index.manifest()
111 }
112
113 fn fork(&self) -> StorageBackendResult<Self> {
114 self.control.check()?;
115 let memory = self.control.memory().reserve(size_of::<Self>())?;
116 Ok(Self {
117 index: self.index.clone(),
118 clock: self.clock.clone(),
119 options: self.options,
120 temporary: self.temporary.clone(),
121 control: self.control.clone(),
122 _memory: memory,
123 })
124 }
125
126 fn replace(
127 &mut self,
128 document: DocId,
129 values: Vec<Vec<f32>>,
130 memory: MemoryReservation,
131 ) -> StorageBackendResult<()> {
132 self.control.check()?;
133 let canonical =
134 self.index
135 .canonical()
136 .replaced(document, values, self.clock.version()?, memory)?;
137 let candidate = self.index.with_canonical(canonical)?;
138 self.control.check()?;
139 self.index = candidate;
140 Ok(())
141 }
142
143 fn rebuild(&mut self, canonical: Canonical) -> StorageBackendResult<()> {
144 self.control.check()?;
145 let candidate = build::prepare(
146 canonical,
147 self.clock.generation()?,
148 self.options,
149 &self.temporary,
150 &self.control,
151 )?;
152 self.control.check()?;
153 self.index = candidate;
154 Ok(())
155 }
156}
157
158impl VectorIndex for DiskANNMemoryIndex {
159 fn dimensions(&self) -> u32 {
160 self.index.dimensions()
161 }
162 fn index_kind(&self) -> &'static str {
163 "diskann"
164 }
165 fn diskann_query_metadata(
166 &self,
167 control: &StorageReadControl,
168 ) -> StorageBackendResult<Option<super::DiskANNQueryMetadata>> {
169 self.index.diskann_query_metadata(control)
170 }
171 fn add(&mut self, document: DocId, vector: Vec<f32>) -> StorageBackendResult<()> {
172 self.control.check()?;
174 let header = self.control.memory().reserve(size_of::<Vec<f32>>())?;
175 let values = vec![vector];
176 self.replace(document, values, header)
177 }
178 fn add_many(&mut self, document: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()> {
179 self.replace(document, vectors, self.control.memory().empty_reservation())
180 }
181 fn delete(&mut self, document: DocId) -> StorageBackendResult<()> {
182 if !self.index.contains_document(document)? {
183 return Ok(());
184 }
185 self.replace(
186 document,
187 Vec::new(),
188 self.control.memory().empty_reservation(),
189 )
190 }
191 fn clear(&mut self) -> StorageBackendResult<()> {
192 self.rebuild(Canonical::empty(self.dimensions(), &self.control))
193 }
194 fn initialize(&mut self) -> StorageBackendResult<()> {
195 self.rebuild(self.index.canonical().clone())
196 }
197 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList> {
198 self.index.search_knn(query, k)
199 }
200 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList> {
201 self.index.search_threshold(query, threshold)
202 }
203 fn search_knn_with_control(
204 &self,
205 query: &[f32],
206 k: usize,
207 control: &StorageReadControl,
208 ) -> StorageBackendResult<PostingList> {
209 self.index.search_knn_with_control(query, k, control)
210 }
211 fn search_threshold_with_control(
212 &self,
213 query: &[f32],
214 threshold: f32,
215 control: &StorageReadControl,
216 ) -> StorageBackendResult<PostingList> {
217 self.index
218 .search_threshold_with_control(query, threshold, control)
219 }
220 fn search_knn_with_statistics(
221 &self,
222 query: &[f32],
223 k: usize,
224 control: Option<&StorageReadControl>,
225 ) -> StorageBackendResult<crate::vector_index::VectorQueryResult> {
226 self.index.search_knn_with_statistics(query, k, control)
227 }
228 fn search_threshold_with_statistics(
229 &self,
230 query: &[f32],
231 threshold: f32,
232 control: Option<&StorageReadControl>,
233 ) -> StorageBackendResult<crate::vector_index::VectorQueryResult> {
234 self.index
235 .search_threshold_with_statistics(query, threshold, control)
236 }
237 fn count(&self) -> StorageBackendResult<usize> {
238 self.index.count()
239 }
240 fn contains_document(&self, document: DocId) -> StorageBackendResult<bool> {
241 self.index.contains_document(document)
242 }
243 fn diskann_read_snapshot(
244 &self,
245 control: &StorageReadControl,
246 ) -> StorageBackendResult<Option<super::DiskANNReadSnapshot>> {
247 control.check()?;
248 self.index.diskann_read_snapshot(control)
249 }
250 fn snapshot_with_diskann_changes(
251 &self,
252 changes: &super::DiskANNReadChanges,
253 control: &StorageReadControl,
254 ) -> StorageBackendResult<Option<Arc<dyn VectorIndex>>> {
255 control.check()?;
256 self.index.snapshot_with_diskann_changes(changes, control)
257 }
258 fn snapshot_with_vector_read(
259 &self,
260 source: crate::vector_index::VectorReadSnapshot,
261 control: &StorageReadControl,
262 ) -> StorageBackendResult<Option<Arc<dyn VectorIndex>>> {
263 self.index.snapshot_with_vector_read(source, control)
264 }
265 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>> {
266 self.index.snapshot()
267 }
268 fn snapshot_with_control(
269 &self,
270 control: &StorageReadControl,
271 ) -> StorageBackendResult<Arc<dyn VectorIndex>> {
272 self.index.snapshot_with_control(control)
273 }
274 fn writable_snapshot(&self) -> StorageBackendResult<Box<dyn VectorIndex>> {
275 Ok(Box::new(self.fork()?))
276 }
277}