qdrant_edge/edge/update_only/mod.rs
1//! Update-only shard: a batch writer over an edge-shard directory, the mirror
2//! image of [`ReadOnlyEdgeShard`](crate::ReadOnlyEdgeShard). Its whole public
3//! surface is [`apply_batch`].
4//!
5//! Built for the serverless updater's cost model — batches of many tiny
6//! operations, remote per-file reads, no long-lived process:
7//!
8//! * a batch is folded before it is applied: a point is read at most once and
9//! written at most once, however many operations named it;
10//! * only the components a write needs are opened, and every lookup is one
11//! batched pass per component over the whole point set;
12//! * there is no WAL: a batch is durable when the storages are flushed.
13//!
14//! Storage is append-only throughout. Updating a point appends it in full and
15//! tombstones its old slot; a deletion writes nothing but the deleted-points
16//! bitmask.
17//!
18//! [`apply_batch`]: UpdateOnlyEdgeShard::apply_batch
19
20mod apply;
21mod batch;
22mod holder;
23mod lifecycle;
24mod preview;
25
26use std::path::{Path, PathBuf};
27use std::sync::Arc;
28
29use crate::common::universal_io::UniversalRead;
30use parking_lot::RwLock;
31use rayon::ThreadPool;
32use crate::segment::types::SegmentConfig;
33use uuid::Uuid;
34
35pub use self::apply::UpdateBatchOutcome;
36pub use self::batch::{PointUpdates, UpdateBatchPlan};
37use self::holder::UpdateOnlySegmentHolder;
38pub use self::preview::{PointAction, PointCopy, PointPreview, UpdateBatchPreview};
39
40/// A batch writer over the segments of one shard directory, generic over the
41/// backend `S`.
42///
43/// Compared to [`EdgeShard`](crate::EdgeShard), there is no WAL, no
44/// optimizers, and no `EdgeConfig` — the write target's own segment config is
45/// the only configuration a write needs.
46pub struct UpdateOnlyEdgeShard<S: UniversalRead + 'static> {
47 path: PathBuf,
48 /// Backend the segments were opened on, and the one their appends go
49 /// through. Unread until the writer can create the appendable segment a
50 /// fresh directory needs.
51 #[expect(dead_code)]
52 fs: S::Fs,
53 segments: RwLock<UpdateOnlySegmentHolder<S>>,
54 /// Thread pool the per-segment work of a batch runs on: on a remote
55 /// backend each segment's reads block on the network, so segments are
56 /// visited in parallel.
57 pool: Arc<ThreadPool>,
58}
59
60/// One segment's schema, as reported by
61/// [`UpdateOnlyEdgeShard::segment_configs`].
62pub struct SegmentConfigInfo {
63 pub uuid: Uuid,
64 /// Whether this segment is the write target — the one every write in a
65 /// batch is appended to.
66 pub is_write_target: bool,
67 pub config: SegmentConfig,
68}
69
70impl<S: UniversalRead + 'static> UpdateOnlyEdgeShard<S> {
71 pub fn path(&self) -> &Path {
72 &self.path
73 }
74
75 /// Number of segments the writer has open.
76 pub fn segments_count(&self) -> usize {
77 self.segments.read().len()
78 }
79
80 /// Every segment's config, cloned out, with the write target marked.
81 /// Order is unspecified. The write target's config is the schema a write
82 /// must conform to: the named vectors a point carries, and their shapes.
83 pub fn segment_configs(&self) -> Vec<SegmentConfigInfo> {
84 let segments = self.segments.read();
85 let write_target = segments.write_target_uuid();
86 segments
87 .iter()
88 .map(|(uuid, segment)| SegmentConfigInfo {
89 uuid,
90 is_write_target: Some(uuid) == write_target,
91 config: segment.read().segment_config.clone(),
92 })
93 .collect()
94 }
95}