1use std::sync::Arc;
10
11use uqa_core::{memory::Budgeted, DocId, PostingList};
12
13use super::codec::{other_error, vector_field_prefix};
14use super::index_keys::{
15 hnsw_metadata_key, hnsw_node_prefix, ivf_assignment_prefix, ivf_centroid_prefix,
16 ivf_metadata_key,
17};
18use super::index_view::{read_view, IndexState, IndexView};
19use super::ivf_persistence;
20use super::{KeyValueBatch, KeyValueRead, KeyValueStore, KeyValueVectorIndex};
21use crate::ivf_index::{
22 IVFCanonicalBuilder, IVFMutation, IVFPreparedMetadata, IVFReadIndex, IVFState,
23};
24use crate::vector_index::{IVFIndexParams, VectorIndex};
25use crate::{ReadOnlySnapshot, StorageBackendError, StorageBackendResult};
26
27#[cfg(test)]
28mod tests;
29
30pub struct KeyValueIVFIndex {
31 store: Arc<dyn KeyValueStore>,
32 raw: KeyValueVectorIndex,
33 table: String,
34 field: String,
35 dimensions: u32,
36 params: IVFIndexParams,
37 require_persisted: bool,
38 view: IndexView<IVFReadIndex>,
39}
40
41impl KeyValueIVFIndex {
42 pub fn create(
43 store: Arc<dyn KeyValueStore>,
44 table: impl Into<String>,
45 field: impl Into<String>,
46 dimensions: u32,
47 params: IVFIndexParams,
48 ) -> StorageBackendResult<Self> {
49 let index = Self::new(store, table.into(), field.into(), dimensions, params, false)?;
50 index.read_index()?;
51 Ok(index)
52 }
53
54 pub fn restore(
55 store: Arc<dyn KeyValueStore>,
56 table: impl Into<String>,
57 field: impl Into<String>,
58 dimensions: u32,
59 params: IVFIndexParams,
60 ) -> StorageBackendResult<Self> {
61 let index = Self::new(store, table.into(), field.into(), dimensions, params, true)?;
62 index.read_index()?;
63 Ok(index)
64 }
65
66 fn new(
67 store: Arc<dyn KeyValueStore>,
68 table: String,
69 field: String,
70 dimensions: u32,
71 params: IVFIndexParams,
72 require_persisted: bool,
73 ) -> StorageBackendResult<Self> {
74 Ok(Self {
75 raw: KeyValueVectorIndex::new(store.clone(), &table, &field, dimensions),
76 store,
77 table,
78 field,
79 dimensions,
80 params: params.validate()?,
81 require_persisted,
82 view: IndexView::new(!require_persisted),
83 })
84 }
85
86 pub(super) fn drop_metadata(
87 store: &dyn KeyValueStore,
88 table: &str,
89 field: &str,
90 ) -> StorageBackendResult<()> {
91 let mut batch = store.batch();
92 batch.fence_ivf_prefix(&ivf_metadata_key(table, field)?)?;
93 batch.fence_hnsw_prefix(&hnsw_metadata_key(table, field)?)?;
94 batch.delete(&ivf_metadata_key(table, field)?)?;
95 batch.delete_prefix(&ivf_centroid_prefix(table, field)?)?;
96 batch.delete_prefix(&ivf_assignment_prefix(table, field)?)?;
97 batch.delete(&hnsw_metadata_key(table, field)?)?;
98 batch.delete_prefix(&hnsw_node_prefix(table, field)?)?;
99 batch.commit()
100 }
101
102 fn index_at(&self, read: &dyn KeyValueRead) -> StorageBackendResult<IndexState<IVFReadIndex>> {
103 self.view.load(
104 read,
105 &[
106 &ivf_metadata_key(&self.table, &self.field)?,
107 &ivf_centroid_prefix(&self.table, &self.field)?,
108 &ivf_assignment_prefix(&self.table, &self.field)?,
109 &vector_field_prefix(&self.table, &self.field)?,
110 ],
111 |creating| {
112 let revision = ivf_persistence::load_revision(read, &self.table, &self.field)?;
113 let index = if creating || (revision.is_none() && !self.require_persisted) {
114 self.build_from_canonical(read)?
115 } else {
116 ivf_persistence::restore_state(
117 read,
118 &self.raw,
119 &self.table,
120 &self.field,
121 self.dimensions,
122 self.params,
123 )?
124 .0
125 };
126 Ok((index, revision))
127 },
128 )
129 }
130
131 fn read_index(&self) -> StorageBackendResult<IndexState<IVFReadIndex>> {
132 read_view(self.store.as_ref(), |read| self.index_at(read))
133 }
134
135 fn mutate(
136 &self,
137 mutation: IVFMutation<'_>,
138 canonical: impl FnOnce(&mut dyn KeyValueBatch) -> StorageBackendResult<()>,
139 ) -> StorageBackendResult<()> {
140 let metadata = ivf_metadata_key(&self.table, &self.field)?;
141 let centroids = ivf_centroid_prefix(&self.table, &self.field)?;
142 let assignments = ivf_assignment_prefix(&self.table, &self.field)?;
143 let vectors = vector_field_prefix(&self.table, &self.field)?;
144 self.view.evaluate_candidate(
145 self.store.as_ref(),
146 &[&metadata, ¢roids, &assignments, &vectors],
147 |read, batch| {
148 let cached = self.index_at(read)?;
149 let after = cached
150 .value
151 .clone_controlled(read.control())?
152 .prepare(&[mutation])?;
153 let changed_doc = match mutation {
154 IVFMutation::Replace { document, .. } | IVFMutation::Delete(document) => {
155 Some(document)
156 }
157 IVFMutation::Clear | IVFMutation::Train => None,
158 };
159 let full_rewrite = changed_doc.is_none()
160 || cached.definition_candidate
161 || cached.revision.is_none()
162 || cached.value.header().centroids != after.header().centroids;
163 canonical(batch)?;
164 let preview = !cached.definition_candidate
165 && cached.revision.is_some()
166 && changed_doc.is_some();
167 if preview {
168 batch.ivf_mutation(&ivf_metadata_key(&self.table, &self.field)?, mutation)?;
169 }
170 let revision = next_revision(cached.revision)?;
171 self.stage_snapshot(batch, &after, revision, full_rewrite, changed_doc, preview)?;
172 drop(cached);
173 Ok((IVFReadIndex::new(after)?, revision))
174 },
175 )
176 }
177
178 fn build_from_canonical(
179 &self,
180 read: &dyn KeyValueRead,
181 ) -> StorageBackendResult<Budgeted<IVFReadIndex>> {
182 IVFReadIndex::new(self.canonical_candidate(read)?)
183 }
184
185 fn canonical_candidate(
186 &self,
187 read: &dyn KeyValueRead,
188 ) -> StorageBackendResult<IVFPreparedMetadata> {
189 let mut builder = IVFCanonicalBuilder::new(self.dimensions, self.params, read.control())?;
190 self.raw
191 .visit_canonical_from(read, |document, ordinal, vector| {
192 builder.vector(document, ordinal, vector)
193 })?;
194 builder.finish()
195 }
196
197 fn rebuild(&self) -> StorageBackendResult<()> {
198 self.view.evaluate(self.store.as_ref(), |read, batch| {
199 let revision = ivf_persistence::load_revision(read, &self.table, &self.field)?;
200 if revision.is_none() && self.require_persisted {
201 return Err(other_error(format!(
202 "missing persisted IVF metadata for {}.{}",
203 self.table, self.field
204 )));
205 }
206 let candidate = self
207 .canonical_candidate(read)?
208 .prepare(&[IVFMutation::Train])?;
209 self.stage_snapshot(
210 batch,
211 &candidate,
212 next_revision(revision)?,
213 true,
214 None,
215 false,
216 )
217 })
218 }
219
220 fn stage_snapshot(
221 &self,
222 batch: &mut dyn KeyValueBatch,
223 snapshot: &IVFPreparedMetadata,
224 revision: u64,
225 full_rewrite: bool,
226 changed_doc: Option<DocId>,
227 preview: bool,
228 ) -> StorageBackendResult<()> {
229 ivf_persistence::stage_snapshot(
230 batch,
231 &self.table,
232 &self.field,
233 self.dimensions,
234 self.params,
235 snapshot,
236 revision,
237 full_rewrite,
238 changed_doc,
239 preview,
240 )
241 }
242}
243
244impl VectorIndex for KeyValueIVFIndex {
245 fn contains_document(&self, doc_id: DocId) -> StorageBackendResult<bool> {
246 self.raw.contains_document(doc_id)
247 }
248
249 fn dimensions(&self) -> u32 {
250 self.dimensions
251 }
252 fn index_kind(&self) -> &'static str {
253 "ivf"
254 }
255 fn add(&mut self, doc_id: DocId, vector: Vec<f32>) -> StorageBackendResult<()> {
256 self.add_many(doc_id, vec![vector])
257 }
258 fn add_many(&mut self, doc_id: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()> {
259 self.mutate(
260 IVFMutation::Replace {
261 document: doc_id,
262 vectors: &vectors,
263 },
264 |batch| self.raw.stage_replace(batch, doc_id, &vectors),
265 )
266 }
267 fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
268 self.mutate(IVFMutation::Delete(doc_id), |batch| {
269 self.raw.stage_replace(batch, doc_id, &[])
270 })
271 }
272 fn clear(&mut self) -> StorageBackendResult<()> {
273 self.mutate(IVFMutation::Clear, |batch| self.raw.stage_clear(batch))
274 }
275 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList> {
276 self.snapshot()?.search_knn(query, k)
277 }
278 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList> {
279 self.read_index()?.value.search_threshold(query, threshold)
280 }
281 fn count(&self) -> StorageBackendResult<usize> {
282 self.read_index()?.value.count()
283 }
284 fn initialize(&mut self) -> StorageBackendResult<()> {
285 self.rebuild()
286 }
287 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>> {
288 read_view(self.store.as_ref(), |read| {
289 let cached = self.index_at(read)?;
290 if cached.value.header().state != IVFState::Stale {
291 return Ok(cached.snapshot);
292 }
293 let candidate = IVFReadIndex::new(
294 cached
295 .value
296 .clone_controlled(read.control())?
297 .prepare(&[IVFMutation::Train])?,
298 )?;
299 ReadOnlySnapshot::from_budgeted(candidate)?
300 .with_canonical_vectors(Some(read.control()))?
301 .with_vector_read_control(read.control())?
302 .snapshot()
303 })
304 }
305 fn vector_read_snapshot(
306 &self,
307 control: &crate::read_control::StorageReadControl,
308 ) -> StorageBackendResult<Option<crate::vector_index::VectorReadSnapshot>> {
309 self.raw.vector_read_snapshot(control)
310 }
311}
312
313fn next_revision(revision: Option<u64>) -> StorageBackendResult<u64> {
314 revision
315 .unwrap_or(0)
316 .checked_add(1)
317 .ok_or_else(|| StorageBackendError::Other("IVF metadata revision space exhausted".into()))
318}