1use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::path::Path;
16
17use crate::dsl::{Schema, VectorIndexType};
18use crate::error::{Error, Result};
19
20pub const INDEX_META_FILENAME: &str = "metadata.json";
22const INDEX_META_TMP_FILENAME: &str = "metadata.json.tmp";
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
27pub enum VectorIndexState {
28 #[default]
30 Flat,
31 Built {
33 vector_count: usize,
35 num_clusters: usize,
37 },
38}
39
40fn default_true() -> bool {
41 true
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct SegmentMetaInfo {
48 pub num_docs: u32,
50 pub ancestors: Vec<String>,
52 pub generation: u32,
54 #[serde(default)]
58 pub reordered: bool,
59 #[serde(default = "default_true")]
64 pub bp_converged: bool,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct FieldVectorMeta {
70 pub field_id: u32,
72 pub index_type: VectorIndexType,
74 pub state: VectorIndexState,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub centroids_file: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub codebook_file: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct IndexMetadata {
87 pub version: u32,
89 pub schema: Schema,
91 #[serde(default)]
94 pub segment_metas: HashMap<String, SegmentMetaInfo>,
95 #[serde(default)]
97 pub vector_fields: HashMap<u32, FieldVectorMeta>,
98 #[serde(default)]
100 pub total_vectors: usize,
101}
102
103impl IndexMetadata {
104 pub fn new(schema: Schema) -> Self {
106 Self {
107 version: 1,
108 schema,
109 segment_metas: HashMap::new(),
110 vector_fields: HashMap::new(),
111 total_vectors: 0,
112 }
113 }
114
115 pub fn segment_ids(&self) -> Vec<String> {
117 let mut ids: Vec<String> = self.segment_metas.keys().cloned().collect();
118 ids.sort();
119 ids
120 }
121
122 pub fn add_segment(&mut self, segment_id: String, num_docs: u32) {
124 self.segment_metas.insert(
125 segment_id,
126 SegmentMetaInfo {
127 num_docs,
128 ancestors: Vec::new(),
129 generation: 0,
130 reordered: false,
131 bp_converged: true,
132 },
133 );
134 }
135
136 pub fn add_merged_segment(
138 &mut self,
139 segment_id: String,
140 num_docs: u32,
141 ancestors: Vec<String>,
142 generation: u32,
143 reordered: bool,
144 bp_converged: bool,
145 ) {
146 self.segment_metas.insert(
147 segment_id,
148 SegmentMetaInfo {
149 num_docs,
150 ancestors,
151 generation,
152 reordered,
153 bp_converged,
154 },
155 );
156 }
157
158 pub fn remove_segment(&mut self, segment_id: &str) {
160 self.segment_metas.remove(segment_id);
161 }
162
163 pub fn has_segment(&self, segment_id: &str) -> bool {
165 self.segment_metas.contains_key(segment_id)
166 }
167
168 pub fn segment_doc_count(&self, segment_id: &str) -> Option<u32> {
170 self.segment_metas.get(segment_id).map(|m| m.num_docs)
171 }
172
173 pub fn is_field_built(&self, field_id: u32) -> bool {
175 self.vector_fields
176 .get(&field_id)
177 .map(|f| matches!(f.state, VectorIndexState::Built { .. }))
178 .unwrap_or(false)
179 }
180
181 pub fn get_field_meta(&self, field_id: u32) -> Option<&FieldVectorMeta> {
183 self.vector_fields.get(&field_id)
184 }
185
186 pub fn init_field(&mut self, field_id: u32, index_type: VectorIndexType) {
188 self.vector_fields
189 .entry(field_id)
190 .or_insert(FieldVectorMeta {
191 field_id,
192 index_type,
193 state: VectorIndexState::Flat,
194 centroids_file: None,
195 codebook_file: None,
196 });
197 }
198
199 pub fn mark_field_built(
201 &mut self,
202 field_id: u32,
203 vector_count: usize,
204 num_clusters: usize,
205 centroids_file: String,
206 codebook_file: Option<String>,
207 ) {
208 if let Some(field) = self.vector_fields.get_mut(&field_id) {
209 field.state = VectorIndexState::Built {
210 vector_count,
211 num_clusters,
212 };
213 field.centroids_file = Some(centroids_file);
214 field.codebook_file = codebook_file;
215 }
216 }
217
218 pub fn should_build_field(&self, field_id: u32, threshold: usize) -> bool {
220 if self.is_field_built(field_id) {
222 return false;
223 }
224 self.total_vectors >= threshold
226 }
227
228 pub async fn load<D: crate::directories::Directory>(dir: &D) -> Result<Self> {
233 let path = Path::new(INDEX_META_FILENAME);
234 match dir.open_read(path).await {
235 Ok(slice) => {
236 let bytes = slice.read_bytes().await?;
237 serde_json::from_slice(bytes.as_slice())
238 .map_err(|e| Error::Serialization(e.to_string()))
239 }
240 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
241 let tmp_path = Path::new(INDEX_META_TMP_FILENAME);
243 let slice = dir.open_read(tmp_path).await?;
244 let bytes = slice.read_bytes().await?;
245 let meta: Self = serde_json::from_slice(bytes.as_slice())
246 .map_err(|e| Error::Serialization(e.to_string()))?;
247 log::warn!("Recovered metadata from temp file (previous crash during save)");
248 Ok(meta)
249 }
250 Err(e) => Err(Error::Io(e)),
251 }
252 }
253
254 pub async fn save<D: crate::directories::DirectoryWriter>(&self, dir: &D) -> Result<()> {
259 let bytes = self.serialize_to_bytes()?;
260 Self::save_bytes(dir, &bytes).await
261 }
262
263 pub fn serialize_to_bytes(&self) -> Result<Vec<u8>> {
266 serde_json::to_vec_pretty(self).map_err(|e| Error::Serialization(e.to_string()))
267 }
268
269 pub async fn save_bytes<D: crate::directories::DirectoryWriter>(
274 dir: &D,
275 bytes: &[u8],
276 ) -> Result<()> {
277 let tmp_path = Path::new(INDEX_META_TMP_FILENAME);
278 let final_path = Path::new(INDEX_META_FILENAME);
279 dir.write(tmp_path, bytes).await.map_err(Error::Io)?;
280 dir.sync().await.map_err(Error::Io)?;
281 dir.rename(tmp_path, final_path).await.map_err(Error::Io)?;
282 dir.sync().await.map_err(Error::Io)?;
283 Ok(())
284 }
285
286 pub async fn load_trained_from_fields<D: crate::directories::Directory>(
289 vector_fields: &HashMap<u32, FieldVectorMeta>,
290 dir: &D,
291 ) -> Option<crate::segment::TrainedVectorStructures> {
292 use std::sync::Arc;
293
294 let mut centroids = rustc_hash::FxHashMap::default();
295 let mut codebooks = rustc_hash::FxHashMap::default();
296
297 log::debug!(
298 "[trained] loading trained structures, vector_fields={:?}",
299 vector_fields.keys().collect::<Vec<_>>()
300 );
301
302 for (field_id, field_meta) in vector_fields {
303 log::debug!(
304 "[trained] field {} state={:?} centroids_file={:?} codebook_file={:?}",
305 field_id,
306 field_meta.state,
307 field_meta.centroids_file,
308 field_meta.codebook_file,
309 );
310 if !matches!(field_meta.state, VectorIndexState::Built { .. }) {
311 log::debug!("[trained] field {} skipped (not Built)", field_id);
312 continue;
313 }
314
315 match &field_meta.centroids_file {
317 None => {
318 log::warn!(
319 "[trained] field {} is Built but has no centroids_file",
320 field_id
321 );
322 }
323 Some(file) => match dir.open_read(Path::new(file)).await {
324 Err(e) => {
325 log::warn!(
326 "[trained] field {} failed to open centroids file '{}': {}",
327 field_id,
328 file,
329 e
330 );
331 }
332 Ok(slice) => match slice.read_bytes().await {
333 Err(e) => {
334 log::warn!(
335 "[trained] field {} failed to read centroids file '{}': {}",
336 field_id,
337 file,
338 e
339 );
340 }
341 Ok(bytes) => {
342 match bincode::serde::decode_from_slice::<
343 crate::structures::CoarseCentroids,
344 _,
345 >(
346 bytes.as_slice(), bincode::config::standard()
347 )
348 .map(|(v, _)| v)
349 {
350 Err(e) => {
351 log::warn!(
352 "[trained] field {} failed to deserialize centroids from '{}': {}",
353 field_id,
354 file,
355 e
356 );
357 }
358 Ok(c) => {
359 log::debug!(
360 "[trained] field {} loaded centroids ({} clusters)",
361 field_id,
362 c.num_clusters
363 );
364 centroids.insert(*field_id, Arc::new(c));
365 }
366 }
367 }
368 },
369 },
370 }
371
372 match &field_meta.codebook_file {
374 None => {} Some(file) => match dir.open_read(Path::new(file)).await {
376 Err(e) => {
377 log::warn!(
378 "[trained] field {} failed to open codebook file '{}': {}",
379 field_id,
380 file,
381 e
382 );
383 }
384 Ok(slice) => match slice.read_bytes().await {
385 Err(e) => {
386 log::warn!(
387 "[trained] field {} failed to read codebook file '{}': {}",
388 field_id,
389 file,
390 e
391 );
392 }
393 Ok(bytes) => {
394 match bincode::serde::decode_from_slice::<
395 crate::structures::PQCodebook,
396 _,
397 >(
398 bytes.as_slice(), bincode::config::standard()
399 )
400 .map(|(v, _)| v)
401 {
402 Err(e) => {
403 log::warn!(
404 "[trained] field {} failed to deserialize codebook from '{}': {}",
405 field_id,
406 file,
407 e
408 );
409 }
410 Ok(c) => {
411 log::debug!("[trained] field {} loaded codebook", field_id);
412 codebooks.insert(*field_id, Arc::new(c));
413 }
414 }
415 }
416 },
417 },
418 }
419 }
420
421 if centroids.is_empty() {
422 None
423 } else {
424 Some(crate::segment::TrainedVectorStructures {
425 centroids,
426 codebooks,
427 })
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 fn test_schema() -> Schema {
437 Schema::default()
438 }
439
440 #[test]
441 fn test_metadata_init() {
442 let mut meta = IndexMetadata::new(test_schema());
443 assert_eq!(meta.total_vectors, 0);
444 assert!(meta.segment_metas.is_empty());
445 assert!(!meta.is_field_built(0));
446
447 meta.init_field(0, VectorIndexType::IvfRaBitQ);
448 assert!(!meta.is_field_built(0));
449 assert!(meta.vector_fields.contains_key(&0));
450 }
451
452 #[test]
453 fn test_metadata_segments() {
454 let mut meta = IndexMetadata::new(test_schema());
455 meta.add_segment("abc123".to_string(), 50);
456 meta.add_segment("def456".to_string(), 100);
457 assert_eq!(meta.segment_metas.len(), 2);
458 assert_eq!(meta.segment_doc_count("abc123"), Some(50));
459 assert_eq!(meta.segment_doc_count("def456"), Some(100));
460
461 meta.add_segment("abc123".to_string(), 75);
463 assert_eq!(meta.segment_metas.len(), 2);
464 assert_eq!(meta.segment_doc_count("abc123"), Some(75));
465
466 meta.remove_segment("abc123");
467 assert_eq!(meta.segment_metas.len(), 1);
468 assert!(meta.has_segment("def456"));
469 assert!(!meta.has_segment("abc123"));
470 }
471
472 #[test]
473 fn test_mark_field_built() {
474 let mut meta = IndexMetadata::new(test_schema());
475 meta.init_field(0, VectorIndexType::IvfRaBitQ);
476 meta.total_vectors = 10000;
477
478 assert!(!meta.is_field_built(0));
479
480 meta.mark_field_built(0, 10000, 256, "field_0_centroids.bin".to_string(), None);
481
482 assert!(meta.is_field_built(0));
483 let field = meta.get_field_meta(0).unwrap();
484 assert_eq!(
485 field.centroids_file.as_deref(),
486 Some("field_0_centroids.bin")
487 );
488 }
489
490 #[test]
491 fn test_should_build_field() {
492 let mut meta = IndexMetadata::new(test_schema());
493 meta.init_field(0, VectorIndexType::IvfRaBitQ);
494
495 meta.total_vectors = 500;
497 assert!(!meta.should_build_field(0, 1000));
498
499 meta.total_vectors = 1500;
501 assert!(meta.should_build_field(0, 1000));
502
503 meta.mark_field_built(0, 1500, 256, "centroids.bin".to_string(), None);
505 assert!(!meta.should_build_field(0, 1000));
506 }
507
508 #[test]
509 fn test_serialization() {
510 let mut meta = IndexMetadata::new(test_schema());
511 meta.add_segment("seg1".to_string(), 100);
512 meta.init_field(0, VectorIndexType::IvfRaBitQ);
513 meta.total_vectors = 5000;
514
515 let json = serde_json::to_string_pretty(&meta).unwrap();
516 let loaded: IndexMetadata = serde_json::from_str(&json).unwrap();
517
518 assert_eq!(loaded.segment_ids().len(), meta.segment_ids().len());
519 assert_eq!(loaded.segment_doc_count("seg1"), Some(100));
520 assert_eq!(loaded.total_vectors, meta.total_vectors);
521 assert!(loaded.vector_fields.contains_key(&0));
522 }
523
524 #[test]
525 fn test_merged_segment_lineage() {
526 let mut meta = IndexMetadata::new(test_schema());
527 meta.add_segment("a".to_string(), 50);
528 meta.add_segment("b".to_string(), 75);
529
530 assert_eq!(meta.segment_metas["a"].generation, 0);
532 assert!(meta.segment_metas["a"].ancestors.is_empty());
533
534 meta.add_merged_segment(
536 "c".to_string(),
537 125,
538 vec!["a".to_string(), "b".to_string()],
539 1,
540 false,
541 true,
542 );
543 assert_eq!(meta.segment_metas["c"].generation, 1);
544 assert_eq!(meta.segment_metas["c"].ancestors, vec!["a", "b"]);
545 assert_eq!(meta.segment_doc_count("c"), Some(125));
546
547 meta.add_segment("d".to_string(), 30);
549 meta.add_merged_segment(
550 "e".to_string(),
551 155,
552 vec!["c".to_string(), "d".to_string()],
553 2,
554 false,
555 true,
556 );
557 assert_eq!(meta.segment_metas["e"].generation, 2);
558 }
559}