Repository navigation
Conversation
6d21b35 to
a31079e
Compare
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
5 open findings
This dispatcher now registers bothBlockKind::NoneandBlockKind::Blockedspecializations under… · New This dispatcher now registers bothBlockKind::NoneandBlockKind::Blockedspecializations under… · New The reverse-edge memory estimate is very approximate:sizeof(std::vector<T>)doesn’t include heap… · New Initializingmin_idto0can permanently expand the sampled ID range when the first inserted… · New This earlyreturnleaksstoragewhenstorage_usable(storage)is false. Please freestorage… · New
What changed in this PR
Adds C API support for SVS_SYNC_KIND_FINE_GRAIN by wiring in the concurrent dynamic Vamana implementation and updating storage-layout selection, memory reporting, and docs to reflect the fine-grained concurrency model.
Changes:
- Introduces concurrent dynamic Vamana dispatch/build/load/copy paths and a
ConcurrentIndexVamanawrapper for C API synchronization semantics. - Generalizes dataset/graph storage layout selection via
BlockKind/MaybeBlockedAllocacross all data builders and dispatchers. - Updates C API docs/headers and tests to cover fine-grained sync behavior and memory accounting (including reverse edges).
| File | Description |
|---|---|
| include/svs/orchestrators/concurrent_dynamic_vamana.h | Adds logger plumbing and stream-load graph type tweaks for concurrent dynamic Vamana. |
| bindings/c/src/index_vamana.hpp / .cpp | Adds ConcurrentIndexVamana and atomic ID-range tracking for concurrent updates. |
| bindings/c/src/index_builder.hpp / .cpp | Selects concurrent vs regular dynamic index implementation; adds sync-kind-aware memory estimation. |
| bindings/c/src/dispatcher_concurrent_vamana.hpp / .cpp | New dispatcher for build/load/copy and memory estimate for concurrent dynamic Vamana. |
| bindings/c/src/dispatcher_dynamic_vamana_copy.hpp | New shared dynamic-index conversion layer (flavors) used by regular/concurrent paths. |
| bindings/c/src/dispatcher_dynamic_vamana.cpp | Refactors copy path to use shared copy dispatcher and BlockKind-based specialization enumeration. |
| bindings/c/src/dispatcher_vamana.cpp | Switches static Vamana dispatcher specialization enumeration to BlockKind. |
| bindings/c/src/allocator.hpp | Introduces BlockKind and MaybeBlockedAlloc to choose None/Blocked/Segmented layouts. |
| bindings/c/src/data_builder*.hpp / data_builder.hpp / data_builder.cpp | Updates specialization enumeration to BlockKind and threads concurrent extension headers. |
| bindings/c/src/svs_c.cpp | Updates memory breakdown reporting to include concurrent reverse edges as graph memory. |
| bindings/c/include/svs/c/svs_c.h | Documents fine-grained sync behavior and memory estimation semantics. |
| bindings/c/docs/C_API_Design.md | Updates public design docs for fine-grain sync, conversion, and reverse-edge memory reporting. |
| bindings/c/tests/c_api_dynamic_index_sync.cpp | Adds tests for sync-kind conversion, fine-grain storage compatibility, and memory reporting. |
| bindings/c/CMakeLists.txt | Adds new dispatcher/copy sources to the C API target. |
🧠 Review effort: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
a31079e to
5b1bd90
Compare
razdoburdin
left a comment
There was a problem hiding this comment.
I have few findings. The most critical one is a serialization of operations by NativeThreadPool being used by default.
|
|
||
| void compact(size_t batchsize) override { | ||
| auto lock = read_lock(); | ||
| compact_unlocked(batchsize); |
There was a problem hiding this comment.
There is a deadlock vs filtered_topk_search if NativeThreadPool is used.
The compact() takes compact_mutex then threadpool takes it's use_mutex_.
The filtered_topk_searchtakes use_mutex_ than compact_mutex (iterator.next() calls lock_for_search())
There was a problem hiding this comment.
Can you please provide a code suggestion to prevent internal deadlocks?
Thank you.
There was a problem hiding this comment.
I think we should first decide if we still use NativeThreadPool. If a threadpool doesn't take an internal mutex, this deadlock will be fixed automatically.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
9 open findings
This dispatcher now registers bothBlockKind::NoneandBlockKind::Blockedspecializations under… This dispatcher now registers bothBlockKind::NoneandBlockKind::Blockedspecializations under… Initializingmin_id/max_idto extreme values is unsafe if the index is ever empty or… · New Initializingmin_idto0can permanently expand the sampled ID range when the first inserted… The reverse-edge memory estimate is very approximate:sizeof(std::vector<T>)doesn’t include heap…allocator.hppis already included near the top of this header; re-including it in the `#else //… · New This header includesallocator.hppmultiple times (including in the disabled-feature stub… · New This header includesallocator.hppmultiple times (including in the disabled-feature stub… · New This header includesallocator.hppmultiple times (including in the disabled-feature stub… · New
1 resolved since last review
🧠 Review effort: Lite
…amana - Added atomic tracking for minimum and maximum IDs in DynamicIndexVamana to support concurrent operations. - Introduced ConcurrentIndexVamana to manage concurrent access and operations on the dynamic index. - Updated memory estimation functions to account for new synchronization parameters. - Implemented comprehensive tests for synchronization kind conversion and memory usage. - Created dispatcher for concurrent Vamana index build and load operations. - Added support for copying between concurrent and regular dynamic indices.
a39da58 to
9bbee5f
Compare
| } | ||
|
|
||
| void compact(size_t batchsize) override { | ||
| auto lock = read_lock(); |
There was a problem hiding this comment.
| auto lock = read_lock(); | |
| // exclusive lock prevents potential deadlock in case of custom threadpool takes internal mutex in parallel_for method | |
| auto lock = write_lock(); |
| /// With SVS_SYNC_KIND_GLOBAL, mutating operations (add_points, delete_points, | ||
| /// consolidate, compact, save, set_num_threads) take an exclusive lock. | ||
| /// | ||
| /// With SVS_SYNC_KIND_FINE_GRAIN, the handle is backed by a concurrent index that |
There was a problem hiding this comment.
Concurrent execution of add_points(), delete_entries() and search() are properly teste for a single query operations only. These methods are called by svs_index_dynamic_add_points, svs_index_dynamic_delete_points, svs_index_search_topk.
| SVS_SYNC_KIND_GLOBAL = 1, ///< Index-wide reader/writer lock | ||
| SVS_SYNC_KIND_FINE_GRAIN = 2 ///< Currently the same as GLOBAL; reserved for | ||
| ///< finer-grained locking | ||
| SVS_SYNC_KIND_FINE_GRAIN = 2 ///< Concurrent index; updates run in parallel |
There was a problem hiding this comment.
An index built with FINE_GRAIN and loaded without sync_kind silently becomes a normal index with no locking. Could we save the sync kind and warn when the load uses a different one? A test for this case would help too.
| ); | ||
| auto vamana_algorithm = static_cast<AlgorithmVamana*>(algorithm.get()); | ||
| return dispatch_dynamic_vamana_memory_estimate( | ||
| auto estimate = uses_concurrent_index(sync_kind) |
There was a problem hiding this comment.
Could we apply the same patter for build and load too?
| @@ -237,4 +249,27 @@ void DynamicIndexVamana::set_num_threads(size_t num_threads) { | |||
| index.set_threadpool(this->builder->pool_builder.build()); | |||
There was a problem hiding this comment.
| index.set_threadpool(this->builder->pool_builder.build()); | |
| index.set_threadpool(this->builder->pool_builder.build(sync_kind)); |
Should also pass sync_kind here, otherwise calling this function on a FINE_GRAIN index goes back to NativeThreadPool
| ); | ||
|
|
||
| // The concurrent index's reverse edges are reported as part of the graph. | ||
| const size_t graph_bytes = breakdown.graph_bytes + breakdown.reverse_edges_bytes; |
There was a problem hiding this comment.
#369 added reverse_edges_bytes to the C++ MemoryBreakdown as its own field, could we add a reverse_edges_bytes field too/move the reverse edge bytes to graph_bytes on C++, to keep a unified design?



This pull request adds support for a concurrent dynamic index ("fine-grained" synchronization) in the C API, updating both the implementation and documentation to reflect the new concurrency model. It introduces a generalized way to select storage layouts based on the concurrency mode, and updates various data builder utilities to use this new approach. The documentation and C API headers are also updated to clarify the behavior and memory usage of the new concurrent index.
Concurrent index support and synchronization model:
dispatcher_concurrent_vamana.hpp/cpp,dispatcher_dynamic_vamana_copy.hpp). [1] [2]SVS_SYNC_KIND_FINE_GRAINmode, which uses a concurrent index allowing parallel updates and searches, and clarified the locking behavior and memory reporting for this mode. [1] [2] [3] [4]Generalization of storage layout selection:
BlockKindenum and a generalizedMaybeBlockedAlloctemplate to select between contiguous, blocked, or segmented (concurrent) storage layouts based on the concurrency mode.for_simple_specializations,for_sq_specializations,for_leanvec_specializations,for_lvq_specializations) to use the newBlockKind-based selection instead of a simple boolean for blocked/unblocked storage. [1] [2] [3] [4] [5] [6]Concurrent extension support for data builders: