shodh_memory/vector_db/
mod.rs1pub mod distance_inline;
38pub mod pq;
39pub mod spann;
40pub mod vamana;
41pub mod vamana_persist;
42
43pub use pq::{CompressedVectorStore, PQConfig, ProductQuantizer};
45pub use spann::{SpannConfig, SpannIndex};
46pub use vamana::{DistanceMetric, VamanaConfig, VamanaIndex, REBUILD_THRESHOLD};
47
48use anyhow::Result;
49use std::path::Path;
50
51pub const SPANN_AUTO_THRESHOLD: usize = 100_000;
54
55#[derive(Debug, Clone)]
57pub struct BackendConfig {
58 pub dimension: usize,
60 pub distance_metric: DistanceMetric,
62 pub force_backend: Option<BackendType>,
64 pub use_pq: bool,
66 pub spann_probes: usize,
68 pub vamana_max_degree: usize,
70 pub vamana_search_list_size: usize,
72}
73
74impl Default for BackendConfig {
75 fn default() -> Self {
76 Self {
77 dimension: 384, distance_metric: DistanceMetric::NormalizedDotProduct,
79 force_backend: None,
80 use_pq: true,
81 spann_probes: 20,
82 vamana_max_degree: 32,
83 vamana_search_list_size: 100,
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum BackendType {
91 Vamana,
93 Spann,
95}
96
97pub enum VectorIndexBackend {
99 Vamana(VamanaIndex),
100 Spann(SpannIndex),
101}
102
103impl VectorIndexBackend {
104 pub fn auto(config: BackendConfig, expected_vectors: usize) -> Result<Self> {
106 let backend_type = config.force_backend.unwrap_or_else(|| {
107 if expected_vectors >= SPANN_AUTO_THRESHOLD {
108 BackendType::Spann
109 } else {
110 BackendType::Vamana
111 }
112 });
113
114 match backend_type {
115 BackendType::Vamana => Self::new_vamana(config),
116 BackendType::Spann => Self::new_spann(config),
117 }
118 }
119
120 pub fn new_vamana(config: BackendConfig) -> Result<Self> {
122 let vamana_config = VamanaConfig {
123 dimension: config.dimension,
124 max_degree: config.vamana_max_degree,
125 search_list_size: config.vamana_search_list_size,
126 distance_metric: config.distance_metric,
127 ..Default::default()
128 };
129 Ok(Self::Vamana(VamanaIndex::new(vamana_config)?))
130 }
131
132 pub fn new_spann(config: BackendConfig) -> Result<Self> {
134 let spann_config = SpannConfig {
135 dimension: config.dimension,
136 use_pq: config.use_pq,
137 num_probes: config.spann_probes,
138 distance_metric: config.distance_metric,
139 ..Default::default()
140 };
141 Ok(Self::Spann(SpannIndex::new(spann_config)))
142 }
143
144 pub fn backend_type(&self) -> BackendType {
146 match self {
147 Self::Vamana(_) => BackendType::Vamana,
148 Self::Spann(_) => BackendType::Spann,
149 }
150 }
151
152 pub fn add_vector(&mut self, vector: Vec<f32>) -> Result<u32> {
154 match self {
155 Self::Vamana(idx) => idx.add_vector(vector),
156 Self::Spann(idx) => {
157 let id = idx.len() as u32;
158 idx.insert(id, &vector)?;
159 Ok(id)
160 }
161 }
162 }
163
164 pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<(u32, f32)>> {
166 match self {
167 Self::Vamana(idx) => idx.search(query, k),
168 Self::Spann(idx) => idx.search(query, k),
169 }
170 }
171
172 pub fn len(&self) -> usize {
174 match self {
175 Self::Vamana(idx) => idx.len(),
176 Self::Spann(idx) => idx.len(),
177 }
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.len() == 0
183 }
184
185 pub fn save_to_file(&self, path: &Path) -> Result<()> {
187 match self {
188 Self::Vamana(idx) => idx.save_to_file(path),
189 Self::Spann(idx) => idx.save_to_file(path),
190 }
191 }
192
193 pub fn load_from_file(path: &Path, backend_type: BackendType) -> Result<Self> {
195 match backend_type {
196 BackendType::Vamana => Ok(Self::Vamana(VamanaIndex::load_from_file(path)?)),
197 BackendType::Spann => Ok(Self::Spann(SpannIndex::load_from_file(path)?)),
198 }
199 }
200
201 pub fn build(&mut self, vectors: Vec<Vec<f32>>) -> Result<()> {
203 match self {
204 Self::Vamana(idx) => idx.build(vectors),
205 Self::Spann(idx) => idx.build(vectors),
206 }
207 }
208
209 pub fn needs_rebuild(&self) -> bool {
211 match self {
212 Self::Vamana(idx) => idx.needs_rebuild(),
213 Self::Spann(_) => false, }
215 }
216
217 pub fn auto_rebuild_if_needed(&mut self) -> Result<bool> {
219 match self {
220 Self::Vamana(idx) => idx.auto_rebuild_if_needed(),
221 Self::Spann(_) => Ok(false),
222 }
223 }
224
225 pub fn incremental_insert_count(&self) -> usize {
227 match self {
228 Self::Vamana(idx) => idx.incremental_insert_count(),
229 Self::Spann(_) => 0,
230 }
231 }
232
233 pub fn deleted_count(&self) -> usize {
235 match self {
236 Self::Vamana(idx) => idx.deleted_count(),
237 Self::Spann(_) => 0,
238 }
239 }
240
241 pub fn deletion_ratio(&self) -> f32 {
243 match self {
244 Self::Vamana(idx) => idx.deletion_ratio(),
245 Self::Spann(_) => 0.0,
246 }
247 }
248
249 pub fn needs_compaction(&self) -> bool {
251 match self {
252 Self::Vamana(idx) => idx.needs_compaction(),
253 Self::Spann(_) => false,
254 }
255 }
256
257 pub fn verify_index_file(path: &Path, backend_type: BackendType) -> Result<bool> {
259 match backend_type {
260 BackendType::Vamana => VamanaIndex::verify_index_file(path),
261 BackendType::Spann => SpannIndex::verify_index_file(path),
262 }
263 }
264}