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