Skip to content

Commit 619d904

Browse files
committed
Add in-memory checkpoint header
1 parent 40a9202 commit 619d904

1 file changed

Lines changed: 119 additions & 7 deletions

File tree

‎crates/storage/src/in_memory.rs‎

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ use bincode::{deserialize_from, serialize_into};
55
use defs::{DenseVector, Payload, Point, PointId};
66
use std::collections::BTreeMap;
77
use std::fs::File;
8+
use std::io::{BufReader, BufWriter, Read, Write};
89
use std::ops::Bound::{Excluded, Unbounded};
910
use std::path::Path;
1011
use std::sync::RwLock;
1112

1213
pub const INMEMORY_CHECKPOINT_FILENAME_MARKER: &str = "inmemory";
1314
const INMEMORY_CHECKPOINT_EXTENSION: &str = "bin";
15+
const INMEMORY_CHECKPOINT_MAGIC: &[u8; 8] = b"VDBIMCP\0";
16+
const INMEMORY_CHECKPOINT_VERSION: u16 = 1;
1417

1518
pub struct MemoryStorage {
1619
points: RwLock<BTreeMap<PointId, Point>>,
@@ -123,14 +126,33 @@ impl StorageEngine for MemoryStorage {
123126
source,
124127
}
125128
})?;
129+
let mut writer = BufWriter::new(file);
126130
let points = self
127131
.points
128132
.read()
129133
.map_err(|_| StorageError::InMemoryLock {})?;
130-
serialize_into(file, &*points).map_err(|source| StorageError::Serialization {
131-
id: PointId::nil(),
132-
source,
133-
})?;
134+
writer
135+
.write_all(INMEMORY_CHECKPOINT_MAGIC)
136+
.and_then(|_| writer.write_all(&INMEMORY_CHECKPOINT_VERSION.to_le_bytes()))
137+
.and_then(|_| writer.write_all(&(points.len() as u64).to_le_bytes()))
138+
.map_err(|source| StorageError::InMemoryCheckpointIo {
139+
msg: "Couldn't write in-memory checkpoint header".to_string(),
140+
source,
141+
})?;
142+
143+
for point in points.values() {
144+
serialize_into(&mut writer, point).map_err(|source| StorageError::Serialization {
145+
id: point.id,
146+
source,
147+
})?;
148+
}
149+
150+
writer
151+
.flush()
152+
.map_err(|source| StorageError::InMemoryCheckpointIo {
153+
msg: "Couldn't flush in-memory checkpoint".to_string(),
154+
source,
155+
})?;
134156

135157
Ok(StorageCheckpoint {
136158
path: checkpoint_path,
@@ -169,11 +191,52 @@ impl StorageEngine for MemoryStorage {
169191
msg: "Couldn't open in-memory checkpoint".to_string(),
170192
source,
171193
})?;
172-
let restored_points: BTreeMap<PointId, Point> =
173-
deserialize_from(file).map_err(|source| StorageError::Deserialization {
174-
id: PointId::nil(),
194+
let mut reader = BufReader::new(file);
195+
let mut magic = [0u8; INMEMORY_CHECKPOINT_MAGIC.len()];
196+
reader
197+
.read_exact(&mut magic)
198+
.map_err(|source| StorageError::InMemoryCheckpointIo {
199+
msg: "Couldn't read in-memory checkpoint magic".to_string(),
175200
source,
176201
})?;
202+
if &magic != INMEMORY_CHECKPOINT_MAGIC {
203+
return Err(StorageError::InMemoryCheckpoint {
204+
msg: "Invalid checkpoint magic".to_string(),
205+
});
206+
}
207+
208+
let mut version_bytes = [0u8; size_of::<u16>()];
209+
reader.read_exact(&mut version_bytes).map_err(|source| {
210+
StorageError::InMemoryCheckpointIo {
211+
msg: "Couldn't read in-memory checkpoint version".to_string(),
212+
source,
213+
}
214+
})?;
215+
let version = u16::from_le_bytes(version_bytes);
216+
if version != INMEMORY_CHECKPOINT_VERSION {
217+
return Err(StorageError::InMemoryCheckpoint {
218+
msg: format!("Unsupported checkpoint version: {version}"),
219+
});
220+
}
221+
222+
let mut count_bytes = [0u8; size_of::<u64>()];
223+
reader.read_exact(&mut count_bytes).map_err(|source| {
224+
StorageError::InMemoryCheckpointIo {
225+
msg: "Couldn't read in-memory checkpoint point count".to_string(),
226+
source,
227+
}
228+
})?;
229+
let point_count = u64::from_le_bytes(count_bytes);
230+
let mut restored_points = BTreeMap::new();
231+
for _ in 0..point_count {
232+
let point: Point =
233+
deserialize_from(&mut reader).map_err(|source| StorageError::Deserialization {
234+
id: PointId::nil(),
235+
source,
236+
})?;
237+
restored_points.insert(point.id, point);
238+
}
239+
177240
let mut points = self
178241
.points
179242
.write()
@@ -187,6 +250,7 @@ impl StorageEngine for MemoryStorage {
187250
mod tests {
188251
use super::*;
189252
use defs::ContentType;
253+
use std::io::{Read, Write};
190254
use tempfile::{TempDir, tempdir};
191255
use uuid::Uuid;
192256

@@ -317,4 +381,52 @@ mod tests {
317381
Some(test_payload("before"))
318382
);
319383
}
384+
385+
#[test]
386+
fn test_checkpoint_writes_header() {
387+
let storage = create_test_storage();
388+
let temp_dir = tempdir().unwrap();
389+
let id = Uuid::new_v4();
390+
391+
storage
392+
.insert_point(id, Some(vec![0.1, 0.2, 0.3]), Some(test_payload("point")))
393+
.unwrap();
394+
let checkpoint = storage.checkpoint_at(temp_dir.path()).unwrap();
395+
396+
let mut file = File::open(checkpoint.path).unwrap();
397+
let mut magic = [0u8; INMEMORY_CHECKPOINT_MAGIC.len()];
398+
file.read_exact(&mut magic).unwrap();
399+
assert_eq!(&magic, INMEMORY_CHECKPOINT_MAGIC);
400+
401+
let mut version_bytes = [0u8; size_of::<u16>()];
402+
file.read_exact(&mut version_bytes).unwrap();
403+
assert_eq!(
404+
u16::from_le_bytes(version_bytes),
405+
INMEMORY_CHECKPOINT_VERSION
406+
);
407+
408+
let mut count_bytes = [0u8; size_of::<u64>()];
409+
file.read_exact(&mut count_bytes).unwrap();
410+
assert_eq!(u64::from_le_bytes(count_bytes), 1);
411+
}
412+
413+
#[test]
414+
fn test_restore_rejects_invalid_checkpoint_magic() {
415+
let mut storage = create_test_storage();
416+
let temp_dir = tempdir().unwrap();
417+
let checkpoint_path = temp_dir.path().join("inmemory-invalid.bin");
418+
let mut file = File::create(&checkpoint_path).unwrap();
419+
file.write_all(b"BADMAGIC").unwrap();
420+
file.write_all(&INMEMORY_CHECKPOINT_VERSION.to_le_bytes())
421+
.unwrap();
422+
file.write_all(&0u64.to_le_bytes()).unwrap();
423+
424+
let checkpoint = StorageCheckpoint {
425+
path: checkpoint_path,
426+
storage_type: StorageType::InMemory,
427+
};
428+
429+
let error = storage.restore_checkpoint(&checkpoint).unwrap_err();
430+
assert!(matches!(error, StorageError::InMemoryCheckpoint { .. }));
431+
}
320432
}

0 commit comments

Comments
 (0)