1use std::sync::Arc;
10use uqa_core::{memory::Budgeted, DocId, PostingList};
11
12use super::codec::encode_value;
13use super::codec::{other_error, vector_field_prefix};
14use super::hnsw_persistence::{self, metadata_from_graph, PersistedHNSWNode};
15use super::index_keys::{hnsw_metadata_key, hnsw_node_key, hnsw_node_prefix};
16use super::{KeyValueBatch, KeyValueRead, KeyValueStore, KeyValueVectorIndex};
17use crate::hnsw_index::{HNSWCanonicalBuilder, HNSWGraphDelta, HNSWIndex, HNSWMutation};
18use crate::vector_index::{HNSWIndexParams, VectorIndex};
19use crate::{StorageBackendError, StorageBackendResult};
20
21use super::index_view::{read_view, IndexState, IndexView};
22
23pub struct KeyValueHNSWIndex {
24 store: Arc<dyn KeyValueStore>,
25 raw: KeyValueVectorIndex,
26 table: String,
27 field: String,
28 dimensions: u32,
29 params: HNSWIndexParams,
30 require_persisted: bool,
31 view: IndexView<HNSWIndex>,
32}
33
34impl KeyValueHNSWIndex {
35 pub fn create(
36 store: Arc<dyn KeyValueStore>,
37 table: impl Into<String>,
38 field: impl Into<String>,
39 dimensions: u32,
40 params: HNSWIndexParams,
41 ) -> StorageBackendResult<Self> {
42 let index = Self::new(store, table.into(), field.into(), dimensions, params, false)?;
43 index.read_graph()?;
44 Ok(index)
45 }
46
47 pub fn restore(
48 store: Arc<dyn KeyValueStore>,
49 table: impl Into<String>,
50 field: impl Into<String>,
51 dimensions: u32,
52 params: HNSWIndexParams,
53 ) -> StorageBackendResult<Self> {
54 let index = Self::new(store, table.into(), field.into(), dimensions, params, true)?;
55 index.read_graph()?;
56 Ok(index)
57 }
58
59 fn new(
60 store: Arc<dyn KeyValueStore>,
61 table: String,
62 field: String,
63 dimensions: u32,
64 params: HNSWIndexParams,
65 require_persisted: bool,
66 ) -> StorageBackendResult<Self> {
67 Ok(Self {
68 raw: KeyValueVectorIndex::new(Arc::clone(&store), &table, &field, dimensions),
69 store,
70 table,
71 field,
72 dimensions,
73 params: params.validate()?,
74 require_persisted,
75 view: IndexView::new(!require_persisted),
76 })
77 }
78
79 fn graph_at(&self, read: &dyn KeyValueRead) -> StorageBackendResult<IndexState<HNSWIndex>> {
80 self.view.load(
81 read,
82 &[
83 &hnsw_metadata_key(&self.table, &self.field)?,
84 &hnsw_node_prefix(&self.table, &self.field)?,
85 &vector_field_prefix(&self.table, &self.field)?,
86 ],
87 |creating| {
88 let revision = hnsw_persistence::load_revision(read, &self.table, &self.field)?;
89 let graph = if creating || (revision.is_none() && !self.require_persisted) {
90 self.build_from_canonical(read)?
91 } else {
92 hnsw_persistence::restore_graph(
93 read,
94 &self.raw,
95 &self.table,
96 &self.field,
97 self.dimensions,
98 self.params,
99 )?
100 .0
101 };
102 Ok((graph, revision))
103 },
104 )
105 }
106
107 fn read_graph(&self) -> StorageBackendResult<IndexState<HNSWIndex>> {
108 read_view(self.store.as_ref(), |read| self.graph_at(read))
109 }
110
111 fn mutate_graph(
112 &self,
113 mutation: HNSWMutation<'_>,
114 canonical: impl FnOnce(&mut dyn KeyValueBatch) -> StorageBackendResult<()>,
115 ) -> StorageBackendResult<()> {
116 self.view.evaluate(self.store.as_ref(), |read, batch| {
117 let cached = self.graph_at(read)?;
118 let preview = !cached.definition_candidate
119 && cached.revision.is_some()
120 && !matches!(mutation, HNSWMutation::Clear);
121 if preview {
122 batch.hnsw_mutation(&hnsw_metadata_key(&self.table, &self.field)?, mutation)?;
124 }
125 let delta = cached.value.prepare_delta(mutation, read.control())?;
126 canonical(batch)?;
127 self.stage_delta(batch, &delta, next_revision(cached.revision)?, preview)
129 })
130 }
131
132 fn rebuild_graph(&self) -> StorageBackendResult<()> {
133 self.view.evaluate(self.store.as_ref(), |read, batch| {
134 let revision = hnsw_persistence::load_revision(read, &self.table, &self.field)?;
135 if revision.is_none() && self.require_persisted {
136 return Err(other_error(format!(
137 "missing persisted HNSW metadata for {}.{}",
138 self.table, self.field
139 )));
140 }
141 let delta = self.canonical_builder(read)?.finish_delta()?;
142 self.stage_delta(batch, &delta, next_revision(revision)?, false)
143 })
144 }
145
146 fn build_from_canonical(
147 &self,
148 read: &dyn KeyValueRead,
149 ) -> StorageBackendResult<Budgeted<HNSWIndex>> {
150 self.canonical_builder(read)?.finish()
151 }
152
153 fn canonical_builder(
154 &self,
155 read: &dyn KeyValueRead,
156 ) -> StorageBackendResult<HNSWCanonicalBuilder> {
157 let mut builder = HNSWCanonicalBuilder::new(self.dimensions, self.params, read.control())?;
158 self.raw
159 .visit_canonical_from(read, |document, ordinal, vector| {
160 builder.push(document, ordinal, vector)
161 })?;
162 Ok(builder)
163 }
164
165 fn stage_delta(
166 &self,
167 batch: &mut dyn KeyValueBatch,
168 delta: &HNSWGraphDelta,
169 revision: u64,
170 preview: bool,
171 ) -> StorageBackendResult<()> {
172 let metadata = hnsw_metadata_key(&self.table, &self.field)?;
173 let value = encode_value(&metadata_from_graph(
174 self.dimensions,
175 self.params,
176 delta.meta,
177 revision,
178 )?)?;
179 if preview {
180 batch.preview_hnsw_record(&metadata, Some(&value))?;
181 } else {
182 batch.fence_hnsw_prefix(&metadata)?;
183 batch.put(&metadata, &value)?;
184 }
185 if delta.full_rewrite {
186 let prefix = hnsw_node_prefix(&self.table, &self.field)?;
187 if preview {
188 batch.preview_hnsw_prefix(&prefix)?;
189 } else {
190 batch.delete_prefix(&prefix)?;
191 }
192 }
193 for node in delta.nodes() {
194 let node = node?;
195 let key = hnsw_node_key(&self.table, &self.field, node.node_id)?;
196 let value = encode_value(&PersistedHNSWNode::try_from(&*node)?)?;
197 if preview {
198 batch.preview_hnsw_record(&key, Some(&value))?;
199 } else {
200 batch.put(&key, &value)?;
201 }
202 }
203 Ok(())
204 }
205}
206
207impl VectorIndex for KeyValueHNSWIndex {
208 fn contains_document(&self, doc_id: DocId) -> StorageBackendResult<bool> {
209 self.raw.contains_document(doc_id)
210 }
211
212 fn dimensions(&self) -> u32 {
213 self.dimensions
214 }
215 fn index_kind(&self) -> &'static str {
216 "hnsw"
217 }
218 fn add(&mut self, doc_id: DocId, vector: Vec<f32>) -> StorageBackendResult<()> {
219 self.add_many(doc_id, vec![vector])
220 }
221 fn add_many(&mut self, doc_id: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()> {
222 self.mutate_graph(
223 HNSWMutation::Replace {
224 document: doc_id,
225 vectors: &vectors,
226 },
227 |batch| self.raw.stage_replace(batch, doc_id, &vectors),
228 )
229 }
230 fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
231 self.mutate_graph(HNSWMutation::Delete(doc_id), |batch| {
232 self.raw.stage_replace(batch, doc_id, &[])
233 })
234 }
235 fn clear(&mut self) -> StorageBackendResult<()> {
236 self.mutate_graph(HNSWMutation::Clear, |batch| self.raw.stage_clear(batch))
237 }
238 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList> {
239 self.read_graph()?.value.search_knn(query, k)
240 }
241 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList> {
242 self.read_graph()?.value.search_threshold(query, threshold)
243 }
244 fn count(&self) -> StorageBackendResult<usize> {
245 self.read_graph()?.value.count()
246 }
247 fn initialize(&mut self) -> StorageBackendResult<()> {
248 self.rebuild_graph()
249 }
250 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>> {
251 Ok(self.read_graph()?.snapshot)
252 }
253 fn vector_read_snapshot(
254 &self,
255 control: &crate::read_control::StorageReadControl,
256 ) -> StorageBackendResult<Option<crate::vector_index::VectorReadSnapshot>> {
257 self.raw.vector_read_snapshot(control)
258 }
259}
260
261fn next_revision(revision: Option<u64>) -> StorageBackendResult<u64> {
262 revision
263 .unwrap_or(0)
264 .checked_add(1)
265 .ok_or_else(|| StorageBackendError::Other("HNSW metadata revision space exhausted".into()))
266}