vecdb_core/storage/
mmap.rs1use std::fs::{File, OpenOptions};
2use std::path::{Path, PathBuf};
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use memmap2::MmapMut;
6
7use crate::errors::{Result, VecDbError};
8use crate::types::Vector;
9
10const MAGIC: u32 = 0x5645_4344;
11const VERSION: u32 = 1;
12const HEADER_SIZE: usize = 64;
13
14fn now_unix() -> u64 {
15 SystemTime::now()
16 .duration_since(UNIX_EPOCH)
17 .unwrap_or_default()
18 .as_secs()
19}
20
21pub struct MmapVectorStore {
22 pub path: PathBuf,
23 pub dimension: usize,
24 pub count: usize,
25 pub capacity: usize,
26 mmap: MmapMut,
27 file: File,
28}
29
30impl MmapVectorStore {
31 pub fn create(path: &Path, dimension: usize, initial_capacity: usize) -> Result<Self> {
32 let file = OpenOptions::new()
33 .read(true)
34 .write(true)
35 .create_new(true)
36 .open(path)
37 .map_err(|e| VecDbError::StorageError(format!("create failed: {e}")))?;
38
39 let total_size = (HEADER_SIZE + initial_capacity * dimension * 4) as u64;
40 file.set_len(total_size)
41 .map_err(|e| VecDbError::StorageError(format!("set_len failed: {e}")))?;
42
43 let mut mmap = unsafe {
44 MmapMut::map_mut(&file)
45 .map_err(|e| VecDbError::StorageError(format!("mmap failed: {e}")))?
46 };
47
48 let now = now_unix();
49 mmap[0..4].copy_from_slice(&MAGIC.to_le_bytes());
50 mmap[4..8].copy_from_slice(&VERSION.to_le_bytes());
51 mmap[8..16].copy_from_slice(&(dimension as u64).to_le_bytes());
52 mmap[16..24].copy_from_slice(&0u64.to_le_bytes());
53 mmap[24..32].copy_from_slice(&(initial_capacity as u64).to_le_bytes());
54 mmap[32..40].copy_from_slice(&now.to_le_bytes());
55 mmap[40..48].copy_from_slice(&now.to_le_bytes());
56 mmap.flush()
59 .map_err(|e| VecDbError::StorageError(format!("flush failed: {e}")))?;
60
61 Ok(Self {
62 path: path.to_path_buf(),
63 dimension,
64 count: 0,
65 capacity: initial_capacity,
66 mmap,
67 file,
68 })
69 }
70
71 pub fn open(path: &Path) -> Result<Self> {
72 let file = OpenOptions::new()
73 .read(true)
74 .write(true)
75 .open(path)
76 .map_err(|e| VecDbError::StorageError(format!("open failed: {e}")))?;
77
78 let mmap = unsafe {
79 MmapMut::map_mut(&file)
80 .map_err(|e| VecDbError::StorageError(format!("mmap failed: {e}")))?
81 };
82
83 #[cfg(target_os = "linux")]
88 {
89 let _ = mmap.advise(memmap2::Advice::WillNeed);
90 }
91
92 let magic = u32::from_le_bytes(mmap[0..4].try_into().unwrap());
93 if magic != MAGIC {
94 return Err(VecDbError::StorageError(format!(
95 "invalid magic: {magic:#x}"
96 )));
97 }
98 let version = u32::from_le_bytes(mmap[4..8].try_into().unwrap());
99 if version != VERSION {
100 return Err(VecDbError::StorageError(format!(
101 "unsupported version: {version}"
102 )));
103 }
104
105 let dimension = u64::from_le_bytes(mmap[8..16].try_into().unwrap()) as usize;
106 let count = u64::from_le_bytes(mmap[16..24].try_into().unwrap()) as usize;
107 let capacity = u64::from_le_bytes(mmap[24..32].try_into().unwrap()) as usize;
108
109 Ok(Self {
110 path: path.to_path_buf(),
111 dimension,
112 count,
113 capacity,
114 mmap,
115 file,
116 })
117 }
118
119 pub fn append(&mut self, vector: &Vector) -> Result<usize> {
120 if vector.len() != self.dimension {
121 return Err(VecDbError::DimensionMismatch {
122 expected: self.dimension,
123 got: vector.len(),
124 });
125 }
126 if self.count >= self.capacity {
127 self.grow()?;
128 }
129
130 let offset = HEADER_SIZE + self.count * self.dimension * 4;
131 for (i, &val) in vector.iter().enumerate() {
132 self.mmap[offset + i * 4..offset + i * 4 + 4].copy_from_slice(&val.to_le_bytes());
133 }
134
135 let index = self.count;
136 self.count += 1;
137 self.mmap[16..24].copy_from_slice(&(self.count as u64).to_le_bytes());
138 self.mmap[40..48].copy_from_slice(&now_unix().to_le_bytes());
139 self.mmap
140 .flush()
141 .map_err(|e| VecDbError::StorageError(format!("flush failed: {e}")))?;
142
143 Ok(index)
144 }
145
146 pub fn overwrite(&mut self, index: usize, vector: &Vector) -> Result<()> {
147 if vector.len() != self.dimension {
148 return Err(VecDbError::DimensionMismatch {
149 expected: self.dimension,
150 got: vector.len(),
151 });
152 }
153 if index >= self.count {
154 return Err(VecDbError::StorageError("index out of bounds".into()));
155 }
156
157 let offset = HEADER_SIZE + index * self.dimension * 4;
158 for (i, &val) in vector.iter().enumerate() {
159 self.mmap[offset + i * 4..offset + i * 4 + 4].copy_from_slice(&val.to_le_bytes());
160 }
161 self.mmap[40..48].copy_from_slice(&now_unix().to_le_bytes());
162 self.mmap
163 .flush()
164 .map_err(|e| VecDbError::StorageError(format!("flush failed: {e}")))?;
165 Ok(())
166 }
167
168 pub fn get(&self, index: usize) -> Result<Vector> {
169 if index >= self.count {
170 return Err(VecDbError::StorageError("index out of bounds".into()));
171 }
172 let offset = HEADER_SIZE + index * self.dimension * 4;
173 let mut vector = Vec::with_capacity(self.dimension);
174 for i in 0..self.dimension {
175 let bytes: [u8; 4] = self.mmap[offset + i * 4..offset + i * 4 + 4]
176 .try_into()
177 .unwrap();
178 vector.push(f32::from_le_bytes(bytes));
179 }
180 Ok(vector)
181 }
182
183 pub fn get_all(&self) -> Result<Vec<(usize, Vector)>> {
184 #[cfg(target_os = "linux")]
188 {
189 let _ = self.mmap.advise(memmap2::Advice::Sequential);
190 }
191 let mut out = Vec::with_capacity(self.count);
192 for i in 0..self.count {
193 out.push((i, self.get(i)?));
194 }
195 Ok(out)
196 }
197
198 fn grow(&mut self) -> Result<()> {
199 let new_capacity = self.capacity * 2;
200 let new_size = (HEADER_SIZE + new_capacity * self.dimension * 4) as u64;
201
202 let placeholder = MmapMut::map_anon(1)
204 .map_err(|e| VecDbError::StorageError(format!("anon mmap failed: {e}")))?;
205 drop(std::mem::replace(&mut self.mmap, placeholder));
206
207 self.file
208 .set_len(new_size)
209 .map_err(|e| VecDbError::StorageError(format!("set_len failed: {e}")))?;
210
211 self.mmap = unsafe {
212 MmapMut::map_mut(&self.file)
213 .map_err(|e| VecDbError::StorageError(format!("remap failed: {e}")))?
214 };
215
216 self.capacity = new_capacity;
217 self.mmap[24..32].copy_from_slice(&(new_capacity as u64).to_le_bytes());
218
219 tracing::info!("MmapVectorStore grew to capacity {}", new_capacity);
220 Ok(())
221 }
222
223 pub fn len(&self) -> usize {
224 self.count
225 }
226
227 pub fn is_empty(&self) -> bool {
228 self.count == 0
229 }
230
231 pub fn dimension(&self) -> usize {
232 self.dimension
233 }
234
235 pub fn flush(&self) -> Result<()> {
236 self.mmap
237 .flush()
238 .map_err(|e| VecDbError::StorageError(format!("flush failed: {e}")))
239 }
240}