Skip to main content

selene_graph/vector_index/
rebuild.rs

1//! Vector-index rebuild reporting.
2
3use std::num::NonZeroUsize;
4
5use selene_core::{DbString, HnswIndexConfig, IvfIndexConfig};
6
7use super::{VectorIndexKind, VectorIndexMemoryUsage};
8
9/// Policy for explicit vector-index maintenance runs.
10///
11/// The policy only controls derived index maintenance. It never changes graph
12/// data, WAL contents, or vector-index registrations.
13#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
14pub struct VectorIndexMaintenancePolicy {
15    /// Maximum recommended indexes to rebuild in one maintenance call.
16    ///
17    /// `None` rebuilds every index whose diagnostics currently recommend
18    /// maintenance. A cap lets embedders amortize IVF retraining across
19    /// foreground write-heavy periods while keeping reads free of rebuild work.
20    pub max_indexes_per_run: Option<NonZeroUsize>,
21}
22
23impl VectorIndexMaintenancePolicy {
24    /// Return the default recommended-index maintenance policy.
25    #[must_use]
26    pub const fn recommended() -> Self {
27        Self {
28            max_indexes_per_run: None,
29        }
30    }
31
32    /// Return a policy capped to at most `max_indexes_per_run` rebuilds.
33    #[must_use]
34    pub const fn with_max_indexes_per_run(mut self, max_indexes_per_run: NonZeroUsize) -> Self {
35        self.max_indexes_per_run = Some(max_indexes_per_run);
36        self
37    }
38}
39
40/// One vector-index entry rebuilt by [`VectorIndexRebuildReport`].
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct VectorIndexRebuildEntry {
43    /// Indexed node label.
44    pub label: DbString,
45    /// Indexed node property.
46    pub property: DbString,
47    /// Optional explicit index catalog name.
48    pub name: Option<DbString>,
49    /// Rebuilt index algorithm kind.
50    pub kind: VectorIndexKind,
51    /// Rebuilt vector dimensionality.
52    pub dimension: u32,
53    /// HNSW construction config for HNSW indexes.
54    pub hnsw_config: Option<HnswIndexConfig>,
55    /// IVF construction config for IVF indexes.
56    pub ivf_config: Option<IvfIndexConfig>,
57    /// Memory and cardinality before the rebuild.
58    pub before: VectorIndexMemoryUsage,
59    /// Memory and cardinality after the rebuild.
60    pub after: VectorIndexMemoryUsage,
61}
62
63/// Result returned after rebuilding all registered vector indexes.
64#[derive(Clone, Debug, Default, Eq, PartialEq)]
65pub struct VectorIndexRebuildReport {
66    /// Number of vector-index registrations rebuilt.
67    pub indexes_rebuilt: usize,
68    /// Per-index before/after memory accounting.
69    pub entries: Vec<VectorIndexRebuildEntry>,
70    /// HNSW entries removed by the rebuild, including stale deleted versions.
71    pub reclaimed_hnsw_entries: usize,
72    /// Stale HNSW deleted entries removed by the rebuild.
73    pub reclaimed_hnsw_deleted_entries: usize,
74    /// IVF entries removed by the rebuild, including stale deleted versions.
75    pub reclaimed_ivf_entries: usize,
76    /// Stale IVF deleted entries removed by the rebuild.
77    pub reclaimed_ivf_deleted_entries: usize,
78    /// Estimated index-owned bytes reclaimed by the rebuild.
79    pub reclaimed_index_bytes: usize,
80    /// Estimated reachable bytes reclaimed, including ANN vector components.
81    pub reclaimed_reachable_bytes: usize,
82}
83
84impl VectorIndexRebuildReport {
85    pub(crate) fn new(entries: Vec<VectorIndexRebuildEntry>) -> Self {
86        let mut report = Self {
87            indexes_rebuilt: entries.len(),
88            entries,
89            ..Self::default()
90        };
91        for entry in &report.entries {
92            report.reclaimed_hnsw_entries = report.reclaimed_hnsw_entries.saturating_add(
93                entry
94                    .before
95                    .hnsw_entries
96                    .saturating_sub(entry.after.hnsw_entries),
97            );
98            report.reclaimed_hnsw_deleted_entries =
99                report.reclaimed_hnsw_deleted_entries.saturating_add(
100                    entry
101                        .before
102                        .hnsw_deleted_entries
103                        .saturating_sub(entry.after.hnsw_deleted_entries),
104                );
105            report.reclaimed_ivf_entries = report.reclaimed_ivf_entries.saturating_add(
106                entry
107                    .before
108                    .ivf_entries
109                    .saturating_sub(entry.after.ivf_entries),
110            );
111            report.reclaimed_ivf_deleted_entries =
112                report.reclaimed_ivf_deleted_entries.saturating_add(
113                    entry
114                        .before
115                        .ivf_deleted_entries
116                        .saturating_sub(entry.after.ivf_deleted_entries),
117                );
118            report.reclaimed_index_bytes = report.reclaimed_index_bytes.saturating_add(
119                entry
120                    .before
121                    .estimated_index_bytes
122                    .saturating_sub(entry.after.estimated_index_bytes),
123            );
124            report.reclaimed_reachable_bytes = report.reclaimed_reachable_bytes.saturating_add(
125                entry
126                    .before
127                    .estimated_reachable_bytes
128                    .saturating_sub(entry.after.estimated_reachable_bytes),
129            );
130        }
131        report
132    }
133}