1#![warn(missing_docs)]
6
7use std::ops::Deref;
9
10use diskann::{ANNError, ANNResult};
11use diskann_quantization::alloc::{AlignedAllocator, Poly};
12
13use crate::{
14 data_model::GraphHeader,
15 search::provider::aligned_file_reader::{traits::AlignedFileReader, AlignedRead, Alignment},
16};
17
18const DEFAULT_DISK_SECTOR_LEN: usize = 4096;
19
20pub struct DiskSectorGraph<AlignedReaderType: AlignedFileReader> {
22 sector_reader: AlignedReaderType,
25 sectors_data: Poly<[u8], AlignedAllocator>,
38 cur_sector_idx: u64,
40
41 num_nodes_per_sector: u64,
43
44 node_len: u64,
45
46 max_n_batch_sector_read: usize,
47
48 num_sectors_per_node: usize,
49
50 block_size: usize,
51}
52
53impl<AlignedReaderType: AlignedFileReader> DiskSectorGraph<AlignedReaderType> {
54 pub fn new(
56 sector_reader: AlignedReaderType,
57 header: &GraphHeader,
58 max_n_batch_sector_read: usize,
59 ) -> ANNResult<Self> {
60 let mut block_size = header.block_size() as usize;
61 let version = header.layout_version();
62 if (version.major_version() == 0 && version.minor_version() == 0) || block_size == 0 {
63 block_size = DEFAULT_DISK_SECTOR_LEN;
64 }
65
66 let num_nodes_per_sector = header.metadata().num_nodes_per_block;
67 let node_len = header.metadata().node_len;
68 let num_sectors_per_node = if num_nodes_per_sector > 0 {
69 1
70 } else {
71 (node_len as usize).div_ceil(block_size)
72 };
73
74 Ok(Self {
75 sector_reader,
76 sectors_data: Poly::broadcast(
77 0u8,
78 max_n_batch_sector_read * num_sectors_per_node * block_size,
79 AlignedAllocator::new(AlignedReaderType::Alignment::VALUE),
80 )
81 .map_err(ANNError::log_index_error)?,
82 cur_sector_idx: 0,
83 num_nodes_per_sector,
84 node_len,
85 max_n_batch_sector_read,
86 num_sectors_per_node,
87 block_size,
88 })
89 }
90
91 pub fn reconfigure(&mut self, max_n_batch_sector_read: usize) -> ANNResult<()> {
93 if max_n_batch_sector_read > self.max_n_batch_sector_read {
94 self.max_n_batch_sector_read = max_n_batch_sector_read;
95 self.sectors_data = Poly::broadcast(
96 0u8,
97 max_n_batch_sector_read * self.num_sectors_per_node * self.block_size,
98 AlignedAllocator::new(AlignedReaderType::Alignment::VALUE),
99 )
100 .map_err(ANNError::log_index_error)?;
101 }
102 Ok(())
103 }
104
105 pub fn reset(&mut self) {
107 self.cur_sector_idx = 0;
108 }
109
110 pub fn read_graph(&mut self, sectors_to_fetch: &[u64]) -> ANNResult<()> {
113 let cur_sector_idx_usize: usize = self.cur_sector_idx.try_into()?;
114 if sectors_to_fetch.len() > self.max_n_batch_sector_read - cur_sector_idx_usize {
115 return Err(ANNError::log_index_error(format_args!(
116 "Trying to read too many sectors. number of sectors to read: {}, max number of sectors can read: {}",
117 sectors_to_fetch.len(),
118 self.max_n_batch_sector_read - cur_sector_idx_usize,
119 )));
120 }
121
122 let len_per_node = self.num_sectors_per_node * self.block_size;
123 if len_per_node == 0 {
124 return Err(ANNError::log_index_error(format_args!(
125 "len_per_node is 0 (num_sectors_per_node={}, block_size={})",
126 self.num_sectors_per_node, self.block_size,
127 )));
128 }
129 let range = cur_sector_idx_usize * len_per_node
130 ..(cur_sector_idx_usize + sectors_to_fetch.len()) * len_per_node;
131 debug_assert!(
132 range.len() % len_per_node == 0,
133 "range length {} is not divisible by {}",
134 range.len(),
135 len_per_node
136 );
137 let mut sector_slices: Vec<&mut [u8]> =
138 self.sectors_data[range].chunks_mut(len_per_node).collect();
139 let mut read_requests: Vec<AlignedRead<'_, u8, AlignedReaderType::Alignment>> =
140 Vec::with_capacity(sector_slices.len());
141 for (local_sector_idx, slice) in sector_slices.iter_mut().enumerate() {
142 let sector_id = sectors_to_fetch[local_sector_idx];
143 read_requests.push(AlignedRead::new(sector_id * self.block_size as u64, slice)?);
144 }
145
146 self.sector_reader.read(&mut read_requests)?;
147 self.cur_sector_idx += sectors_to_fetch.len() as u64;
148
149 Ok(())
150 }
151
152 #[inline]
153 pub fn node_disk_buf(&self, node_index_local: usize, vertex_id: u32) -> &[u8] {
155 let sector_buf = self.get_sector_buf(node_index_local);
157 let node_offset = self.get_node_offset(vertex_id);
158 §or_buf[node_offset..node_offset + self.node_len as usize]
159 }
160
161 #[inline]
163 fn get_sector_buf(&self, local_sector_idx: usize) -> &[u8] {
164 let len_per_node = self.num_sectors_per_node * self.block_size;
165 &self.sectors_data[local_sector_idx * len_per_node..(local_sector_idx + 1) * len_per_node]
166 }
167
168 #[inline]
170 fn get_node_offset(&self, vertex_id: u32) -> usize {
171 if self.num_nodes_per_sector == 0 {
172 0
174 } else {
175 (vertex_id as u64 % self.num_nodes_per_sector * self.node_len) as usize
177 }
178 }
179
180 #[inline]
181 pub fn node_sector_index(&self, vertex_id: u32) -> u64 {
183 1 + if self.num_nodes_per_sector > 0 {
184 vertex_id as u64 / self.num_nodes_per_sector
185 } else {
186 vertex_id as u64 * self.num_sectors_per_node as u64
187 }
188 }
189}
190
191impl<AlignedReaderType: AlignedFileReader> Deref for DiskSectorGraph<AlignedReaderType> {
192 type Target = [u8];
193
194 fn deref(&self) -> &Self::Target {
195 &self.sectors_data
196 }
197}
198
199#[cfg(test)]
200mod disk_sector_graph_test {
201 use diskann_utils::test_data_root;
202
203 use super::*;
204 use crate::{
205 data_model::{GraphLayoutVersion, GraphMetadata},
206 search::provider::aligned_file_reader::{
207 traits::AlignedReaderFactory, AlignedFileReaderFactory,
208 },
209 };
210
211 fn test_index_path() -> String {
212 test_data_root()
213 .join("disk_index_misc/disk_index_siftsmall_learn_256pts_R4_L50_A1.2_aligned_reader_test.index")
214 .to_string_lossy()
215 .to_string()
216 }
217
218 fn test_initialize_disk_sector_graph(
219 num_nodes_per_sector: u64,
220 num_sectors_per_node: usize,
221 sector_reader: <AlignedFileReaderFactory as AlignedReaderFactory>::AlignedReaderType,
222 ) -> DiskSectorGraph<<AlignedFileReaderFactory as AlignedReaderFactory>::AlignedReaderType>
223 {
224 DiskSectorGraph {
225 sectors_data: Poly::broadcast(0u8, 512, AlignedAllocator::A512).unwrap(),
226 sector_reader,
227 cur_sector_idx: 0,
228 num_nodes_per_sector,
229 node_len: 32,
230 max_n_batch_sector_read: 4,
231 num_sectors_per_node,
232 block_size: 64,
233 }
234 }
235
236 #[test]
237 fn test_new_disk_sector_graph_multi_node_per_sector() {
238 let metadata = GraphMetadata::new(1000, 32, 500, 32, 2, 20, 50, 1024, 256);
239 let header = GraphHeader::new(metadata, 64, GraphLayoutVersion::new(1, 0));
240 let reader = AlignedFileReaderFactory::new(test_index_path())
241 .build()
242 .unwrap();
243 let graph = DiskSectorGraph::new(reader, &header, 2).unwrap();
244 assert_eq!(graph.sectors_data.len(), 128);
245 assert_eq!(graph.num_sectors_per_node, 1);
246 assert_eq!(graph.num_nodes_per_sector, 2);
247 }
248
249 #[test]
250 fn test_new_disk_sector_graph_multi_sector_per_node() {
251 let metadata = GraphMetadata::new(1000, 32, 500, 128, 0, 20, 50, 1024, 256);
252 let header = GraphHeader::new(metadata, 64, GraphLayoutVersion::new(1, 0));
253 let reader = AlignedFileReaderFactory::new(test_index_path())
254 .build()
255 .unwrap();
256 let graph = DiskSectorGraph::new(reader, &header, 2).unwrap();
257 assert_eq!(graph.sectors_data.len(), 256);
258 assert_eq!(graph.num_sectors_per_node, 2);
259 assert_eq!(graph.num_nodes_per_sector, 0);
260 }
261
262 #[test]
263 fn test_new_disk_sector_graph_old_version_data() {
264 let metadata = GraphMetadata::new(1000, 32, 500, 128, 0, 20, 50, 1024, 256);
265 let header = GraphHeader::new(metadata, 9999, GraphLayoutVersion::new(0, 0));
266 let reader = AlignedFileReaderFactory::new(test_index_path())
267 .build()
268 .unwrap();
269 let graph = DiskSectorGraph::new(reader, &header, 2).unwrap();
270 assert_eq!(graph.block_size, DEFAULT_DISK_SECTOR_LEN);
271 }
272
273 #[test]
274 fn get_sector_buf_test() {
275 let reader = AlignedFileReaderFactory::new(test_index_path())
276 .build()
277 .unwrap();
278 let graph = test_initialize_disk_sector_graph(2, 1, reader);
279 let sector_buf = graph.get_sector_buf(0);
280 assert_eq!(sector_buf.len(), 64);
281 }
282
283 #[test]
284 fn get_node_offset_test_multi_node_per_sector() {
285 let reader = AlignedFileReaderFactory::new(test_index_path())
286 .build()
287 .unwrap();
288 let graph = test_initialize_disk_sector_graph(4, 1, reader);
289
290 assert_eq!(graph.get_node_offset(0), 0);
291 assert_eq!(graph.get_node_offset(1), 32);
292 assert_eq!(graph.get_node_offset(2), 64);
293 assert_eq!(graph.get_node_offset(3), 96);
294 assert_eq!(graph.get_node_offset(4), 0);
295 assert_eq!(graph.get_node_offset(5), 32);
296 assert_eq!(graph.get_node_offset(6), 64);
297 assert_eq!(graph.get_node_offset(7), 96);
298 }
299
300 #[test]
301 fn get_node_offset_test_multi_sector_per_node() {
302 let reader = AlignedFileReaderFactory::new(test_index_path())
303 .build()
304 .unwrap();
305 let graph = test_initialize_disk_sector_graph(0, 2, reader);
306
307 assert_eq!(graph.get_node_offset(0), 0);
308 assert_eq!(graph.get_node_offset(1), 0);
309 assert_eq!(graph.get_node_offset(2), 0);
310 assert_eq!(graph.get_node_offset(3), 0);
311 assert_eq!(graph.get_node_offset(4), 0);
312 assert_eq!(graph.get_node_offset(5), 0);
313 }
314
315 #[test]
316 fn node_sector_index_test_multi_node_per_sector() {
317 let reader = AlignedFileReaderFactory::new(test_index_path())
318 .build()
319 .unwrap();
320 let graph = test_initialize_disk_sector_graph(4, 1, reader);
321
322 assert_eq!(graph.node_sector_index(0), 1);
323 assert_eq!(graph.node_sector_index(3), 1);
324 assert_eq!(graph.node_sector_index(4), 2);
325 assert_eq!(graph.node_sector_index(5), 2);
326 assert_eq!(graph.node_sector_index(7), 2);
327 assert_eq!(graph.node_sector_index(8), 3);
328 assert_eq!(graph.node_sector_index(1023), 256);
329 assert_eq!(graph.node_sector_index(1024), 257);
330 assert_eq!(graph.node_sector_index(2047), 512);
331 assert_eq!(graph.node_sector_index(2048), 513);
332 }
333
334 #[test]
335 fn node_sector_index_test_multi_sector_per_node() {
336 let reader = AlignedFileReaderFactory::new(test_index_path())
337 .build()
338 .unwrap();
339 let graph = test_initialize_disk_sector_graph(0, 2, reader);
340
341 assert_eq!(graph.node_sector_index(0), 1);
342 assert_eq!(graph.node_sector_index(3), 7);
343 assert_eq!(graph.node_sector_index(4), 9);
344 assert_eq!(graph.node_sector_index(5), 11);
345 assert_eq!(graph.node_sector_index(7), 15);
346 assert_eq!(graph.node_sector_index(8), 17);
347 assert_eq!(graph.node_sector_index(1023), 2047);
348 assert_eq!(graph.node_sector_index(1024), 2049);
349 assert_eq!(graph.node_sector_index(2047), 4095);
350 assert_eq!(graph.node_sector_index(2048), 4097);
351 }
352
353 #[test]
354 fn test_read_graph_max_sectors() {
355 let reader = AlignedFileReaderFactory::new(test_index_path())
356 .build()
357 .unwrap();
358 let mut disk_sector_graph = test_initialize_disk_sector_graph(0, 2, reader);
359
360 let sectors_to_fetch = vec![1, 2, 3, 4, 5, 6];
362 let result = disk_sector_graph.read_graph(§ors_to_fetch);
363
364 assert!(result.is_err());
367 }
368
369 #[test]
370 fn test_disk_sector_graph_deref() {
371 let reader = AlignedFileReaderFactory::new(test_index_path())
372 .build()
373 .unwrap();
374 let graph = test_initialize_disk_sector_graph(1, 1, reader);
375 let data = &graph;
376 assert_eq!(data.len(), 512);
377 }
378
379 #[test]
380 fn test_reconfigure_grows_buffer() {
381 let reader = AlignedFileReaderFactory::new(test_index_path())
382 .build()
383 .unwrap();
384 let mut graph = test_initialize_disk_sector_graph(2, 1, reader);
385 assert_eq!(graph.max_n_batch_sector_read, 4);
386
387 graph.reconfigure(16).unwrap();
389 assert_eq!(graph.max_n_batch_sector_read, 16);
390 assert_eq!(graph.sectors_data.len(), 16 * 64);
391 }
392
393 #[test]
394 fn test_reconfigure_noop_for_smaller_size() {
395 let reader = AlignedFileReaderFactory::new(test_index_path())
396 .build()
397 .unwrap();
398 let mut graph = test_initialize_disk_sector_graph(2, 1, reader);
399 let original_len = graph.sectors_data.len();
400
401 graph.reconfigure(4).unwrap();
403 assert_eq!(graph.max_n_batch_sector_read, 4);
404 assert_eq!(graph.sectors_data.len(), original_len);
405
406 graph.reconfigure(2).unwrap();
407 assert_eq!(graph.max_n_batch_sector_read, 4);
408 assert_eq!(graph.sectors_data.len(), original_len);
409 }
410
411 #[test]
412 fn test_new_disk_sector_graph_zero_block_size_defaults() {
413 let metadata = GraphMetadata::new(1000, 32, 500, 32, 2, 20, 50, 1024, 256);
414 let header = GraphHeader::new(metadata, 0, GraphLayoutVersion::new(1, 0));
416 let reader = AlignedFileReaderFactory::new(test_index_path())
417 .build()
418 .unwrap();
419 let graph = DiskSectorGraph::new(reader, &header, 2).unwrap();
420 assert_eq!(graph.block_size, DEFAULT_DISK_SECTOR_LEN);
421 }
422}