1use std::collections::{HashMap, HashSet};
4use std::path::Path;
5
6use crate::common::fs::{atomic_save_json, read_json};
7use crate::segment::common::operation_error::{OperationError, OperationResult};
8use crate::segment::types::{
9 Distance, HnswConfig, PayloadStorageType, QuantizationConfig, SegmentConfig, VectorName,
10 VectorNameBuf,
11};
12use serde::{Deserialize, Serialize};
13use crate::shard::operations::optimization::OptimizerThresholds;
14use crate::wal::WalOptions;
15
16use super::optimizers::EdgeOptimizersConfig;
17use super::vectors::{EdgeSparseVectorParams, EdgeVectorParams};
18
19pub(crate) const EDGE_CONFIG_FILE: &str = "edge_config.json";
21
22#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub struct EdgeConfig {
36 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub on_disk_payload: Option<bool>,
40 #[serde(default)]
42 pub vectors: HashMap<VectorNameBuf, EdgeVectorParams>,
43 #[serde(default)]
45 pub sparse_vectors: HashMap<VectorNameBuf, EdgeSparseVectorParams>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub hnsw_config: Option<HnswConfig>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub quantization_config: Option<QuantizationConfig>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub optimizers: Option<EdgeOptimizersConfig>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub wal_options: Option<WalOptions>,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub max_search_threads: Option<usize>,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub search_pool_core: Option<usize>,
73}
74
75impl EdgeConfig {
76 pub fn builder() -> crate::edge::builders::EdgeConfigBuilder {
78 crate::edge::builders::EdgeConfigBuilder::new()
79 }
80
81 pub fn on_disk_payload(&self) -> bool {
83 self.on_disk_payload.unwrap_or(true)
84 }
85
86 pub fn hnsw_config(&self) -> HnswConfig {
88 self.hnsw_config.unwrap_or_default()
89 }
90
91 pub fn optimizers(&self) -> EdgeOptimizersConfig {
93 self.optimizers.clone().unwrap_or_default()
94 }
95
96 pub fn fill_unspecified_from(self, base: &EdgeConfig) -> Self {
106 let Self {
107 on_disk_payload,
108 vectors,
109 sparse_vectors,
110 hnsw_config,
111 quantization_config,
112 optimizers,
113 wal_options,
114 max_search_threads,
115 search_pool_core,
116 } = self;
117 Self {
118 on_disk_payload: on_disk_payload.or(base.on_disk_payload),
119 vectors: if vectors.is_empty() {
120 base.vectors.clone()
121 } else {
122 vectors
123 },
124 sparse_vectors: if sparse_vectors.is_empty() {
125 base.sparse_vectors.clone()
126 } else {
127 sparse_vectors
128 },
129 hnsw_config: hnsw_config.or(base.hnsw_config),
130 quantization_config: quantization_config.or_else(|| base.quantization_config.clone()),
131 optimizers: optimizers.or_else(|| base.optimizers.clone()),
132 wal_options: wal_options.or_else(|| base.wal_options.clone()),
133 max_search_threads: max_search_threads.or(base.max_search_threads),
134 search_pool_core: search_pool_core.or(base.search_pool_core),
135 }
136 }
137
138 pub(crate) fn fold_from_segment_config(acc: Option<Self>, segment: &SegmentConfig) -> Self {
146 let derived = Self::from_segment_config(segment);
147 match acc {
148 Some(acc) => acc.fill_unspecified_from(&derived),
149 None => derived,
150 }
151 }
152
153 pub fn from_segment_config(segment: &SegmentConfig) -> Self {
155 let SegmentConfig {
156 vector_data,
157 sparse_vector_data,
158 payload_storage_type,
159 } = segment;
160
161 let vectors = vector_data
162 .iter()
163 .map(|(name, v)| (name.clone(), EdgeVectorParams::from_vector_data_config(v)))
164 .collect();
165
166 let sparse_vectors = sparse_vector_data
167 .iter()
168 .map(|(name, s)| {
169 (
170 name.clone(),
171 EdgeSparseVectorParams::from_sparse_vector_data_config(s),
172 )
173 })
174 .collect();
175
176 let on_disk_payload = payload_storage_type.is_on_disk();
177
178 let hnsw_configs: Vec<HnswConfig> = vector_data
180 .values()
181 .filter_map(|v| match &v.index {
182 crate::segment::types::Indexes::Plain {} => None,
183 crate::segment::types::Indexes::Hnsw(h) => Some(*h),
184 })
185 .collect();
186 let hnsw_config = hnsw_configs.first().and_then(|first| {
187 if hnsw_configs.iter().all(|h| h == first) {
188 Some(*first)
189 } else {
190 None
191 }
192 });
193
194 Self {
195 on_disk_payload: Some(on_disk_payload),
196 vectors,
197 sparse_vectors,
198 hnsw_config,
199 quantization_config: None,
200 optimizers: None,
201 wal_options: None,
202 max_search_threads: None,
203 search_pool_core: None,
204 }
205 }
206
207 pub fn search_thread_count(&self) -> usize {
211 crate::common::defaults::search_thread_count(self.max_search_threads.unwrap_or(0))
212 }
213
214 pub fn check_compatible_with_segment_config(
216 &self,
217 other: &SegmentConfig,
218 ) -> Result<(), String> {
219 self.plain_segment_config().check_compatible(other)
220 }
221
222 pub fn plain_segment_config(&self) -> SegmentConfig {
225 let payload_storage_type = PayloadStorageType::from_on_disk_payload(self.on_disk_payload());
226 let vector_data = self
227 .vectors
228 .iter()
229 .map(|(name, p)| {
230 (
231 name.clone(),
232 p.to_plain_vector_data_config(self.quantization_config.as_ref()),
233 )
234 })
235 .collect();
236
237 let sparse_vector_data = self
238 .sparse_vectors
239 .iter()
240 .map(|(name, p)| (name.clone(), p.to_plain_sparse_vector_data_config()))
241 .collect();
242
243 SegmentConfig {
244 vector_data,
245 sparse_vector_data,
246 payload_storage_type,
247 }
248 }
249
250 pub fn vector_names(&self) -> HashSet<VectorNameBuf> {
255 self.vectors
256 .keys()
257 .chain(self.sparse_vectors.keys())
258 .cloned()
259 .collect()
260 }
261
262 pub fn segment_optimizer_config(&self) -> crate::shard::optimizers::config::SegmentOptimizerConfig {
265 use crate::shard::optimizers::config::SegmentOptimizerConfig;
266
267 let SegmentConfig {
268 vector_data: plain_dense_vector_config,
269 sparse_vector_data: plain_sparse_vector_config,
270 payload_storage_type,
271 } = self.plain_segment_config();
272
273 let hnsw_config = self.hnsw_config();
274 let dense_vector = self
275 .vectors
276 .iter()
277 .map(|(name, p)| {
278 (
279 name.clone(),
280 p.to_dense_vector_optimizer_config(
281 &hnsw_config,
282 self.quantization_config.as_ref(),
283 ),
284 )
285 })
286 .collect();
287
288 let sparse_vector = self
289 .sparse_vectors
290 .iter()
291 .map(|(name, p)| (name.clone(), p.to_sparse_vector_optimizer_config()))
292 .collect();
293
294 SegmentOptimizerConfig {
295 payload_storage_type,
296 plain_dense_vector_config,
297 plain_sparse_vector_config,
298 dense_vector,
299 sparse_vector,
300 live_vector_names: None,
301 }
302 }
303
304 pub fn vector_data_config(
307 &self,
308 name: &VectorNameBuf,
309 ) -> Option<crate::segment::types::VectorDataConfig> {
310 self.vectors
311 .get(name)
312 .map(|p| p.to_plain_vector_data_config(self.quantization_config.as_ref()))
313 }
314
315 pub fn get_distance(&self, vector_name: &VectorName) -> OperationResult<Distance> {
318 if let Some(params) = self.vectors.get(vector_name) {
319 Ok(params.distance)
320 } else if self.sparse_vectors.contains_key(vector_name) {
321 Ok(Distance::Dot)
322 } else {
323 Err(OperationError::vector_name_not_exists(vector_name))
324 }
325 }
326
327 pub fn optimizer_thresholds(&self, num_indexing_threads: usize) -> OptimizerThresholds {
328 let optimizers = self.optimizers();
329 OptimizerThresholds {
330 memmap_threshold_kb: usize::MAX,
331 indexing_threshold_kb: optimizers.get_indexing_threshold_kb(),
332 max_segment_size_kb: optimizers.get_max_segment_size_kb(num_indexing_threads),
333 deferred_internal_id: None,
334 }
335 }
336
337 pub fn save(&self, path: &Path) -> OperationResult<()> {
338 let config_path = path.join(EDGE_CONFIG_FILE);
339 atomic_save_json(&config_path, self).map_err(|e| {
340 OperationError::service_error(format!(
341 "failed to write {}: {}",
342 config_path.display(),
343 e
344 ))
345 })
346 }
347
348 pub fn load(path: &Path) -> Option<OperationResult<Self>> {
349 let config_path = path.join(EDGE_CONFIG_FILE);
350 match fs_err::exists(&config_path) {
351 Ok(false) => return None,
352 Err(e) => return Some(Err(OperationError::from(e))),
353 Ok(true) => {}
354 }
355 Some(read_json(&config_path).map_err(OperationError::from))
356 }
357
358 pub fn set_hnsw_config(&mut self, hnsw_config: HnswConfig) {
359 self.hnsw_config = Some(hnsw_config);
360 }
361
362 pub fn set_vector_hnsw_config(
363 &mut self,
364 vector_name: &str,
365 hnsw_config: HnswConfig,
366 ) -> OperationResult<()> {
367 let name = VectorNameBuf::from(vector_name);
368 let params = self
369 .vectors
370 .get_mut(&name)
371 .ok_or_else(|| OperationError::vector_name_not_exists(vector_name))?;
372 params.hnsw_config = Some(hnsw_config);
373 Ok(())
374 }
375
376 pub fn set_optimizers_config(&mut self, optimizers: EdgeOptimizersConfig) {
377 self.optimizers = Some(optimizers);
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use crate::segment::types::{Distance, Indexes, VectorDataConfig, VectorStorageType};
384
385 use super::*;
386
387 fn segment_config(index: Indexes) -> SegmentConfig {
388 SegmentConfig {
389 vector_data: HashMap::from([(
390 "vec".to_string(),
391 VectorDataConfig {
392 size: 4,
393 distance: Distance::Dot,
394 storage_type: VectorStorageType::ChunkedMmap,
395 index,
396 quantization_config: None,
397 multivector_config: None,
398 datatype: None,
399 },
400 )]),
401 sparse_vector_data: HashMap::new(),
402 payload_storage_type: PayloadStorageType::from_on_disk_payload(true),
403 }
404 }
405
406 #[test]
409 fn fold_derives_hnsw_from_indexed_segment_regardless_of_order() {
410 let hnsw = HnswConfig {
411 m: 32,
412 ..HnswConfig::default()
413 };
414 let plain = segment_config(Indexes::Plain {});
415 let indexed = segment_config(Indexes::Hnsw(hnsw));
416
417 for segments in [[&plain, &indexed], [&indexed, &plain]] {
418 let derived = segments
419 .into_iter()
420 .fold(None, |acc, segment| {
421 Some(EdgeConfig::fold_from_segment_config(acc, segment))
422 })
423 .unwrap();
424 assert_eq!(derived.hnsw_config, Some(hnsw));
425 assert!(derived.vectors.contains_key("vec"));
426 }
427 }
428}