Skip to content

feat: add parallel batch insertion via addPoints#366

Closed
andreinknv wants to merge 2 commits into
yoshoku:mainfrom
andreinknv:feat/parallel-batch-add
Closed

feat: add parallel batch insertion via addPoints#366
andreinknv wants to merge 2 commits into
yoshoku:mainfrom
andreinknv:feat/parallel-batch-add

Conversation

@andreinknv

Copy link
Copy Markdown

Summary

Building an index with addPoint inserts every point on the JS thread,
one at a time. hnswlib's HierarchicalNSW::addPoint is thread-safe for
concurrent calls — it is the basis of hnswlib's own Python
add_items(num_threads=…) — so a batch can be inserted in parallel.

This PR adds HierarchicalNSW.addPoints(points, labels, options?): Promise<void>.

Details

  • Points and labels are extracted into C++ memory on the JS thread, then
    inserted across a worker-thread pool inside a Napi::AsyncWorker (the
    JS event loop stays free), returning a Promise.
  • options.numThreads — default: CPU core count; validated to 0–1024.
    options.replaceDeleted — default false.
  • parallelFor is modeled on hnswlib's own ParallelFor; the first
    exception thrown by any worker surfaces as a Promise rejection.
  • Points may be number[] or Float32Array.
  • HierarchicalNSW only — hnswlib's BruteforceSearch::addPoint
    serializes on an internal lock, so parallel batching there yields
    nothing.

Verification

npm test — 117/117 passing (9 new tests covering parallel insert,
Float32Array input, the numThreads option, and the validation /
rejection paths); native addon builds clean. binding.gyp adds
-pthread on Linux for std::thread.

Notes

  • Depends on feat: accept Float32Array as data-point input #365 (Float32Array input) — this branch is stacked on
    it and reuses its extractVector helper. Merge feat: accept Float32Array as data-point input #365 first; this PR's
    diff then reduces to just the batch-insert change.
  • The async worker references index_ by address — the same pattern the
    existing readIndex / writeIndex workers already use. Calling
    initIndex on the same instance while a batch is in flight is
    unsupported (a limitation shared with those methods).

🤖 Generated with Claude Code

andreinknv and others added 2 commits May 17, 2026 21:44
addPoint, searchKnn (both index classes) and the L2Space /
InnerProductSpace distance methods only accepted plain JS Arrays, so
a caller holding embeddings in a Float32Array had to copy them into
an Array element by element first.

Accept a Float32Array directly anywhere a vector is taken. A new
extractVector helper copies straight from the typed array's backing
buffer, skipping the per-element N-API number conversion the JS
Array path requires. The accepted types and the error messages /
.d.ts signatures are updated to match; tests cover both input forms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Building an index one addPoint call at a time inserts every point on
the JS thread, serially. hnswlib's HierarchicalNSW addPoint is
thread-safe for concurrent calls, so a batch can be inserted in
parallel.

addPoints(points, labels, options?) extracts the points and labels on
the JS thread, then inserts them across a pool of worker threads
(options.numThreads, default = CPU core count) inside an AsyncWorker,
returning a Promise. The parallelFor helper is modeled on hnswlib's
own ParallelFor; the first exception from any worker surfaces as a
Promise rejection. Points may be number[] or Float32Array.

HierarchicalNSW only — hnswlib's BruteforceSearch::addPoint serializes
on an internal lock, so parallel batching there yields nothing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andreinknv andreinknv closed this by deleting the head repository May 28, 2026
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.

1 participant