qdrant_edge/edge/edge_shard/
mod.rs1mod optimize;
2mod shard_read;
3mod snapshots;
4mod update;
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9use std::sync::atomic::AtomicBool;
10
11use crate::wal::WalOptions;
12use crate::common::save_on_disk::SaveOnDisk;
13use fs_err as fs;
14use parking_lot::Mutex;
15use crate::segment::common::operation_error::{OperationError, OperationResult};
16use crate::segment::entry::ReadSegmentEntry as _;
17use crate::segment::segment_constructor::{load_segment, normalize_segment_dir};
18use crate::shard::files::{PAYLOAD_INDEX_CONFIG_FILE, SEGMENTS_PATH, segment_manifest_path};
19use crate::shard::operations::CollectionUpdateOperations;
20use crate::shard::segment_holder::locked::LockedSegmentHolder;
21use crate::shard::segment_holder::{FlushMode, SegmentHolder};
22use crate::shard::segment_manifest::SegmentsManifest;
23use crate::shard::wal::SerdeWal;
24use uuid::Uuid;
25
26use crate::edge::config::optimizers::EdgeOptimizersConfig;
27use crate::edge::config::shard::{EDGE_CONFIG_FILE, EdgeConfig};
28use crate::edge::read_view::build_segment_pool;
29
30#[derive(Debug)]
31pub struct EdgeShard {
32 path: PathBuf,
33 config: Arc<SaveOnDisk<EdgeConfig>>,
36 wal: Mutex<SerdeWal<CollectionUpdateOperations>>,
37 segments: LockedSegmentHolder,
38 segment_manifest: Option<SaveOnDisk<SegmentsManifest>>,
42 search_pool: Arc<rayon::ThreadPool>,
45}
46
47const WAL_PATH: &str = "wal";
48impl EdgeShard {
49 pub fn new(path: &Path, config: EdgeConfig) -> OperationResult<Self> {
56 if has_existing_segments(path) {
57 return Err(OperationError::service_error(
58 "cannot create edge shard: path already contains segment data",
59 ));
60 }
61
62 let wal_options = config.wal_options.clone().unwrap_or_default();
63 let (wal, segments_path) = ensure_dirs_and_open_wal(path, wal_options)?;
64 config.save(path)?;
65
66 let mut segments = SegmentHolder::default();
67 ensure_appendable_segment(&mut segments, path, &segments_path, &config)?;
68
69 let search_pool = build_segment_pool(
70 "edge-search",
71 config.search_thread_count(),
72 config.search_pool_core,
73 )?;
74
75 let config_path = path.join(EDGE_CONFIG_FILE);
76 let config = Arc::new(
77 SaveOnDisk::new(&config_path, config)
78 .map_err(|e| OperationError::service_error(e.to_string()))?,
79 );
80
81 let segment_manifest = init_segment_manifest(path, &segments)?;
82
83 Ok(Self {
84 path: path.into(),
85 config,
86 wal: parking_lot::Mutex::new(wal),
87 segments: LockedSegmentHolder::new(segments),
88 segment_manifest,
89 search_pool,
90 })
91 }
92
93 pub fn load(path: &Path, config: Option<EdgeConfig>) -> OperationResult<Self> {
111 let resolved = resolve_initial_config(path, config)?;
112
113 let wal_options = resolved
114 .as_ref()
115 .and_then(|c| c.wal_options.clone())
116 .unwrap_or_default();
117 let (wal, segments_path) = ensure_dirs_and_open_wal(path, wal_options)?;
118
119 let (mut segments, derived) = load_segments(&segments_path)?;
120
121 let config = match (resolved, derived) {
122 (Some(resolved), Some(derived)) => {
123 let merged = resolved.fill_unspecified_from(&derived);
124 merged
127 .check_compatible_with_segment_config(&derived.plain_segment_config())
128 .map_err(|err| {
129 OperationError::service_error(format!(
130 "config is incompatible with existing segments: {err}"
131 ))
132 })?;
133 merged
134 }
135 (Some(resolved), None) => resolved,
136 (None, Some(derived)) => derived,
137 (None, None) => {
138 return Err(OperationError::service_error(
139 "edge config is not provided and no segments were loaded",
140 ));
141 }
142 };
143
144 ensure_appendable_segment(&mut segments, path, &segments_path, &config)?;
145
146 let search_pool = build_segment_pool(
147 "edge-search",
148 config.search_thread_count(),
149 config.search_pool_core,
150 )?;
151
152 let config_path = path.join(EDGE_CONFIG_FILE);
153 let config = Arc::new(
154 SaveOnDisk::new(&config_path, config)
155 .map_err(|e| OperationError::service_error(e.to_string()))?,
156 );
157
158 let segment_manifest = init_segment_manifest(path, &segments)?;
159
160 Ok(Self {
161 path: path.into(),
162 config,
163 wal: parking_lot::Mutex::new(wal),
164 segments: LockedSegmentHolder::new(segments),
165 segment_manifest,
166 search_pool,
167 })
168 }
169
170 pub(crate) fn update_segment_manifest(&self) -> OperationResult<()> {
174 let Some(manifest) = &self.segment_manifest else {
175 return Ok(());
176 };
177
178 let rebuilt = {
179 let holder = self.segments.read();
180 SegmentsManifest::from_segment_holder(&holder)
181 };
182 manifest
184 .write_optional(|previous| {
185 let current = rebuilt.preserving(previous);
186 (*previous != current).then_some(current)
187 })
188 .map_err(|err| OperationError::service_error(err.to_string()))?;
189 Ok(())
190 }
191
192 pub fn config(&self) -> parking_lot::RwLockReadGuard<'_, EdgeConfig> {
193 self.config.read()
194 }
195
196 pub fn path(&self) -> &Path {
197 &self.path
198 }
199
200 pub fn set_hnsw_config(&self, hnsw_config: crate::segment::types::HnswConfig) -> OperationResult<()> {
202 self.config
203 .write(|cfg| cfg.set_hnsw_config(hnsw_config))
204 .map_err(|e| OperationError::service_error(e.to_string()))
205 }
206
207 pub fn set_vector_hnsw_config(
210 &self,
211 vector_name: &str,
212 hnsw_config: crate::segment::types::HnswConfig,
213 ) -> OperationResult<()> {
214 let mut mutation = Ok(());
220 self.config
221 .write_optional(|cfg| {
222 let mut updated = cfg.clone();
223 match updated.set_vector_hnsw_config(vector_name, hnsw_config) {
224 Ok(()) => Some(updated),
225 Err(e) => {
226 mutation = Err(e);
227 None
228 }
229 }
230 })
231 .map_err(|e| OperationError::service_error(e.to_string()))
232 .and(mutation)
233 }
234
235 pub fn set_optimizers_config(&self, optimizers: EdgeOptimizersConfig) -> OperationResult<()> {
237 self.config
238 .write(|cfg| cfg.set_optimizers_config(optimizers))
239 .map_err(|e| OperationError::service_error(e.to_string()))
240 }
241
242 pub fn flush(&self) -> OperationResult<()> {
255 self.wal
256 .lock()
257 .flush()
258 .map_err(|e| OperationError::service_error(format!("WAL flush failed: {e}")))?;
259
260 self.segments.read().flush_all(FlushMode::Sync, true)?;
261
262 Ok(())
263 }
264}
265
266impl Drop for EdgeShard {
267 fn drop(&mut self) {
268 if let Err(e) = self.flush() {
269 log::error!("EdgeShard flush during drop failed: {e}");
270 }
271 }
272}
273
274fn init_segment_manifest(
277 path: &Path,
278 segments: &SegmentHolder,
279) -> OperationResult<Option<SaveOnDisk<SegmentsManifest>>> {
280 if !crate::common::flags::feature_flags().write_segment_manifest {
281 return Ok(None);
282 }
283
284 let manifest_path = segment_manifest_path(path);
287 let rebuilt = SegmentsManifest::from_segment_holder(segments);
288 let manifest = match fs::read(&manifest_path)
289 .ok()
290 .and_then(|bytes| serde_json::from_slice::<SegmentsManifest>(&bytes).ok())
291 {
292 Some(previous) => rebuilt.preserving(&previous),
293 None => rebuilt,
294 };
295 let manifest = SaveOnDisk::new(manifest_path, manifest)
296 .map_err(|err| OperationError::service_error(err.to_string()))?;
297 Ok(Some(manifest))
298}
299
300fn has_existing_segments(path: &Path) -> bool {
301 let segments_path = path.join(SEGMENTS_PATH);
302 let Ok(entries) = fs::read_dir(&segments_path) else {
303 return false;
304 };
305 for entry in entries.flatten() {
306 let p = entry.path();
307 if !p.is_dir() {
308 continue;
309 }
310 if p.file_name()
311 .and_then(|n| n.to_str())
312 .is_some_and(|n| n.starts_with('.'))
313 {
314 continue;
315 }
316 if normalize_segment_dir(&p).ok().flatten().is_some() {
317 return true;
318 }
319 }
320 false
321}
322
323fn ensure_dirs_and_open_wal(
324 path: &Path,
325 wal_options: WalOptions,
326) -> OperationResult<(SerdeWal<CollectionUpdateOperations>, PathBuf)> {
327 let wal_path = path.join(WAL_PATH);
328 if !wal_path.exists() {
329 fs::create_dir(&wal_path).map_err(|err| {
330 OperationError::service_error(format!("failed to create WAL directory: {err}"))
331 })?;
332 }
333
334 let wal = SerdeWal::new(&wal_path, wal_options).map_err(|err| {
335 OperationError::service_error(format!("failed to open WAL {}: {err}", wal_path.display(),))
336 })?;
337
338 let segments_path = path.join(SEGMENTS_PATH);
339 if !segments_path.exists() {
340 fs::create_dir(&segments_path).map_err(|err| {
341 OperationError::service_error(format!("failed to create segments directory: {err}"))
342 })?;
343 }
344
345 Ok((wal, segments_path))
346}
347
348fn resolve_initial_config(
351 path: &Path,
352 config: Option<EdgeConfig>,
353) -> OperationResult<Option<EdgeConfig>> {
354 let persisted = match EdgeConfig::load(path) {
355 Some(Ok(c)) => Some(c),
356 Some(Err(e)) => return Err(e),
357 None => None,
358 };
359 Ok(match (config, persisted) {
360 (Some(provided), Some(persisted)) => Some(provided.fill_unspecified_from(&persisted)),
362 (Some(provided), None) => Some(provided),
363 (None, persisted) => persisted,
364 })
365}
366
367pub(crate) fn scan_segment_dirs(segments_path: &Path) -> OperationResult<HashMap<Uuid, PathBuf>> {
373 let segments_dir = fs::read_dir(segments_path).map_err(|err| {
374 OperationError::service_error(format!("failed to read segments directory: {err}"))
375 })?;
376
377 let mut result = HashMap::new();
378
379 for entry in segments_dir {
380 let entry = entry.map_err(|err| {
381 OperationError::service_error(format!(
382 "failed to read entry in segments directory: {err}",
383 ))
384 })?;
385
386 let segment_path = entry.path();
387
388 if !segment_path.is_dir() {
389 log::warn!(
390 "Skipping non-directory segment entry {}",
391 segment_path.display(),
392 );
393 continue;
394 }
395
396 if segment_path
397 .file_name()
398 .and_then(|n| n.to_str())
399 .is_some_and(|n| n.starts_with('.'))
400 {
401 log::warn!(
402 "Skipping hidden segment directory {}",
403 segment_path.display(),
404 );
405 continue;
406 }
407
408 let Some((segment_path, segment_uuid)) = normalize_segment_dir(&segment_path)? else {
409 continue;
410 };
411
412 result.insert(segment_uuid, segment_path);
413 }
414
415 Ok(result)
416}
417
418fn load_segments(segments_path: &Path) -> OperationResult<(SegmentHolder, Option<EdgeConfig>)> {
423 let mut segments = SegmentHolder::default();
424 let mut derived: Option<EdgeConfig> = None;
425
426 let mut segment_dirs: Vec<_> = scan_segment_dirs(segments_path)?.into_iter().collect();
427 segment_dirs.sort_unstable_by_key(|(segment_uuid, _)| *segment_uuid);
428
429 for (segment_uuid, segment_path) in segment_dirs {
430 let mut segment = load_segment(&segment_path, segment_uuid, None, &AtomicBool::new(false))
431 .map_err(|err| {
432 OperationError::service_error(format!(
433 "failed to load segment {}: {err}",
434 segment_path.display(),
435 ))
436 })?;
437
438 let segment_cfg = segment.config();
439 if let Some(acc) = derived.as_ref() {
440 acc.check_compatible_with_segment_config(segment_cfg)
441 .map_err(|err| {
442 OperationError::service_error(format!(
443 "segment {} is incompatible with previously loaded segments: {err}",
444 segment_path.display(),
445 ))
446 })?;
447 }
448 derived = Some(EdgeConfig::fold_from_segment_config(derived, segment_cfg));
449
450 segment.check_consistency_and_repair().map_err(|err| {
451 OperationError::service_error(format!(
452 "failed to repair segment {}: {err}",
453 segment_path.display(),
454 ))
455 })?;
456
457 segments.add_new(segment);
458 }
459
460 Ok((segments, derived))
461}
462
463fn ensure_appendable_segment(
464 segments: &mut SegmentHolder,
465 path: &Path,
466 segments_path: &Path,
467 config: &EdgeConfig,
468) -> OperationResult<()> {
469 if segments.has_appendable_segment() {
470 return Ok(());
471 }
472
473 let payload_index_schema_path = path.join(PAYLOAD_INDEX_CONFIG_FILE);
474 let payload_index_schema = SaveOnDisk::load_or_init_default(&payload_index_schema_path)
475 .map_err(|err| {
476 OperationError::service_error(format!(
477 "failed to initialize payload index schema file {}: {err}",
478 payload_index_schema_path.display(),
479 ))
480 })?;
481
482 segments.create_appendable_segment(
483 segments_path,
484 config.plain_segment_config(),
485 Arc::new(payload_index_schema),
486 None,
487 )?;
488
489 debug_assert!(segments.has_appendable_segment());
490 Ok(())
491}