1use crate::diskann::config::DiskAnnConfig;
17use crate::diskann::graph::VamanaGraph;
18use crate::diskann::types::{DiskAnnError, DiskAnnResult, VectorId};
19use serde::{Deserialize, Serialize};
20use std::collections::HashMap;
21use std::fs::{File, OpenOptions};
22use std::io::{BufReader, BufWriter, Read, Write};
23use std::path::{Path, PathBuf};
24
25pub trait StorageBackend: Send + Sync {
27 fn write_vector(&mut self, vector_id: &VectorId, vector: &[f32]) -> DiskAnnResult<()>;
29
30 fn read_vector(&self, vector_id: &VectorId) -> DiskAnnResult<Vec<f32>>;
32
33 fn write_graph(&mut self, graph: &VamanaGraph) -> DiskAnnResult<()>;
35
36 fn read_graph(&self) -> DiskAnnResult<VamanaGraph>;
38
39 fn write_metadata(&mut self, metadata: &StorageMetadata) -> DiskAnnResult<()>;
41
42 fn read_metadata(&self) -> DiskAnnResult<StorageMetadata>;
44
45 fn clear(&mut self) -> DiskAnnResult<()>;
47
48 fn flush(&mut self) -> DiskAnnResult<()>;
50
51 fn size(&self) -> DiskAnnResult<u64>;
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct StorageMetadata {
58 pub version: String,
59 pub config: DiskAnnConfig,
60 pub num_vectors: usize,
61 pub created_at: chrono::DateTime<chrono::Utc>,
62 pub updated_at: chrono::DateTime<chrono::Utc>,
63}
64
65impl StorageMetadata {
66 pub fn new(config: DiskAnnConfig) -> Self {
67 let now = chrono::Utc::now();
68 Self {
69 version: env!("CARGO_PKG_VERSION").to_string(),
70 config,
71 num_vectors: 0,
72 created_at: now,
73 updated_at: now,
74 }
75 }
76
77 pub fn update_timestamp(&mut self) {
78 self.updated_at = chrono::Utc::now();
79 }
80}
81
82#[derive(Debug)]
84pub struct DiskStorage {
85 base_path: PathBuf,
86 vector_file: Option<PathBuf>,
87 graph_file: Option<PathBuf>,
88 metadata_file: Option<PathBuf>,
89 dimension: usize,
90 vector_cache: HashMap<VectorId, Vec<f32>>,
91 cache_limit: usize,
92}
93
94impl DiskStorage {
95 pub fn new<P: AsRef<Path>>(base_path: P, dimension: usize) -> DiskAnnResult<Self> {
97 let base_path = base_path.as_ref().to_path_buf();
98
99 if !base_path.exists() {
101 std::fs::create_dir_all(&base_path).map_err(|e| DiskAnnError::IoError {
102 message: format!("Failed to create directory: {}", e),
103 })?;
104 }
105
106 let vector_file = Some(base_path.join("vectors.bin"));
107 let graph_file = Some(base_path.join("graph.bin"));
108 let metadata_file = Some(base_path.join("metadata.json"));
109
110 Ok(Self {
111 base_path,
112 vector_file,
113 graph_file,
114 metadata_file,
115 dimension,
116 vector_cache: HashMap::new(),
117 cache_limit: 1000,
118 })
119 }
120
121 pub fn with_cache_limit(mut self, limit: usize) -> Self {
123 self.cache_limit = limit;
124 self
125 }
126
127 pub fn vector_file_path(&self) -> &Option<PathBuf> {
129 &self.vector_file
130 }
131
132 pub fn graph_file_path(&self) -> &Option<PathBuf> {
134 &self.graph_file
135 }
136
137 fn evict_cache_if_needed(&mut self) {
139 if self.vector_cache.len() > self.cache_limit {
140 if let Some(key) = self.vector_cache.keys().next().cloned() {
142 self.vector_cache.remove(&key);
143 }
144 }
145 }
146}
147
148impl Clone for DiskStorage {
149 fn clone(&self) -> Self {
150 Self {
151 base_path: self.base_path.clone(),
152 vector_file: self.vector_file.clone(),
153 graph_file: self.graph_file.clone(),
154 metadata_file: self.metadata_file.clone(),
155 dimension: self.dimension,
156 vector_cache: HashMap::new(), cache_limit: self.cache_limit,
158 }
159 }
160}
161
162impl StorageBackend for DiskStorage {
163 fn write_vector(&mut self, vector_id: &VectorId, vector: &[f32]) -> DiskAnnResult<()> {
164 if vector.len() != self.dimension {
165 return Err(DiskAnnError::DimensionMismatch {
166 expected: self.dimension,
167 actual: vector.len(),
168 });
169 }
170
171 self.vector_cache.insert(vector_id.clone(), vector.to_vec());
173 self.evict_cache_if_needed();
174
175 if let Some(path) = &self.vector_file {
177 let file = OpenOptions::new()
178 .create(true)
179 .append(true)
180 .open(path)
181 .map_err(|e| DiskAnnError::IoError {
182 message: format!("Failed to open vector file: {}", e),
183 })?;
184
185 let mut writer = BufWriter::new(file);
186
187 let id_bytes = vector_id.as_bytes();
189 writer
190 .write_all(&(id_bytes.len() as u32).to_le_bytes())
191 .map_err(|e| DiskAnnError::IoError {
192 message: format!("Failed to write vector ID length: {}", e),
193 })?;
194 writer
195 .write_all(id_bytes)
196 .map_err(|e| DiskAnnError::IoError {
197 message: format!("Failed to write vector ID: {}", e),
198 })?;
199
200 for &value in vector {
202 writer
203 .write_all(&value.to_le_bytes())
204 .map_err(|e| DiskAnnError::IoError {
205 message: format!("Failed to write vector data: {}", e),
206 })?;
207 }
208
209 writer.flush().map_err(|e| DiskAnnError::IoError {
210 message: format!("Failed to flush vector file: {}", e),
211 })?;
212 }
213
214 Ok(())
215 }
216
217 fn read_vector(&self, vector_id: &VectorId) -> DiskAnnResult<Vec<f32>> {
218 if let Some(vector) = self.vector_cache.get(vector_id) {
220 return Ok(vector.clone());
221 }
222
223 if let Some(path) = &self.vector_file {
225 if !path.exists() {
226 return Err(DiskAnnError::VectorNotFound {
227 id: vector_id.clone(),
228 });
229 }
230
231 let file = File::open(path).map_err(|e| DiskAnnError::IoError {
232 message: format!("Failed to open vector file: {}", e),
233 })?;
234 let mut reader = BufReader::new(file);
235
236 loop {
238 let mut id_len_bytes = [0u8; 4];
240 if reader.read_exact(&mut id_len_bytes).is_err() {
241 break; }
243 let id_len = u32::from_le_bytes(id_len_bytes) as usize;
244
245 let mut id_bytes = vec![0u8; id_len];
247 reader
248 .read_exact(&mut id_bytes)
249 .map_err(|e| DiskAnnError::IoError {
250 message: format!("Failed to read vector ID: {}", e),
251 })?;
252 let id = String::from_utf8(id_bytes).map_err(|e| DiskAnnError::IoError {
253 message: format!("Invalid UTF-8 in vector ID: {}", e),
254 })?;
255
256 let mut vector = vec![0.0f32; self.dimension];
258 for value in &mut vector {
259 let mut bytes = [0u8; 4];
260 reader
261 .read_exact(&mut bytes)
262 .map_err(|e| DiskAnnError::IoError {
263 message: format!("Failed to read vector data: {}", e),
264 })?;
265 *value = f32::from_le_bytes(bytes);
266 }
267
268 if &id == vector_id {
269 return Ok(vector);
270 }
271 }
272
273 Err(DiskAnnError::VectorNotFound {
274 id: vector_id.clone(),
275 })
276 } else {
277 Err(DiskAnnError::VectorNotFound {
278 id: vector_id.clone(),
279 })
280 }
281 }
282
283 fn write_graph(&mut self, graph: &VamanaGraph) -> DiskAnnResult<()> {
284 if let Some(path) = &self.graph_file {
285 let file = File::create(path).map_err(|e| DiskAnnError::IoError {
286 message: format!("Failed to create graph file: {}", e),
287 })?;
288
289 let mut writer = BufWriter::new(file);
290 oxicode::serde::encode_into_std_write(graph, &mut writer, oxicode::config::standard())?;
291
292 let file = writer.into_inner().map_err(|e| DiskAnnError::IoError {
298 message: format!("Failed to flush graph file: {}", e),
299 })?;
300 file.sync_all().map_err(|e| DiskAnnError::IoError {
301 message: format!("Failed to sync graph file: {}", e),
302 })?;
303 }
304 Ok(())
305 }
306
307 fn read_graph(&self) -> DiskAnnResult<VamanaGraph> {
308 if let Some(path) = &self.graph_file {
309 if !path.exists() {
310 return Err(DiskAnnError::StorageError {
311 message: "Graph file does not exist".to_string(),
312 });
313 }
314
315 let file = File::open(path).map_err(|e| DiskAnnError::IoError {
316 message: format!("Failed to open graph file: {}", e),
317 })?;
318
319 let mut reader = BufReader::new(file);
320 let (graph, _) =
321 oxicode::serde::decode_from_std_read(&mut reader, oxicode::config::standard())?;
322 Ok(graph)
323 } else {
324 Err(DiskAnnError::StorageError {
325 message: "Graph file path not set".to_string(),
326 })
327 }
328 }
329
330 fn write_metadata(&mut self, metadata: &StorageMetadata) -> DiskAnnResult<()> {
331 if let Some(path) = &self.metadata_file {
332 let mut file = File::create(path).map_err(|e| DiskAnnError::IoError {
333 message: format!("Failed to create metadata file: {}", e),
334 })?;
335
336 serde_json::to_writer_pretty(&mut file, metadata).map_err(|e| {
337 DiskAnnError::SerializationError {
338 message: format!("Failed to serialize metadata: {}", e),
339 }
340 })?;
341
342 file.sync_all().map_err(|e| DiskAnnError::IoError {
344 message: format!("Failed to sync metadata file: {}", e),
345 })?;
346 }
347 Ok(())
348 }
349
350 fn read_metadata(&self) -> DiskAnnResult<StorageMetadata> {
351 if let Some(path) = &self.metadata_file {
352 if !path.exists() {
353 return Err(DiskAnnError::StorageError {
354 message: "Metadata file does not exist".to_string(),
355 });
356 }
357
358 let file = File::open(path).map_err(|e| DiskAnnError::IoError {
359 message: format!("Failed to open metadata file: {}", e),
360 })?;
361
362 let metadata =
363 serde_json::from_reader(file).map_err(|e| DiskAnnError::SerializationError {
364 message: format!("Failed to deserialize metadata: {}", e),
365 })?;
366
367 Ok(metadata)
368 } else {
369 Err(DiskAnnError::StorageError {
370 message: "Metadata file path not set".to_string(),
371 })
372 }
373 }
374
375 fn clear(&mut self) -> DiskAnnResult<()> {
376 self.vector_cache.clear();
377
378 if let Some(path) = &self.vector_file {
379 if path.exists() {
380 std::fs::remove_file(path).map_err(|e| DiskAnnError::IoError {
381 message: format!("Failed to remove vector file: {}", e),
382 })?;
383 }
384 }
385
386 if let Some(path) = &self.graph_file {
387 if path.exists() {
388 std::fs::remove_file(path).map_err(|e| DiskAnnError::IoError {
389 message: format!("Failed to remove graph file: {}", e),
390 })?;
391 }
392 }
393
394 if let Some(path) = &self.metadata_file {
395 if path.exists() {
396 std::fs::remove_file(path).map_err(|e| DiskAnnError::IoError {
397 message: format!("Failed to remove metadata file: {}", e),
398 })?;
399 }
400 }
401
402 Ok(())
403 }
404
405 fn flush(&mut self) -> DiskAnnResult<()> {
406 Ok(())
408 }
409
410 fn size(&self) -> DiskAnnResult<u64> {
411 let mut total_size = 0u64;
412
413 if let Some(path) = &self.vector_file {
414 if path.exists() {
415 total_size += std::fs::metadata(path)
416 .map_err(|e| DiskAnnError::IoError {
417 message: format!("Failed to get vector file size: {}", e),
418 })?
419 .len();
420 }
421 }
422
423 if let Some(path) = &self.graph_file {
424 if path.exists() {
425 total_size += std::fs::metadata(path)
426 .map_err(|e| DiskAnnError::IoError {
427 message: format!("Failed to get graph file size: {}", e),
428 })?
429 .len();
430 }
431 }
432
433 if let Some(path) = &self.metadata_file {
434 if path.exists() {
435 total_size += std::fs::metadata(path)
436 .map_err(|e| DiskAnnError::IoError {
437 message: format!("Failed to get metadata file size: {}", e),
438 })?
439 .len();
440 }
441 }
442
443 Ok(total_size)
444 }
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
449pub struct MemoryMappedStorage {
450 base_path: PathBuf,
451 dimension: usize,
452}
453
454impl MemoryMappedStorage {
455 pub fn new<P: AsRef<Path>>(base_path: P, dimension: usize) -> DiskAnnResult<Self> {
456 Ok(Self {
457 base_path: base_path.as_ref().to_path_buf(),
458 dimension,
459 })
460 }
461}
462
463impl StorageBackend for MemoryMappedStorage {
464 fn write_vector(&mut self, _vector_id: &VectorId, _vector: &[f32]) -> DiskAnnResult<()> {
465 Err(DiskAnnError::StorageError {
466 message: "MemoryMappedStorage not yet implemented".to_string(),
467 })
468 }
469
470 fn read_vector(&self, _vector_id: &VectorId) -> DiskAnnResult<Vec<f32>> {
471 Err(DiskAnnError::StorageError {
472 message: "MemoryMappedStorage not yet implemented".to_string(),
473 })
474 }
475
476 fn write_graph(&mut self, _graph: &VamanaGraph) -> DiskAnnResult<()> {
477 Err(DiskAnnError::StorageError {
478 message: "MemoryMappedStorage not yet implemented".to_string(),
479 })
480 }
481
482 fn read_graph(&self) -> DiskAnnResult<VamanaGraph> {
483 Err(DiskAnnError::StorageError {
484 message: "MemoryMappedStorage not yet implemented".to_string(),
485 })
486 }
487
488 fn write_metadata(&mut self, _metadata: &StorageMetadata) -> DiskAnnResult<()> {
489 Err(DiskAnnError::StorageError {
490 message: "MemoryMappedStorage not yet implemented".to_string(),
491 })
492 }
493
494 fn read_metadata(&self) -> DiskAnnResult<StorageMetadata> {
495 Err(DiskAnnError::StorageError {
496 message: "MemoryMappedStorage not yet implemented".to_string(),
497 })
498 }
499
500 fn clear(&mut self) -> DiskAnnResult<()> {
501 Ok(())
502 }
503
504 fn flush(&mut self) -> DiskAnnResult<()> {
505 Ok(())
506 }
507
508 fn size(&self) -> DiskAnnResult<u64> {
509 Ok(0)
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
516 use super::*;
517 use crate::diskann::config::PruningStrategy;
518 use std::env;
519
520 fn temp_dir() -> PathBuf {
521 use std::sync::atomic::{AtomicU64, Ordering};
522 static COUNTER: AtomicU64 = AtomicU64::new(0);
523 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
524 env::temp_dir().join(format!(
525 "diskann_storage_test_{}_{}_{}",
526 std::process::id(),
527 chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0),
528 id
529 ))
530 }
531
532 #[test]
533 fn test_disk_storage_vector_write_read() -> Result<()> {
534 let dir = temp_dir();
535 let mut storage = DiskStorage::new(&dir, 3)?;
536
537 let vector = vec![1.0, 2.0, 3.0];
538 storage.write_vector(&"vec1".to_string(), &vector)?;
539
540 let read_vector = storage.read_vector(&"vec1".to_string())?;
541 assert_eq!(read_vector, vector);
542
543 storage.clear()?;
544 Ok(())
545 }
546
547 #[test]
548 fn test_disk_storage_dimension_mismatch() -> Result<()> {
549 let dir = temp_dir();
550 let mut storage = DiskStorage::new(&dir, 3)?;
551
552 let vector = vec![1.0, 2.0]; let result = storage.write_vector(&"vec1".to_string(), &vector);
554
555 assert!(result.is_err());
556 storage.clear()?;
557 Ok(())
558 }
559
560 #[test]
561 fn test_disk_storage_graph() -> Result<()> {
562 let dir = temp_dir();
563 std::fs::remove_dir_all(&dir).ok(); let mut storage = DiskStorage::new(&dir, 3)?;
565
566 let mut graph = VamanaGraph::new(3, PruningStrategy::Alpha, 1.2);
567 graph.add_node("v1".to_string())?;
568 graph.add_node("v2".to_string())?;
569
570 storage.write_graph(&graph)?;
571 let read_graph = storage.read_graph()?;
572
573 assert_eq!(read_graph.num_nodes(), 2);
574 storage.clear()?;
575 std::fs::remove_dir_all(&dir).ok();
576 Ok(())
577 }
578
579 #[test]
580 fn test_disk_storage_metadata() -> Result<()> {
581 let dir = temp_dir();
582 std::fs::remove_dir_all(&dir).ok(); let mut storage = DiskStorage::new(&dir, 128)?;
584
585 let config = DiskAnnConfig::default_config(128);
586 let metadata = StorageMetadata::new(config);
587
588 storage.write_metadata(&metadata)?;
589 let read_metadata = storage.read_metadata()?;
590
591 assert_eq!(read_metadata.config.dimension, 128);
592 storage.clear()?;
593 std::fs::remove_dir_all(&dir).ok();
594 Ok(())
595 }
596
597 #[test]
598 fn test_disk_storage_size() -> Result<()> {
599 let dir = temp_dir();
600 let mut storage = DiskStorage::new(&dir, 3)?;
601
602 let initial_size = storage.size()?;
603 assert_eq!(initial_size, 0);
604
605 let vector = vec![1.0, 2.0, 3.0];
606 storage.write_vector(&"vec1".to_string(), &vector)?;
607
608 let after_write = storage.size()?;
609 assert!(after_write > initial_size);
610
611 storage.clear()?;
612 Ok(())
613 }
614
615 #[test]
616 fn test_disk_storage_cache() -> Result<()> {
617 let dir = temp_dir();
618 std::fs::remove_dir_all(&dir).ok(); let mut storage = DiskStorage::new(&dir, 3)?.with_cache_limit(2);
620
621 storage.write_vector(&"v1".to_string(), &[1.0, 2.0, 3.0])?;
622 storage.write_vector(&"v2".to_string(), &[4.0, 5.0, 6.0])?;
623 storage.write_vector(&"v3".to_string(), &[7.0, 8.0, 9.0])?;
624
625 assert!(storage.vector_cache.len() <= 2);
627
628 storage.clear()?;
629 std::fs::remove_dir_all(&dir).ok();
630 Ok(())
631 }
632
633 #[test]
634 fn test_vector_not_found() -> Result<()> {
635 let dir = temp_dir();
636 let storage = DiskStorage::new(&dir, 3)?;
637
638 let result = storage.read_vector(&"nonexistent".to_string());
639 assert!(result.is_err());
640 Ok(())
641 }
642
643 #[test]
644 fn test_storage_clear() -> Result<()> {
645 let dir = temp_dir();
646 std::fs::remove_dir_all(&dir).ok(); let mut storage = DiskStorage::new(&dir, 3)?;
648
649 storage.write_vector(&"v1".to_string(), &[1.0, 2.0, 3.0])?;
650
651 let vector_file = storage
653 .vector_file
654 .as_ref()
655 .ok_or("vector_file is None")?
656 .clone();
657 assert!(
658 vector_file.exists(),
659 "Vector file should exist after write: {:?}",
660 vector_file
661 );
662
663 storage.clear()?;
664 assert!(
665 !vector_file.exists(),
666 "Vector file should not exist after clear: {:?}",
667 vector_file
668 );
669
670 std::fs::remove_dir_all(&dir).ok();
672 Ok(())
673 }
674}