1use std::marker::PhantomData;
9
10use bf_tree::{BfTree, Config};
11use bytemuck::{cast_slice, cast_slice_mut};
12use diskann::{
13 graph::AdjacencyList,
14 provider::{self, HasId},
15 utils::{IntoUsize, VectorId},
16 ANNError, ANNResult,
17};
18
19use super::ConfigError;
20use crate::locks::StripedLocks;
21use crate::{bftree_insert, TestCallCount};
22
23pub struct NeighborProvider<I: VectorId + IntoUsize> {
24 adjacency_list_index: BfTree,
25 dim: usize, #[allow(dead_code)]
27 pub(crate) num_get_calls: TestCallCount,
28 _phantom: PhantomData<I>,
29}
30
31impl<I: VectorId + IntoUsize> HasId for NeighborProvider<I> {
32 type Id = I;
33}
34
35impl<I: VectorId + IntoUsize> NeighborProvider<I> {
36 pub fn new_with_config(max_degree: u32, config: Config) -> ANNResult<Self> {
38 let key_size = std::mem::size_of::<u32>();
39 let value_size = (max_degree as usize + 1) * std::mem::size_of::<u32>();
40 crate::validate_record_size("neighbor_provider", &config, key_size, value_size)?;
41
42 let adj_list_index = BfTree::with_config(config, None).map_err(ConfigError)?;
43
44 Self::new(max_degree, adj_list_index)
45 }
46
47 fn new(max_degree: u32, adjacency_list_index: BfTree) -> ANNResult<Self> {
48 let dim = 1 + max_degree.into_usize();
49
50 Ok(Self {
51 adjacency_list_index,
52 dim,
53 num_get_calls: TestCallCount::default(),
54 _phantom: PhantomData,
55 })
56 }
57
58 pub(crate) fn config(&self) -> &Config {
60 self.adjacency_list_index.config()
61 }
62
63 pub(crate) fn bftree(&self) -> &BfTree {
65 &self.adjacency_list_index
66 }
67
68 pub fn max_degree(&self) -> u32 {
71 (self.dim - 1) as u32
72 }
73
74 pub(crate) fn new_from_bftree(
77 max_degree: u32,
78 adjacency_list_index: BfTree,
79 ) -> ANNResult<Self> {
80 Self::new(max_degree, adjacency_list_index)
81 }
82
83 pub fn get_neighbors(&self, vector_id: I, neighbors: &mut AdjacencyList<I>) -> ANNResult<()> {
87 #[cfg(test)]
88 self.num_get_calls.increment();
89
90 neighbors.clear();
93
94 self.get_neighbors_unlocked(vector_id, neighbors)
95 }
96
97 fn get_neighbors_unlocked(
101 &self,
102 vector_id: I,
103 neighbors: &mut AdjacencyList<I>,
104 ) -> ANNResult<()> {
105 let mut guard = neighbors.resize(self.dim);
107
108 let key = bytemuck::bytes_of(&vector_id);
110
111 let value = cast_slice_mut::<I, u8>(&mut guard);
114 match self.adjacency_list_index.read(key, value) {
115 bf_tree::LeafReadResult::Found(read_size) => {
116 if read_size > 0 {
118 if read_size as usize != self.dim * std::mem::size_of::<I>() {
120 return Err(ANNError::log_index_error(
121 "Retrieved neighbor list is not expected length = max degree + 1",
122 ));
123 }
124
125 let count = cell_to_len(&guard[self.dim - 1]);
130
131 if count > self.max_degree() {
133 return Err(ANNError::log_index_error(
134 "Size of retrieved neighbor list is shorter than the stored neighbor count",
135 ));
136 }
137
138 guard.finish(count as usize);
139 }
140 }
141 bf_tree::LeafReadResult::Deleted => {
142 return Err(ANNError::log_index_error(
143 "The bf-tree entry for the vector is marked as deleted",
144 ));
145 }
146 bf_tree::LeafReadResult::InvalidKey => {
147 return Err(ANNError::log_index_error(
148 "The bf-tree entry for the vector key is marked as invalid",
149 ));
150 }
151 bf_tree::LeafReadResult::NotFound => {
152 return Err(ANNError::log_index_error(
153 "The bf-tree entry for the vector key is marked as not found",
154 ));
155 }
156 };
157
158 Ok(())
159 }
160
161 fn set_neighbors_internal(
179 &self,
180 vector_id: I,
181 neighbors: &[I],
182 buf: &mut [I],
183 ) -> ANNResult<()> {
184 #[cfg(test)]
185 self.num_get_calls.increment();
186
187 if buf.len() < self.dim {
188 return Err(ANNError::log_index_error(
189 "The provided buffer is not long enough",
190 ));
191 }
192
193 buf[..neighbors.len()].copy_from_slice(neighbors);
195 buf[self.dim - 1] = len_to_cell(neighbors.len() as u32);
196
197 let key = bytemuck::bytes_of(&vector_id);
198 let value = cast_slice::<I, u8>(&buf[..self.dim]);
199
200 bftree_insert(&self.adjacency_list_index, key, value)?;
201
202 Ok(())
203 }
204
205 pub fn set_neighbors(&self, vector_id: I, neighbors: &[I], buf: &mut [I]) -> ANNResult<()> {
218 if neighbors.len() > self.dim - 1 {
219 return Err(ANNError::log_index_error(
220 "The provided neighbor list is longer than the max degree",
221 ));
222 };
223
224 self.set_neighbors_internal(vector_id, neighbors, buf)
225 }
226
227 pub fn append_vector(
237 &self,
238 vector_id: I,
239 new_neighbor_ids: &[I],
240 buf: &mut [I],
241 ) -> ANNResult<()> {
242 let mut neighbor_list = AdjacencyList::with_capacity(self.dim);
244 self.get_neighbors_unlocked(vector_id, &mut neighbor_list)?;
245
246 let mut new_neighbor_added = false;
248 for new_neighbor_id in new_neighbor_ids {
249 if neighbor_list.len() == self.dim - 1 {
250 break;
251 }
252 new_neighbor_added |= neighbor_list.push(*new_neighbor_id);
253 }
254
255 if new_neighbor_added {
257 self.set_neighbors_internal(vector_id, &neighbor_list, buf)?;
258 }
259
260 Ok(())
261 }
262
263 pub fn delete_vector(&self, vector_id: I) -> ANNResult<()> {
264 let key = bytemuck::bytes_of(&vector_id);
265 self.adjacency_list_index.delete(key);
266 Ok(())
267 }
268
269 pub(crate) fn scratch<'a>(&'a self, locks: &'a StripedLocks) -> NeighborAccessor<'a, I> {
270 NeighborAccessor {
271 provider: self,
272 locks,
273 buf: vec![I::zeroed(); self.dim],
274 }
275 }
276}
277
278pub struct NeighborAccessor<'a, I>
279where
280 I: VectorId + IntoUsize,
281{
282 provider: &'a NeighborProvider<I>,
283 locks: &'a StripedLocks,
284 buf: Vec<I>,
285}
286
287impl<'a, I> NeighborAccessor<'a, I>
288where
289 I: VectorId + IntoUsize,
290{
291 pub fn write_neighbors(&mut self, id: I, neighbors: &[I]) -> ANNResult<()> {
292 let _guard = self.locks.lock(id.into_usize());
293 self.provider.set_neighbors(id, neighbors, &mut self.buf)
294 }
295 pub fn write_append(&mut self, id: I, neighbors: &[I]) -> ANNResult<()> {
296 let _guard = self.locks.lock(id.into_usize());
297 self.provider.append_vector(id, neighbors, &mut self.buf)
298 }
299}
300
301impl<'a, I> HasId for NeighborAccessor<'a, I>
302where
303 I: VectorId + IntoUsize,
304{
305 type Id = I;
306}
307
308impl<'a, I> provider::NeighborAccessor for NeighborAccessor<'a, I>
309where
310 I: VectorId + IntoUsize,
311{
312 fn get_neighbors(
313 &mut self,
314 id: Self::Id,
315 neighbors: &mut AdjacencyList<Self::Id>,
316 ) -> impl std::future::Future<Output = ANNResult<()>> + Send {
317 std::future::ready(self.provider.get_neighbors(id, neighbors))
318 }
319}
320
321impl<'a, I> provider::NeighborAccessorMut for NeighborAccessor<'a, I>
322where
323 I: VectorId + IntoUsize,
324{
325 fn set_neighbors(
326 &mut self,
327 id: Self::Id,
328 neighbors: &[Self::Id],
329 ) -> impl std::future::Future<Output = ANNResult<()>> + Send {
330 let _guard = self.locks.lock(id.into_usize());
331 std::future::ready(self.provider.set_neighbors(id, neighbors, &mut self.buf))
332 }
333 fn append_vector(
334 &mut self,
335 id: Self::Id,
336 neighbors: &[Self::Id],
337 ) -> impl std::future::Future<Output = ANNResult<()>> + Send {
338 let _guard = self.locks.lock(id.into_usize());
339 std::future::ready(self.provider.append_vector(id, neighbors, &mut self.buf))
340 }
341}
342
343const FOUR: usize = std::mem::size_of::<u32>();
345
346fn len_to_cell<I: bytemuck::Pod>(len: u32) -> I {
352 const { assert!(std::mem::size_of::<I>() >= FOUR) };
353 let mut cell = I::zeroed();
354 bytemuck::bytes_of_mut(&mut cell)[..FOUR].copy_from_slice(&len.to_le_bytes());
355 cell
356}
357
358fn cell_to_len<I: bytemuck::Pod>(cell: &I) -> u32 {
360 const { assert!(std::mem::size_of::<I>() >= FOUR) };
361
362 let mut low = [0u8; FOUR];
363 low.copy_from_slice(&bytemuck::bytes_of(cell)[..FOUR]);
364
365 u32::from_le_bytes(low)
366}
367
368#[cfg(test)]
374mod tests {
375 use std::sync::Arc;
376
377 use tokio::task::JoinSet;
378
379 use super::*;
380
381 fn stress_thread_count() -> u32 {
385 std::thread::available_parallelism()
386 .map(|n| n.get() as u32)
387 .unwrap_or(8)
388 .max(8)
389 }
390
391 fn new_provider(max_degree: u32) -> NeighborProvider<u32> {
393 NeighborProvider::<u32>::new_with_config(max_degree, Config::default()).unwrap()
394 }
395
396 fn new_shared_provider(max_degree: u32) -> Arc<NeighborProvider<u32>> {
398 Arc::new(new_provider(max_degree))
399 }
400
401 #[test]
403 fn len_cell_round_trip() {
404 for len in [0u32, 1, 42, u32::MAX] {
405 assert_eq!(cell_to_len::<u32>(&len_to_cell::<u32>(len)), len);
406 assert_eq!(cell_to_len::<u64>(&len_to_cell::<u64>(len)), len);
407 }
408 }
409
410 #[tokio::test]
412 async fn test_neighbor_accessors() {
413 let locks = Arc::new(StripedLocks::new());
414 let neighbor_provider = new_provider(6);
415 let mut scratch = neighbor_provider.scratch(&locks);
416
417 let adj_list = vec![1, 2, 3];
419 scratch.write_neighbors(1, &adj_list).unwrap();
420
421 let mut result = AdjacencyList::with_capacity(10);
422 neighbor_provider.get_neighbors(1, &mut result).unwrap();
423 assert_eq!(&*adj_list, &*result);
424
425 let new_neighbors = vec![9, 2, 9];
427 scratch.write_append(1, &new_neighbors).unwrap();
428
429 neighbor_provider.get_neighbors(1, &mut result).unwrap();
430
431 let adj_list_new = vec![1, 2, 3, 9];
432 assert_eq!(&*adj_list_new, &*result);
433
434 let new_neighbors = vec![5, 6, 7];
436 scratch.write_append(1, &new_neighbors).unwrap();
437
438 neighbor_provider.get_neighbors(1, &mut result).unwrap();
439
440 let adj_list_new = vec![1, 2, 3, 9, 5, 6];
441 assert_eq!(&*adj_list_new, &*result);
442
443 scratch.write_neighbors(1, &[]).unwrap();
445 neighbor_provider.get_neighbors(1, &mut result).unwrap();
446 assert!(result.is_empty());
447
448 let new_neighbors = vec![3, 4, 5];
450 scratch.write_append(1, &new_neighbors).unwrap();
451
452 neighbor_provider.get_neighbors(1, &mut result).unwrap();
453 assert_eq!(&*new_neighbors, &*result);
454
455 neighbor_provider.delete_vector(1).unwrap();
456 assert!(neighbor_provider.get_neighbors(1, &mut result).is_err());
457 }
458
459 #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
462 async fn test_parallel_tree_traversal() {
463 let locks = Arc::new(StripedLocks::new());
464 let neighbor_provider = new_shared_provider(120);
465
466 let mut set = JoinSet::new();
467 for i in 0..100 {
468 let neighbor_list = vec![i as u32, (i + 1) as u32, (i + 2) as u32];
469 let neighbor_provider_clone = Arc::clone(&neighbor_provider);
470 let locks = locks.clone();
471 set.spawn(async move {
472 let mut scratch = neighbor_provider_clone.scratch(&locks);
473 scratch.write_neighbors(i as u32, &neighbor_list).unwrap();
474 });
475 }
476
477 while let Some(res) = set.join_next().await {
478 res.unwrap();
479 }
480
481 let mut result = AdjacencyList::with_capacity(121);
482 for i in 0..100 {
483 neighbor_provider
484 .get_neighbors(i as u32, &mut result)
485 .unwrap();
486
487 let neighbor_list = vec![i as u32, (i + 1) as u32, (i + 2) as u32];
488 assert_eq!(&*neighbor_list, &*result);
489 }
490 }
491
492 #[tokio::test]
494 async fn test_snapshot() {
495 let locks = Arc::new(StripedLocks::new());
496 let temp_dir = tempfile::tempdir().unwrap();
498 let snapshot_path = temp_dir.path().join("test_neighbor_snapshot.bftree");
499 let snapshot_output = temp_dir.path().join("test_neighbor_snapshot_out.bftree");
500
501 let mut bf_tree_config = Config::new(&snapshot_path, 16384 * 16);
502 bf_tree_config.storage_backend(bf_tree::StorageBackend::Std);
503 bf_tree_config.use_snapshot(true);
504
505 let neighbor_provider =
506 NeighborProvider::<u32>::new_with_config(6, bf_tree_config).unwrap();
507 let mut scratch = neighbor_provider.scratch(&locks);
508
509 scratch.write_neighbors(1, &[2, 3, 4]).unwrap();
511 scratch.write_neighbors(2, &[1, 3, 5]).unwrap();
512
513 neighbor_provider
515 .adjacency_list_index
516 .cpr_snapshot(&snapshot_output);
517
518 let mut result = AdjacencyList::with_capacity(10);
520 neighbor_provider.get_neighbors(1, &mut result).unwrap();
521 assert_eq!(&[2, 3, 4], &*result);
522
523 neighbor_provider.get_neighbors(2, &mut result).unwrap();
524 assert_eq!(&[1, 3, 5], &*result);
525
526 }
528
529 #[tokio::test]
531 async fn test_max_degree() {
532 let neighbor_provider = new_provider(6);
534 assert_eq!(neighbor_provider.max_degree(), 6);
535
536 let neighbor_provider = new_provider(120);
537 assert_eq!(neighbor_provider.max_degree(), 120);
538
539 let neighbor_provider = new_provider(1);
540 assert_eq!(neighbor_provider.max_degree(), 1);
541 }
542
543 #[tokio::test]
545 async fn test_new_from_bftree() {
546 let locks = Arc::new(StripedLocks::new());
547 let bftree = BfTree::with_config(Config::default(), None).expect("Failed to create BfTree");
548 let neighbor_provider = NeighborProvider::<u32>::new_from_bftree(10, bftree).unwrap();
549
550 assert_eq!(neighbor_provider.max_degree(), 10);
551
552 let mut scratch = neighbor_provider.scratch(&locks);
554 scratch.write_neighbors(1, &[2, 3]).unwrap();
555 let mut result = AdjacencyList::with_capacity(11);
556 neighbor_provider.get_neighbors(1, &mut result).unwrap();
557 assert_eq!(&[2, 3], &*result);
558 }
559
560 #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
562 async fn test_parallel_neighbor_access() {
563 let locks = Arc::new(StripedLocks::new());
564 let neighbor_provider = new_shared_provider(120);
565
566 let mut set = JoinSet::new();
567 for _ in 0..5 {
568 let neighbor_provider_clone = Arc::clone(&neighbor_provider);
569 let locks = locks.clone();
570 set.spawn(async move {
571 let mut scratch = neighbor_provider_clone.scratch(&locks);
572 for i in 0..5 {
573 scratch.write_neighbors(i as u32, &[1, 2, 3, 4, 5]).unwrap();
574 }
575
576 let mut result = AdjacencyList::with_capacity(121);
577 for i in 0..5 {
578 neighbor_provider_clone
579 .get_neighbors(i as u32, &mut result)
580 .unwrap();
581
582 assert_eq!(&[1, 2, 3, 4, 5], &*result);
583 }
584 });
585 }
586
587 while let Some(res) = set.join_next().await {
588 res.unwrap();
589 }
590 }
591
592 #[tokio::test(flavor = "multi_thread")]
605 async fn test_concurrent_append_no_lost_edges() {
606 let num_threads = stress_thread_count();
607 let edges_per_thread = 20u32;
608 let max_degree = num_threads * edges_per_thread;
610
611 let outer_iterations = 100;
614 for _ in 0..outer_iterations {
615 let locks = Arc::new(StripedLocks::new());
616 let provider = new_shared_provider(max_degree);
617
618 let mut scratch = provider.scratch(&locks);
620 scratch.write_neighbors(0, &[]).unwrap();
621
622 let mut set = JoinSet::new();
625 for t in 0..num_threads {
626 let provider_clone = Arc::clone(&provider);
627 let locks = locks.clone();
628 set.spawn(async move {
629 let mut scratch = provider_clone.scratch(&locks);
630 let base = t * edges_per_thread + 1;
631 for offset in 0..edges_per_thread {
632 let neighbor_id = base + offset;
633 scratch.write_append(0, &[neighbor_id]).unwrap();
634 }
635 });
636 }
637
638 while let Some(res) = set.join_next().await {
639 res.unwrap();
640 }
641
642 let mut result = AdjacencyList::with_capacity(max_degree as usize + 1);
644 provider.get_neighbors(0, &mut result).unwrap();
645
646 let expected_count = (num_threads * edges_per_thread) as usize;
647 assert_eq!(
648 result.len(),
649 expected_count,
650 "Expected {} edges but found {} — edges were lost!",
651 expected_count,
652 result.len()
653 );
654
655 for t in 0..num_threads {
657 let base = t * edges_per_thread + 1;
658 for offset in 0..edges_per_thread {
659 let expected_id = base + offset;
660 assert!(
661 result.contains(expected_id),
662 "Missing edge {} from thread {}",
663 expected_id,
664 t
665 );
666 }
667 }
668 }
669 }
670
671 #[tokio::test(flavor = "multi_thread")]
678 async fn test_concurrent_append_independent_vertices() {
679 let locks = Arc::new(StripedLocks::new());
680 let max_degree = 64u32;
681 let num_threads = stress_thread_count();
682 let vertices_per_thread = 50u32;
683 let num_vertices = num_threads * vertices_per_thread; let provider = new_shared_provider(max_degree);
685
686 {
688 let mut scratch = provider.scratch(&locks);
689 for v in 0..num_vertices {
690 scratch.write_neighbors(v, &[]).unwrap();
691 }
692 }
693
694 let mut set = JoinSet::new();
695
696 for t in 0..num_threads {
697 let provider_clone = Arc::clone(&provider);
698 let locks = locks.clone();
699 set.spawn(async move {
700 let mut scratch = provider_clone.scratch(&locks);
701 let base_vertex = t * vertices_per_thread;
703 for i in 0..vertices_per_thread {
704 let vertex = base_vertex + i;
705 let neighbor_id = t * 1000 + i;
706 scratch.write_append(vertex, &[neighbor_id]).unwrap();
707 }
708 });
709 }
710
711 while let Some(res) = set.join_next().await {
712 res.unwrap();
713 }
714
715 let mut total_edges = 0usize;
717 let mut result = AdjacencyList::with_capacity(max_degree as usize + 1);
718 for v in 0..num_vertices {
719 provider.get_neighbors(v, &mut result).unwrap();
720 total_edges += result.len();
721 }
722
723 let expected_total = (num_threads * vertices_per_thread) as usize;
724 assert_eq!(
725 total_edges, expected_total,
726 "Expected {} total edges but found {} — edges were lost or duplicated!",
727 expected_total, total_edges
728 );
729 }
730
731 #[tokio::test(flavor = "multi_thread")]
741 async fn test_concurrent_read_write_consistency() {
742 let parallelism = stress_thread_count();
743 let num_writers = parallelism;
744 let num_readers = parallelism;
745 let edges_per_writer = 20u32;
746 let max_degree = num_writers * edges_per_writer;
748 let max_valid_id = num_writers * edges_per_writer;
750
751 let outer_iterations = 100;
752 for _ in 0..outer_iterations {
753 let locks = Arc::new(StripedLocks::new());
754 let provider = new_shared_provider(max_degree);
755
756 let mut scratch = provider.scratch(&locks);
758 scratch.write_neighbors(0, &[]).unwrap();
759
760 let done = Arc::new(std::sync::atomic::AtomicBool::new(false));
761 let writers_remaining = Arc::new(std::sync::atomic::AtomicU32::new(num_writers));
762
763 let mut set = JoinSet::new();
765
766 for t in 0..num_writers {
767 let provider_clone = Arc::clone(&provider);
768 let done_clone = Arc::clone(&done);
769 let remaining_clone = Arc::clone(&writers_remaining);
770 let locks = locks.clone();
771 set.spawn(async move {
772 let mut scratch = provider_clone.scratch(&locks);
773 let base = t * edges_per_writer + 1;
774 for offset in 0..edges_per_writer {
775 scratch.write_append(0, &[base + offset]).unwrap();
776 tokio::task::yield_now().await;
777 }
778 if remaining_clone.fetch_sub(1, std::sync::atomic::Ordering::AcqRel) == 1 {
780 done_clone.store(true, std::sync::atomic::Ordering::Release);
781 }
782 });
783 }
784
785 for _ in 0..num_readers {
787 let provider_clone = Arc::clone(&provider);
788 let done_clone = Arc::clone(&done);
789 set.spawn(async move {
790 let mut result = AdjacencyList::with_capacity(max_degree as usize + 1);
791 let mut iterations = 0u64;
792 loop {
799 provider_clone.get_neighbors(0, &mut result).unwrap();
800 assert!(
802 result.len() <= max_degree as usize,
803 "Neighbor list exceeded max_degree"
804 );
805 for &id in result.iter() {
809 assert!(id > 0, "Found invalid zero ID in neighbor list");
810 assert!(
811 id <= max_valid_id,
812 "Found out-of-bounds ID {id} (max valid is {max_valid_id})"
813 );
814 }
815 iterations += 1;
816 if done_clone.load(std::sync::atomic::Ordering::Acquire) {
817 break;
818 }
819 tokio::task::yield_now().await;
820 }
821 assert!(
822 iterations > 0,
823 "Reader never got a chance to run — test is not validating anything"
824 );
825 });
826 }
827
828 while let Some(res) = set.join_next().await {
829 res.unwrap();
830 }
831
832 let mut result = AdjacencyList::with_capacity(max_degree as usize + 1);
834 provider.get_neighbors(0, &mut result).unwrap();
835 let expected = (num_writers * edges_per_writer) as usize;
836 assert_eq!(
837 result.len(),
838 expected,
839 "Expected {} edges but found {}",
840 expected,
841 result.len()
842 );
843 }
844 }
845}