Skip to content

[C API] Add SVS_SYNC_KIND_FINE_GRAIN support using ConcurrentDynamicVamana - #418

Open
rfsaliev wants to merge 7 commits into
mainfrom
rfsaliev/c-api-fine-grain-sync
Open

rfsaliev wants to merge 7 commits into
mainfrom
rfsaliev/c-api-fine-grain-sync

Conversation

@rfsaliev

@rfsaliev rfsaliev commented Oct 9, 2026

Copy link
Copy Markdown
Member

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:

  • Added new source files and headers for concurrent and dynamic Vamana index implementations to the C API build configuration (dispatcher_concurrent_vamana.hpp/cpp, dispatcher_dynamic_vamana_copy.hpp). [1] [2]
  • Updated documentation and C API headers to describe the new SVS_SYNC_KIND_FINE_GRAIN mode, 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:

  • Introduced a new BlockKind enum and a generalized MaybeBlockedAlloc template to select between contiguous, blocked, or segmented (concurrent) storage layouts based on the concurrency mode.
  • Updated all data builder specialization utilities (for_simple_specializations, for_sq_specializations, for_leanvec_specializations, for_lvq_specializations) to use the new BlockKind-based selection instead of a simple boolean for blocked/unblocked storage. [1] [2] [3] [4] [5] [6]

Concurrent extension support for data builders:

  • Included new concurrent extension headers in data builder implementations to enable support for concurrent index layouts in LeanVec, LVQ, and scalar quantization data builders. [1] [2] [3]

@rfsaliev
rfsaliev force-pushed the rfsaliev/c-api-fine-grain-sync branch from 6d21b35 to a31079e Compare October 13, 2026 13:32
@rfsaliev
rfsaliev requested a balanced review from Copilot October 13, 2026 13:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
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 ConcurrentIndexVamana wrapper for C API synchronization semantics.
  • Generalizes dataset/graph storage layout selection via BlockKind / MaybeBlockedAlloc across 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.

Comment thread bindings/c/src/data_builder.cpp Outdated
Comment thread bindings/c/src/data_builder.cpp Outdated
Comment thread bindings/c/src/dispatcher_concurrent_vamana.cpp
Comment thread bindings/c/src/index_vamana.hpp Outdated
Comment thread bindings/c/tests/c_api_dynamic_index_sync.cpp
@rfsaliev
rfsaliev force-pushed the rfsaliev/c-api-fine-grain-sync branch from a31079e to 5b1bd90 Compare October 13, 2026 14:58
@rfsaliev
rfsaliev requested a balanced review from Copilot October 14, 2026 12:16

@razdoburdin razdoburdin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please provide a code suggestion to prevent internal deadlocks?
Thank you.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bindings/c/src/index_builder.hpp
Comment thread bindings/c/src/dispatcher_dynamic_vamana_copy.hpp Outdated
Comment thread bindings/c/src/index_vamana.cpp
Comment thread bindings/c/src/index_vamana.hpp

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread bindings/c/src/index_vamana.hpp
Comment thread bindings/c/src/data_builder/leanvec.hpp Outdated
Comment thread bindings/c/src/data_builder/lvq.hpp
Comment thread bindings/c/src/data_builder/lvq.hpp Outdated
Comment thread bindings/c/src/data_builder/lvq.hpp Outdated
…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.
}

void compact(size_t batchsize) override {
auto lock = read_lock();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Comment thread bindings/c/src/svs_c.cpp
);

// 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#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 branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants