Skip to main content

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}