1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Persistence methods for `NativeHnswIndex` (save/load).
//!
//! Extracted from `native_index.rs` to keep file NLOC under 500.
//! All public API signatures remain unchanged.
use super::native_index::NativeHnswIndex;
use super::native_inner::NativeHnswInner;
use super::params::HnswParams;
use super::persistence::{self, HnswMeta};
use crate::distance::DistanceMetric;
use parking_lot::RwLock;
use std::path::Path;
impl NativeHnswIndex {
/// Saves the index to disk.
///
/// # Errors
///
/// Returns an error if file operations fail.
pub fn save<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
let path = path.as_ref();
std::fs::create_dir_all(path)?;
// #617: stamp every on-disk artefact with the same monotonic generation
// so that a crash between any two renames (graph, mappings, vectors,
// meta) is detectable on reload. Errors are propagated rather than
// silently resetting to generation 1 on corrupted meta (Devin #618
// follow-up).
let new_gen = persistence::next_generation(path)?;
// Dump the HNSW graph itself (caller-specific — see persistence::save_sidecars).
let storage_mode = {
let inner = self.inner.read();
inner.file_dump(path, "native_hnsw")?;
inner.storage_mode()
};
// Graph-generation marker is written IMMEDIATELY after the graph dump
// and BEFORE the sidecars — see comment in `HnswIndex::save` for the
// full rationale (#617 Devin follow-up).
persistence::save_graph_generation(path, new_gen)?;
// Mappings + vectors + meta in one shared call (RF-DEDUP #448 Group C).
persistence::save_sidecars(
path,
&self.mappings,
&self.vectors,
&HnswMeta {
dimension: self.dimension,
metric: self.metric,
enable_vector_storage: self.enable_vector_storage,
storage_mode,
// `save_sidecars` overwrites this with `new_gen` (#617).
generation: 0,
},
new_gen,
)
}
/// Loads the index from disk.
///
/// # Arguments
///
/// * `path` - Path to the index directory
/// * `_dimension` - Ignored (read from metadata) - for API compatibility
/// * `_metric` - Ignored (read from metadata) - for API compatibility
///
/// # Errors
///
/// Returns an error if file operations fail or data is corrupted.
pub fn load<P: AsRef<Path>>(
path: P,
_dimension: usize,
_metric: DistanceMetric,
) -> std::io::Result<Self> {
let path = path.as_ref();
let meta = persistence::load_meta(path)?;
// Load HNSW graph (with storage mode for RaBitQ backend support).
let inner = NativeHnswInner::file_load_with_storage_mode(
path,
"native_hnsw",
meta.metric,
meta.dimension,
meta.storage_mode,
)?;
// Mappings + vectors in one shared call (RF-DEDUP #448 Group C).
let (mappings, vectors, enable_vector_storage) = persistence::load_sidecars(path, &meta)?;
Ok(Self {
dimension: meta.dimension,
metric: meta.metric,
inner: RwLock::new(inner),
mappings,
vectors,
enable_vector_storage,
params: HnswParams::auto(meta.dimension),
})
}
}