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 delta = cached.value.prepare_delta(mutation, read.control())?;
119 canonical(batch)?;
120 let preview = !cached.definition_candidate
121 && cached.revision.is_some()
122 && !matches!(mutation, HNSWMutation::Clear);
123 if preview {
124 batch.hnsw_mutation(&hnsw_metadata_key(&self.table, &self.field)?, mutation)?;
125 }
126 self.stage_delta(batch, &delta, next_revision(cached.revision)?, preview)
128 })
129 }
130
131 fn rebuild_graph(&self) -> StorageBackendResult<()> {
132 self.view.evaluate(self.store.as_ref(), |read, batch| {
133 let revision = hnsw_persistence::load_revision(read, &self.table, &self.field)?;
134 if revision.is_none() && self.require_persisted {
135 return Err(other_error(format!(
136 "missing persisted HNSW metadata for {}.{}",
137 self.table, self.field
138 )));
139 }
140 let delta = self.canonical_builder(read)?.finish_delta()?;
141 self.stage_delta(batch, &delta, next_revision(revision)?, false)
142 })
143 }
144
145 fn build_from_canonical(
146 &self,
147 read: &dyn KeyValueRead,
148 ) -> StorageBackendResult<Budgeted<HNSWIndex>> {
149 self.canonical_builder(read)?.finish()
150 }
151
152 fn canonical_builder(
153 &self,
154 read: &dyn KeyValueRead,
155 ) -> StorageBackendResult<HNSWCanonicalBuilder> {
156 let mut builder = HNSWCanonicalBuilder::new(self.dimensions, self.params, read.control())?;
157 self.raw
158 .visit_canonical_from(read, |document, ordinal, vector| {
159 builder.push(document, ordinal, vector)
160 })?;
161 Ok(builder)
162 }
163
164 fn stage_delta(
165 &self,
166 batch: &mut dyn KeyValueBatch,
167 delta: &HNSWGraphDelta,
168 revision: u64,
169 preview: bool,
170 ) -> StorageBackendResult<()> {
171 let metadata = hnsw_metadata_key(&self.table, &self.field)?;
172 let value = encode_value(&metadata_from_graph(
173 self.dimensions,
174 self.params,
175 delta.meta,
176 revision,
177 )?)?;
178 if preview {
179 batch.preview_hnsw_record(&metadata, Some(&value))?;
180 } else {
181 batch.fence_hnsw_prefix(&metadata)?;
182 batch.put(&metadata, &value)?;
183 }
184 if delta.full_rewrite {
185 let prefix = hnsw_node_prefix(&self.table, &self.field)?;
186 if preview {
187 batch.preview_hnsw_prefix(&prefix)?;
188 } else {
189 batch.delete_prefix(&prefix)?;
190 }
191 }
192 for node in delta.nodes() {
193 let node = node?;
194 let key = hnsw_node_key(&self.table, &self.field, node.node_id)?;
195 let value = encode_value(&PersistedHNSWNode::try_from(&*node)?)?;
196 if preview {
197 batch.preview_hnsw_record(&key, Some(&value))?;
198 } else {
199 batch.put(&key, &value)?;
200 }
201 }
202 Ok(())
203 }
204}
205
206impl VectorIndex for KeyValueHNSWIndex {
207 fn contains_document(&self, doc_id: DocId) -> StorageBackendResult<bool> {
208 self.raw.contains_document(doc_id)
209 }
210
211 fn dimensions(&self) -> u32 {
212 self.dimensions
213 }
214 fn index_kind(&self) -> &'static str {
215 "hnsw"
216 }
217 fn add(&mut self, doc_id: DocId, vector: Vec<f32>) -> StorageBackendResult<()> {
218 self.add_many(doc_id, vec![vector])
219 }
220 fn add_many(&mut self, doc_id: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()> {
221 self.mutate_graph(
222 HNSWMutation::Replace {
223 document: doc_id,
224 vectors: &vectors,
225 },
226 |batch| self.raw.stage_replace(batch, doc_id, &vectors),
227 )
228 }
229 fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
230 self.mutate_graph(HNSWMutation::Delete(doc_id), |batch| {
231 self.raw.stage_replace(batch, doc_id, &[])
232 })
233 }
234 fn clear(&mut self) -> StorageBackendResult<()> {
235 self.mutate_graph(HNSWMutation::Clear, |batch| self.raw.stage_clear(batch))
236 }
237 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList> {
238 self.read_graph()?.value.search_knn(query, k)
239 }
240 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList> {
241 self.read_graph()?.value.search_threshold(query, threshold)
242 }
243 fn count(&self) -> StorageBackendResult<usize> {
244 self.read_graph()?.value.count()
245 }
246 fn initialize(&mut self) -> StorageBackendResult<()> {
247 self.rebuild_graph()
248 }
249 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>> {
250 Ok(self.read_graph()?.snapshot)
251 }
252 fn vector_read_snapshot(
253 &self,
254 control: &crate::read_control::StorageReadControl,
255 ) -> StorageBackendResult<Option<crate::vector_index::VectorReadSnapshot>> {
256 self.raw.vector_read_snapshot(control)
257 }
258}
259
260fn next_revision(revision: Option<u64>) -> StorageBackendResult<u64> {
261 revision
262 .unwrap_or(0)
263 .checked_add(1)
264 .ok_or_else(|| StorageBackendError::Other("HNSW metadata revision space exhausted".into()))
265}