Skip to content

feat: high-performance HNSW and durable async indexing - #6

Merged
xDarkicex merged 24 commits into
masterfrom
feat/high-performance-hnsw
Jul 14, 2026
Merged

feat: high-performance HNSW and durable async indexing#6
xDarkicex merged 24 commits into
masterfrom
feat/high-performance-hnsw

Conversation

@xDarkicex

@xDarkicex xDarkicex commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • optimize HNSW construction and traversal with off-heap SoA queues, SIMD batching, lock-free indexing structures, and zero-GC hot paths
  • add bounded asynchronous WAL/index application with precise durable and index-applied LSN tracking
  • harden single-file publication, checkpoint recovery, and incremental index-delta replay
  • add bounded database and collection iteration APIs for transport-neutral export workflows
  • document measured 50k x 768d Nomic recall, construction throughput, search latency, WAL behavior, recovery guarantees, and cross-platform builds

Measured semantic results

  • M=36 serial: 628 graph-ready inserts/s, recall@10 1.000 at ef=200
  • M=24, 4 workers: 2,602 inserts/s, recall@10 0.998 at ef=200 and 0.999 at ef>=216
  • M=16, 4 workers: 2,946-3,408 inserts/s, recall@10 0.996-0.998 at ef=200 and up to 0.999 at ef=300
  • M=16 search at ef=200: approximately 0.33-0.39 ms p50 and 0.66-0.78 ms p99

These are graph-ready measurements, not async accepted-write claims. The README distinguishes current results from the future 20-30k accepted-write architecture target.

Validation

  • go test ./...
  • focused iteration API tests
  • Linux amd64 cross-compile
  • Linux arm64 cross-compile
  • Windows amd64 cross-compile
  • Darwin arm64 build/test path
  • semantic recall and throughput harness documented in docs/research/semantic-scale-validation.md

Review focus

  • WAL transaction boundaries, LSN persistence, checkpoint ordering, and crash recovery invariants
  • lock-free MPMC/off-heap mutation lifecycle and reclamation correctness
  • HNSW concurrent topology publication and recall behavior
  • bounded iteration snapshot semantics and cancellation
  • assembly ABI, scalar/SIMD parity, bounds safety, and architecture fallbacks

Automated review of .s files should be treated as advisory. Assembly changes require architecture builds, parity tests, and hardware benchmarks before acceptance.

Summary by CodeRabbit

  • New Features
    • Added Finite Scalar Quantization (FSQ) and related configuration options.
    • Added async indexing with index progress stats and an explicit index flush operation.
    • Added durability modes plus durable insert APIs for WAL-acknowledged commits.
    • Added Database/Collection streaming iteration APIs and ID map capacity configuration.
  • Documentation
    • Expanded measured performance, durability/recovery, and cross-compilation/SIMD generation guidance; added quantization mode docs.
  • Bug Fixes
    • Improved durability/recovery correctness, persistence and delete consistency, and tightened distance/SIMD parity validation.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@xDarkicex, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3abbc05e-b82d-4286-b3f3-0de8d782847f

📥 Commits

Reviewing files that changed from the base of the PR and between 502577c and daceec7.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • go.mod
  • internal/index/hnsw/delete.go
  • internal/index/hnsw/hnsw.go
  • internal/index/hnsw/reclamation.go
  • internal/index/hnsw/reclamation_test.go
  • internal/index/hnsw/search.go
  • internal/util/simd/distance_test.go
  • libravdb/async_index.go
📝 Walkthrough

Walkthrough

This PR adds concurrent off-heap HNSW storage and search, SIMD dispatch and generated kernels, FSQ quantization, durable WAL/recovery behavior, asynchronous indexing, mutation and iteration APIs, expanded benchmarks and tests, CI validation, and extensive configuration and research documentation.

Changes

HNSW and memory management

Layer / File(s) Summary
Concurrent graph storage and traversal
internal/index/hnsw/...
Nodes, links, registries, candidate queues, search scratch, vector stores, deletion, repair, reclamation, persistence, and insertion now use segmented/off-heap storage with atomic coordination.
Graph correctness and performance validation
internal/index/hnsw/*_test.go
Tests and benchmarks cover queue ordering, graph connectivity, deletion races, reclamation, recall, allocation behavior, semantic-scale workloads, and concurrency scaling.

Durability and indexing

Layer / File(s) Summary
WAL durability and recovery
internal/storage/..., libravdb/index_persistence.go
WAL syncing, durable insertion ranges, checkpoint frontiers, incremental index replay, recovery fallback, durable replacement, and parent-directory synchronization are implemented.
Asynchronous HNSW indexing
libravdb/async_index.go, libravdb/collection.go, libravdb/database.go
A bounded off-heap queue applies durable mutations asynchronously and exposes index flushing and frontier statistics.
Mutation, iteration, and configuration APIs
libravdb/mutation_state.go, libravdb/database.go, libravdb/collection.go
Off-heap mutation guards, streaming iteration, defensive configuration copies, cosine preprocessing, durability options, and ID-map configuration are added.

SIMD and quantization

Layer / File(s) Summary
SIMD dispatch and generation
internal/util/distance.go, internal/util/simd/*
Runtime dispatch selects AVX2/FMA or NEON implementations, with generated AMD64 assembly, ARM64 kernels, pointer-batched operations, prefetch helpers, fallbacks, and architecture-gated tests.
Finite scalar quantization
internal/quant/*, libravdb/options.go
FSQ configuration, training, variable-width packing, decompression, distance evaluation, registration, integration tests, and collection options are added.

Documentation and validation

Layer / File(s) Summary
Documentation and research
README.md, docs/configuration/*, docs/research/*, docs/scratch-hnsw-source-notes.md
Documentation covers performance, durability, lifecycle APIs, cross-compilation, quantization modes, SIMD parity, and ANN research findings.
CI, dependencies, and benchmarks
.github/workflows/*, benchmark/*, go.mod
CI verifies generated SIMD output and runs graph, HNSW, and SIMD tests across the matrix; benchmark setup and allocator dependencies are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

A rabbit hops through SIMD light,
While WAL leaves lock up tight.
HNSW paths bloom and bind,
FSQ packs bits just right.
Async carrots queue in flight!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the PR well, but it does not follow the repository template and omits required sections like Related Issue and Type of Change. Rewrite it using the required template and fill in Related Issue, Type of Change, Changes Made, Testing, Performance Impact, and any Breaking Changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main themes: high-performance HNSW plus durable async indexing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
internal/storage/singlefile/engine.go (1)

3548-3567: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Close aborts before syncing/closing the file when arena or pool teardown fails.

If walWriteArena.Free() or walRequests.close() returns an error, Close returns immediately, so e.file.Sync() (for the WithWALSync(false) dirty case), e.file.Close(), and e.closed.Store(true) never run. That leaks the file descriptor and leaves the engine neither closed nor cleaned up; a retried Close would also re-invoke Free() on the same arena since e.walWriteArena was never niled. Accumulate the errors and always run sync/close.

🛠️ Proposed fix
-	if e.walWriteArena != nil {
-		if err := e.walWriteArena.Free(); err != nil {
-			return err
-		}
-		e.walWriteArena = nil
-	}
-	if e.walRequests != nil {
-		if err := e.walRequests.close(); err != nil {
-			return err
-		}
-		e.walRequests = nil
-	}
-	if e.dirty {
-		if err := e.file.Sync(); err != nil {
-			return err
-		}
-	}
-	e.closed.Store(true)
-	return e.file.Close()
+	var closeErr error
+	if e.walWriteArena != nil {
+		closeErr = errors.Join(closeErr, e.walWriteArena.Free())
+		e.walWriteArena = nil
+	}
+	if e.walRequests != nil {
+		closeErr = errors.Join(closeErr, e.walRequests.close())
+		e.walRequests = nil
+	}
+	if e.dirty {
+		closeErr = errors.Join(closeErr, e.file.Sync())
+	}
+	e.closed.Store(true)
+	return errors.Join(closeErr, e.file.Close())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/singlefile/engine.go` around lines 3548 - 3567, Update Close
to accumulate errors from walWriteArena.Free and walRequests.close instead of
returning immediately, and nil each resource after its teardown attempt so
retries do not repeat cleanup. Always continue through the dirty-file Sync,
closed.Store(true), and file.Close steps, returning the combined cleanup error
afterward.
libravdb/options.go (1)

128-138: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject m == 1 before deriving ML.

math.Log(1) is zero, so the current validation permits an infinite level multiplier. Require m > 1.

Proposed fix
-		if m <= 0 || efConstruction <= 0 || efSearch <= 0 {
-			return fmt.Errorf("HNSW parameters must be positive")
+		if m <= 1 || efConstruction <= 0 || efSearch <= 0 {
+			return fmt.Errorf("HNSW m must be greater than 1 and ef parameters must be positive")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libravdb/options.go` around lines 128 - 138, Update the validation in
WithHNSW so m must be greater than 1 before calculating ML, while retaining the
positive-value requirements for efConstruction and efSearch.
internal/storage/singlefile/codec.go (1)

339-367: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the complete optional payload before decoding or skipping it.

A corrupt optSize can move dec.Off beyond dec.Data, while truncated known fields are silently accepted. After reading the prefix, verify optSize <= len(dec.Data)-dec.Off, decode within that bounded section, then set the offset to its validated end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/singlefile/codec.go` around lines 339 - 367, Update the
version >= 2 optional-field parsing after ReadUint32 to validate that optSize
fits within the remaining decoder data before reading any fields or skipping
unknown bytes. Decode known fields only within this bounded optional section,
reject truncated payloads instead of silently accepting them, then advance
dec.Off to the validated section end rather than adding optSize blindly.
🟡 Minor comments (15)
docs/research/flash-hnsw-compact-codes.md-57-81 (1)

57-81: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the perpendicular-bisector definitions.

The table and Line 79 define e/b using the wrong vectors and signs. For the stated comparison, use e = w - v, b = (||w||² - ||v||²)/2, and δ(u,v)² - δ(u,w)² = 2(e·u - b) consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/flash-hnsw-compact-codes.md` around lines 57 - 81, Correct the
perpendicular-bisector definitions in the notation table and Lemma 1 proof: use
e = w - v and b = (||w||² - ||v||²)/2 for comparing δ(u,v) with δ(u,w), and
update the squared-distance identity to δ(u,v)² - δ(u,w)² = 2(e·u - b). Ensure
the stated less-than, greater-than, and equality conditions consistently use
this corrected sign convention.
docs/research/hnsw-plus-plus-lid.md-35-41 (1)

35-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the sign in the LID estimator.

With log(d_k/d_i), the estimator must be positive:

-LID(x) = -1 / ( (1 / (k-1)) * sum_{i=1}^{k-1} log(d_k / d_i) )
+LID(x) = 1 / ( (1 / (k-1)) * sum_{i=1}^{k-1} log(d_k / d_i) )

Alternatively, retain the minus sign and use log(d_i/d_k).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/hnsw-plus-plus-lid.md` around lines 35 - 41, Correct the LID
estimator equation in the documentation so its result is positive: either remove
the leading minus sign while retaining log(d_k / d_i), or keep the minus sign
and reverse the logarithm ratio to log(d_i / d_k). Update the displayed LID
formula only and preserve the existing k−1 summation.
go.mod-25-25 (1)

25-25: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Bump google.golang.org/protobuf off 1.31.0 (known HIGH advisories).

OSV flags v1.31.0 for GO-2024-2611 / GHSA-8r3f-844c-mc37 (infinite loop in protojson.Unmarshal on invalid JSON). Since this go.mod is already being modified, bump the transitive pin (e.g. to >= v1.33.0) via go get and re-tidy.

#!/bin/bash
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: GO, package: "google.golang.org/protobuf") {
    nodes { advisory { summary severity } vulnerableVersionRange firstPatchedVersion { identifier } }
  }
}'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 25, Update the google.golang.org/protobuf dependency in
go.mod from v1.31.0 to v1.33.0 or newer, then run go get and go mod tidy so the
transitive dependency metadata is consistent.

Source: Linters/SAST tools

docs/research/cagra-gpu.md-30-30 (1)

30-30: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Typo: CACGCAGRA.

the locality CACG needs should read CAGRA.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/cagra-gpu.md` at line 30, In the Stage 1 description, replace
the incorrect acronym “CACG” with “CAGRA” in the sentence describing the
locality requirement.
internal/index/hnsw/insert.go-329-331 (1)

329-331: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate the node and level before indexing Links.

A missing node, negative level, or level above the node’s maximum currently panics before appendWithSpinlock can reject it.

Proposed fix
 func (h *Index) manualConnect(nodeID uint32, linkID uint32, level int) bool {
 	node := h.nodes.Get(nodeID)
+	if node == nil || level < 0 || level > node.Level || level >= MaxLevel {
+		return false
+	}
 	return h.appendWithSpinlock(node, node.Links[level], linkID, h.config.M, level)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/index/hnsw/insert.go` around lines 329 - 331, Update
Index.manualConnect to validate that the retrieved node is non-nil and level is
within the node’s Links bounds before accessing node.Links[level]. Return false
for invalid node or level; otherwise preserve the existing appendWithSpinlock
call.
internal/storage/singlefile/wal_request.go-13-20 (1)

13-20: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

iota is offset by the capacity constants — state enum starts at 2, not 0.

Because the state constants share this const block with walRequestCapacity/walRequestWords, iota is already 2 at walRequestFree, so walRequestFree=2, walRequestPending=3, walRequestComplete=4. The values stay distinct, so the current state machine still works, but a zero-value walRequestRecord has state==0, which is none of these — a fragile mismatch for any future zero-value "is free" check. Split the enum into its own block so it starts at 0.

♻️ Proposed fix
 const (
 	walRequestCapacity = 4096
 	walRequestWords    = walRequestCapacity / 64
+)
 
+const (
 	walRequestFree uint32 = iota
 	walRequestPending
 	walRequestComplete
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/singlefile/wal_request.go` around lines 13 - 20, Split the
walRequestFree, walRequestPending, and walRequestComplete constants into a
separate const block so iota starts at zero and preserves the zero-value
walRequestRecord as the free state; leave walRequestCapacity and walRequestWords
unchanged.
docs/research/freshdiskann.md-21-21 (1)

21-21: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Escape the asterisks in p*.

Markdown interprets these occurrences as emphasis delimiters, breaking the rendered algorithms. Use p\*, inline code, or mathematical notation.

Also applies to: 29-29, 33-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/freshdiskann.md` at line 21, Escape every literal asterisk in
the node notation across the affected documentation lines, including each
occurrence of p* in the navigability and algorithm descriptions, using p\* or an
equivalent Markdown-safe notation while preserving the mathematical meaning.

Source: Linters/SAST tools

docs/research/freshdiskann.md-48-48 (1)

48-48: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the random-read estimate.

~100 ≈ 75·l_search cannot hold with the documented default l_search = 75; the product is 5,625. Replace this with the paper’s actual estimate or relationship.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/freshdiskann.md` at line 48, Correct the random-read estimate
in the FreshDiskANN documentation, replacing the inconsistent “~100 ≈
75·l_search” relationship with the paper’s actual estimate or relationship. Keep
the surrounding I/O-cost and α-RNG explanation unchanged.
docs/research/freshdiskann.md-17-19 (1)

17-19: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the recall definition.

G already denotes the graph, while X = |X ∩ L| / k is self-referential. Use a distinct ground-truth set, for example recall@k = |T ∩ L| / k.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/freshdiskann.md` around lines 17 - 19, Correct Definition 1.1
by replacing the self-referential recall notation with a distinct ground-truth
k-NN set symbol, such as T, and define recall@k as |T ∩ L| / k. Preserve the
existing query output L and target quality statement, and avoid reusing G for
the ground-truth set because G already denotes the graph.
docs/research/diskann-vamana.md-11-11 (1)

11-11: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Drop the machine-local absolute path. The committed Local copy: line leaks a developer username and workstation path. Prefer a repo-relative reference.

🧹 Suggested change
-- **Local copy:** `/Users/z3robit/Development/golang/src/github.com/xDarkicex/libraVDB/docs/research/diskann-vamana.pdf` (10 pages, no appendix)
+- **Local copy:** `docs/research/diskann-vamana.pdf` (10 pages, no appendix)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/diskann-vamana.md` at line 11, Replace the machine-local
absolute path in the “Local copy” entry of the DiskANN-Vamana research document
with a repository-relative reference, preserving the document and file
description without exposing developer-specific usernames or workstation
locations.
internal/index/hnsw/node.go-27-33 (1)

27-33: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid the heap-backed fallback in internal/index/hnsw/vector_store_mmap.go. The normal raw-vector stores keep vectors off-heap, but if copyPool is nil, Get falls back to make([]float32, s.dim), which can leave an off-heap Node pointing at a Go-heap array. Fail closed there or allocate from the same pool.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/index/hnsw/node.go` around lines 27 - 33, Update the vector
retrieval path in vector_store_mmap.go, specifically the Get logic using
copyPool, so a nil copyPool never creates a make([]float32, s.dim) fallback that
is passed to Node.setVector. Fail closed when the pool is unavailable, or obtain
the vector from the established off-heap pool before calling setVector,
preserving the raw-vector off-heap invariant.
docs/research/original-hnsw-malkov-yashunin.md-262-267 (1)

262-267: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Escape |C| so the table renders correctly.

Use \|C\| in the heuristic-cost expression; the current pipes are parsed as column separators.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/original-hnsw-malkov-yashunin.md` around lines 262 - 267,
Update the “Single insert” row in the operation-complexity table so the
heuristic-cost expression escapes both pipe characters around C as \|C\|,
preserving the intended table rendering and all other text unchanged.

Source: Linters/SAST tools

docs/research/original-hnsw-malkov-yashunin.md-7-7 (1)

7-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the workstation-specific PDF path.

This absolute path exposes a local username and cannot work for other contributors. Use a repository-relative path if the PDF is tracked; otherwise omit the field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/original-hnsw-malkov-yashunin.md` at line 7, Remove the
workstation-specific absolute path from the “Local PDF” entry in the document;
if the PDF is tracked, replace it with its repository-relative path, otherwise
remove the field entirely.
docs/research/original-hnsw-malkov-yashunin.md-51-75 (1)

51-75: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the level-distribution and complexity derivation.

For l=floor(-ln(U)·mL), the distribution is:

P(l=i)=(1-exp(-1/mL))·exp(-i/mL).

With mL=1/ln(M), this becomes (1-1/M)·M^-i. The expected layers per node are constant; it is the graph’s maximum height that scales as O(log_M N) and yields logarithmic traversal depth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/original-hnsw-malkov-yashunin.md` around lines 51 - 75, Correct
the level-distribution derivation around the displayed formula and equation (1):
use P(l=i)=(1−exp(−1/mL))·exp(−i/mL), which becomes (1−1/M)·M^-i when
mL=1/ln(M). Update the surrounding explanation to state that expected layers per
node are constant, while the graph’s maximum height scales as O(log_M N) and
determines logarithmic traversal depth; remove claims that per-layer
connectivity or E[l+1] directly establishes this complexity.
docs/research/original-hnsw-malkov-yashunin.md-88-90 (1)

88-90: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the HNSW memory-complexity formula.

Per-node adjacency is approximately M_max0 + M_max·E[level], not M_max0·M_max. With M_max0=2M, graph-link storage is O(N·M), excluding vectors and allocator overhead. Update both the prose and summary table.

Also applies to: 262-267

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/research/original-hnsw-malkov-yashunin.md` around lines 88 - 90, Correct
the HNSW memory-cost description in section 2.6 and its corresponding
summary-table entry: express per-node adjacency as approximately M_max0 +
M_max·E[level], and state graph-link storage as O(N·M) when M_max0=2M, excluding
vectors and allocator overhead. Remove the incorrect M_max0·M_max formulation
while preserving the 60–450 bytes context only if it remains consistent with the
corrected model.
🧹 Nitpick comments (4)
internal/quant/fsq.go (1)

317-324: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove denormalizeLocked or use it in decode. It has no call sites in internal/quant/fsq.go, so it only adds maintenance overhead unless it’s meant to be the reconstruction inverse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/quant/fsq.go` around lines 317 - 324, Remove the unused
FSQQuantizer.denormalizeLocked method, or integrate it into the decode path as
the reconstruction inverse if that is its intended purpose. Ensure no dead
helper remains without call sites in fsq.go.
libravdb/mutation_state.go (1)

103-109: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Park contended waiters instead of spinning indefinitely.

Both acquisition paths retry with only runtime.Gosched(). With many writers or slot collisions, all waiters remain runnable and continuously contend on the same cache lines. Use bounded spinning followed by a parking primitive or backoff, ideally with cancellation support.

Also applies to: 119-151

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libravdb/mutation_state.go` around lines 103 - 109, Update lockMutationID and
the corresponding acquisition path to avoid retrying indefinitely with only
runtime.Gosched(). Add bounded spinning, then park or back off contended
waiters, using cancellation support if the existing mutation-state API permits
it; preserve successful token acquisition and release behavior.
internal/index/hnsw/hnsw.go (1)

1181-1202: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor cleanup: duplicate nil guard and shadowed loop variable.

Lines 1183-1188 nil-check node twice, and the inner links loop at Line 1195 reuses i, shadowing the outer index. Same duplicate-nil pattern recurs in estimateFileSize (Lines 1835-1857) and SnapshotVectorsFromProvider (Lines 1502-1505). Drop the second check and rename the inner index for clarity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/index/hnsw/hnsw.go` around lines 1181 - 1202, Remove the duplicate
node nil guard in the shown usage loop, and rename the inner links-loop index so
it does not shadow the outer node index. Apply the same duplicate nil-check
cleanup in estimateFileSize and SnapshotVectorsFromProvider, preserving their
existing traversal behavior.
internal/storage/singlefile/codec.go (1)

212-220: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Include IDMapCapacity in the encoded-size estimate.

The version-2 payload gained four bytes, but estimateCollectionConfigSize still adds only the length prefix. Increase its version-2 adjustment from 4 to 8 bytes to avoid under-allocation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/singlefile/codec.go` around lines 212 - 220, Update
estimateCollectionConfigSize so its version-2 size adjustment accounts for both
the IDMapCapacity field and its length prefix, increasing the adjustment from 4
to 8 bytes while preserving the existing size calculation for earlier versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go.mod`:
- Line 28: Remove the local replace directive for github.com/xDarkicex/memory
from go.mod, preserving the published v1.2.1 dependency so CI and external
consumers resolve it normally. If local development still requires the sibling
checkout, move that override to an uncommitted workspace configuration.

In `@internal/index/hnsw/delete.go`:
- Around line 442-470: Update handleEntryPointReplacement to compare deletedID
with the current entry point’s ordinal and return immediately when they differ.
Only perform the existing fallback scan and replacement logic when the deleted
node is the current entry point, preserving the current behavior for replacement
failures and empty indexes.

In `@internal/index/hnsw/hnsw.go`:
- Around line 588-599: Update insertSingleMetadata so the early nil return
occurs only when updateEntryPointCAS succeeds while the index remains in the
empty state; if a concurrent higher-level insert has already established an
entry point, continue through the normal insertNode path instead of treating the
CAS success as sufficient.
- Around line 461-468: Update the insert rollback around insertNode failure and
the ID-conflict path to release the nodeSFL slot, all upper-level link/backlink
slab allocations, and the raw-vector slot before clearing registry metadata and
size. Replace the racy getEntryPoint()==nil fast path with synchronized
first-node initialization so concurrent inserts cannot leave a registered node
unconnected, including when a higher-level node wins updateEntryPointCAS.

In `@internal/index/hnsw/neighbors.go`:
- Around line 462-464: In the pruning path around maxCapacity and unsafe.Slice,
reacquire the prune lock and revalidate the node and node.Links[level] before
slicing, mirroring the under-lock checks in connectLinkWithHeuristic. If the
registry entry or level link storage is no longer valid, exit without
constructing the slice; otherwise proceed with the existing capacity calculation
and pruning logic.

In `@internal/index/hnsw/repair.go`:
- Around line 103-118: Update the repair processing loop around scanDirtyRepairs
to clear repairOverflow before scanning, not after a zero-scan result. When the
overflow flag is raised concurrently during or after the scan, preserve it and
rescan rather than returning; only return processed when no repairs were scanned
and no new overflow notification is set.

In `@internal/index/hnsw/search.go`:
- Line 1032: Update newSOACandidateQueue to handle nil soaIDs or soaDistances
without slicing them, returning a valid empty/non-SOA queue state that preserves
search behavior. Keep the existing capacity-backed initialization when both SoA
buffers are present, so heap, unsorted, and reservoir modes no longer panic when
searchLevelScratchValues constructs the queue.

In `@internal/index/hnsw/segmented_array.go`:
- Around line 22-33: Update segmentedNodeArray and its node insertion/removal
paths to retain every live *Node in a Go-heap-owned structure, rather than
relying only on off-heap directory chunks and idToIndex. Ensure the ownership
structure is updated consistently when nodes become live or are removed, while
preserving existing append-only lookup behavior.

In `@internal/index/hnsw/vector_store.go`:
- Around line 139-147: Reclaim backing allocations when vectors are deleted so
insert/delete churn does not exhaust the pool: update
InMemoryRawVectorStore.Delete in internal/index/hnsw/vector_store.go (139-147)
to defer reclamation until no readers can access the slot, then return the
allocation to the reusable pool. Apply the equivalent deferred-reclamation
change to the deleted slab slots in internal/index/hnsw/vector_store_slabby.go
(123-132), ensuring slots are not recycled before outstanding readers finish.

In `@internal/util/distance.go`:
- Around line 22-68: Add a length-mismatch guard in GetDistanceFunc’s SIMD
dispatch paths so every returned AVX2/NEON distance function validates len(a)
equals len(b) before invoking the kernel. Preserve the existing scalar helper
behavior and distance calculations, and ensure mismatched dimensions produce the
same panic behavior rather than allowing SIMD kernels to read past b.

In `@internal/util/simd/stub_arm64.go`:
- Around line 7-19: Add //go:noescape directives to every SIMD declaration in
stub_arm64.go, including DotProductNEON, L2DistanceNEON, L2Distance4NEON,
L2Distance4PtrNEON, L2Distance8PtrNEON, L2Distance8AlignedPtrNEON, and
L2AnyLessThan8AlignedPtrNEON, matching the declarations in stub_amd64.go.

In `@libravdb/async_index.go`:
- Around line 54-58: Extend the asyncIndexStorage interface to declare the
GetIDByOrdinal method required by the usage around lines 250–255, matching the
implementing storage type’s existing signature. Leave the other interface
methods unchanged.

In `@libravdb/collection.go`:
- Around line 846-857: Add both durable-range methods to the asyncIndexStorage
interface/type used by c.asyncIndex.storage: InsertDurableRange(context.Context,
*index.VectorEntry) (storage.DurableRange, error) for the path at
libravdb/collection.go:846-857, and InsertBatchDurableRange(context.Context,
[]*index.VectorEntry) (storage.DurableRange, error) for the path at
libravdb/collection.go:1006-1022. Ensure the concrete storage implementation
satisfies these methods, or retype storage to the existing implementation that
provides them.
- Around line 1155-1160: Reorder locking in Collection.Update and the
corresponding Upsert and Delete paths so the write permit from acquireWrite is
obtained before calling lockAsyncMutation. Preserve existing error handling and
deferred unlock behavior, and align async Insert’s lock order to prevent waits
between the write controller and asyncMutation barrier.

In `@libravdb/database.go`:
- Around line 219-222: Update the async-index failure path in the
collection-creation method around configureAsyncIndex to preserve and join
errors from both collection.Close and db.storage.DeleteCollection instead of
discarding them. Return the combined setup and cleanup errors so callers can
detect when rollback was incomplete; apply the same handling to the
corresponding failure path noted later in the method.

In `@libravdb/index_persistence.go`:
- Around line 84-89: Update the index persistence flow around
preciseAppliedLocked and idx.SerializeToBytes so it does not serialize idx when
the current applied LSN exceeds checkpointLSN. Fall back to the existing
record-based rebuild path in that case, preserving the checkpoint frontier and
avoiding an image containing mutations newer than the checkpoint.

In `@libravdb/migrate.go`:
- Around line 119-134: Update replaceFileDurably to report whether the rename
occurred separately from durability-sync errors, then use that state in the
migration rollback paths around replaceFileDurably calls to restore the correct
file arrangement when sync fails after a successful rename. Update
recoverMigrate to propagate activation-recovery errors instead of suppressing
them, ensuring startup cannot continue with an unresolved swap.

---

Outside diff comments:
In `@internal/storage/singlefile/codec.go`:
- Around line 339-367: Update the version >= 2 optional-field parsing after
ReadUint32 to validate that optSize fits within the remaining decoder data
before reading any fields or skipping unknown bytes. Decode known fields only
within this bounded optional section, reject truncated payloads instead of
silently accepting them, then advance dec.Off to the validated section end
rather than adding optSize blindly.

In `@internal/storage/singlefile/engine.go`:
- Around line 3548-3567: Update Close to accumulate errors from
walWriteArena.Free and walRequests.close instead of returning immediately, and
nil each resource after its teardown attempt so retries do not repeat cleanup.
Always continue through the dirty-file Sync, closed.Store(true), and file.Close
steps, returning the combined cleanup error afterward.

In `@libravdb/options.go`:
- Around line 128-138: Update the validation in WithHNSW so m must be greater
than 1 before calculating ML, while retaining the positive-value requirements
for efConstruction and efSearch.

---

Minor comments:
In `@docs/research/cagra-gpu.md`:
- Line 30: In the Stage 1 description, replace the incorrect acronym “CACG” with
“CAGRA” in the sentence describing the locality requirement.

In `@docs/research/diskann-vamana.md`:
- Line 11: Replace the machine-local absolute path in the “Local copy” entry of
the DiskANN-Vamana research document with a repository-relative reference,
preserving the document and file description without exposing developer-specific
usernames or workstation locations.

In `@docs/research/flash-hnsw-compact-codes.md`:
- Around line 57-81: Correct the perpendicular-bisector definitions in the
notation table and Lemma 1 proof: use e = w - v and b = (||w||² - ||v||²)/2 for
comparing δ(u,v) with δ(u,w), and update the squared-distance identity to
δ(u,v)² - δ(u,w)² = 2(e·u - b). Ensure the stated less-than, greater-than, and
equality conditions consistently use this corrected sign convention.

In `@docs/research/freshdiskann.md`:
- Line 21: Escape every literal asterisk in the node notation across the
affected documentation lines, including each occurrence of p* in the
navigability and algorithm descriptions, using p\* or an equivalent
Markdown-safe notation while preserving the mathematical meaning.
- Line 48: Correct the random-read estimate in the FreshDiskANN documentation,
replacing the inconsistent “~100 ≈ 75·l_search” relationship with the paper’s
actual estimate or relationship. Keep the surrounding I/O-cost and α-RNG
explanation unchanged.
- Around line 17-19: Correct Definition 1.1 by replacing the self-referential
recall notation with a distinct ground-truth k-NN set symbol, such as T, and
define recall@k as |T ∩ L| / k. Preserve the existing query output L and target
quality statement, and avoid reusing G for the ground-truth set because G
already denotes the graph.

In `@docs/research/hnsw-plus-plus-lid.md`:
- Around line 35-41: Correct the LID estimator equation in the documentation so
its result is positive: either remove the leading minus sign while retaining
log(d_k / d_i), or keep the minus sign and reverse the logarithm ratio to
log(d_i / d_k). Update the displayed LID formula only and preserve the existing
k−1 summation.

In `@docs/research/original-hnsw-malkov-yashunin.md`:
- Around line 262-267: Update the “Single insert” row in the
operation-complexity table so the heuristic-cost expression escapes both pipe
characters around C as \|C\|, preserving the intended table rendering and all
other text unchanged.
- Line 7: Remove the workstation-specific absolute path from the “Local PDF”
entry in the document; if the PDF is tracked, replace it with its
repository-relative path, otherwise remove the field entirely.
- Around line 51-75: Correct the level-distribution derivation around the
displayed formula and equation (1): use P(l=i)=(1−exp(−1/mL))·exp(−i/mL), which
becomes (1−1/M)·M^-i when mL=1/ln(M). Update the surrounding explanation to
state that expected layers per node are constant, while the graph’s maximum
height scales as O(log_M N) and determines logarithmic traversal depth; remove
claims that per-layer connectivity or E[l+1] directly establishes this
complexity.
- Around line 88-90: Correct the HNSW memory-cost description in section 2.6 and
its corresponding summary-table entry: express per-node adjacency as
approximately M_max0 + M_max·E[level], and state graph-link storage as O(N·M)
when M_max0=2M, excluding vectors and allocator overhead. Remove the incorrect
M_max0·M_max formulation while preserving the 60–450 bytes context only if it
remains consistent with the corrected model.

In `@go.mod`:
- Line 25: Update the google.golang.org/protobuf dependency in go.mod from
v1.31.0 to v1.33.0 or newer, then run go get and go mod tidy so the transitive
dependency metadata is consistent.

In `@internal/index/hnsw/insert.go`:
- Around line 329-331: Update Index.manualConnect to validate that the retrieved
node is non-nil and level is within the node’s Links bounds before accessing
node.Links[level]. Return false for invalid node or level; otherwise preserve
the existing appendWithSpinlock call.

In `@internal/index/hnsw/node.go`:
- Around line 27-33: Update the vector retrieval path in vector_store_mmap.go,
specifically the Get logic using copyPool, so a nil copyPool never creates a
make([]float32, s.dim) fallback that is passed to Node.setVector. Fail closed
when the pool is unavailable, or obtain the vector from the established off-heap
pool before calling setVector, preserving the raw-vector off-heap invariant.

In `@internal/storage/singlefile/wal_request.go`:
- Around line 13-20: Split the walRequestFree, walRequestPending, and
walRequestComplete constants into a separate const block so iota starts at zero
and preserves the zero-value walRequestRecord as the free state; leave
walRequestCapacity and walRequestWords unchanged.

---

Nitpick comments:
In `@internal/index/hnsw/hnsw.go`:
- Around line 1181-1202: Remove the duplicate node nil guard in the shown usage
loop, and rename the inner links-loop index so it does not shadow the outer node
index. Apply the same duplicate nil-check cleanup in estimateFileSize and
SnapshotVectorsFromProvider, preserving their existing traversal behavior.

In `@internal/quant/fsq.go`:
- Around line 317-324: Remove the unused FSQQuantizer.denormalizeLocked method,
or integrate it into the decode path as the reconstruction inverse if that is
its intended purpose. Ensure no dead helper remains without call sites in
fsq.go.

In `@internal/storage/singlefile/codec.go`:
- Around line 212-220: Update estimateCollectionConfigSize so its version-2 size
adjustment accounts for both the IDMapCapacity field and its length prefix,
increasing the adjustment from 4 to 8 bytes while preserving the existing size
calculation for earlier versions.

In `@libravdb/mutation_state.go`:
- Around line 103-109: Update lockMutationID and the corresponding acquisition
path to avoid retrying indefinitely with only runtime.Gosched(). Add bounded
spinning, then park or back off contended waiters, using cancellation support if
the existing mutation-state API permits it; preserve successful token
acquisition and release behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d4e7e37c-6f2d-4c38-92bc-7cddcd22abed

📥 Commits

Reviewing files that changed from the base of the PR and between e7b6a14 and d51eed6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (109)
  • .github/workflows/ci.yml
  • .github/workflows/graph-tests.yml
  • README.md
  • benchmark/hyaline_smoke_bench_test.go
  • benchmark/write_contention_benchmark_test.go
  • docs/configuration/configuration.md
  • docs/configuration/quantization-modes.md
  • docs/research/async-wal-indexing-plan.md
  • docs/research/cagra-gpu.md
  • docs/research/cross-platform-simd-parity.md
  • docs/research/diskann-scatter-beam-libravdb-experiment.md
  • docs/research/diskann-soa-candidate-queue-experiment.md
  • docs/research/diskann-vamana.md
  • docs/research/exact-early-termination-and-reuse-experiments.md
  • docs/research/flash-hnsw-compact-codes.md
  • docs/research/flat-hnsw-hubs.md
  • docs/research/freshdiskann.md
  • docs/research/hnsw-plus-plus-lid.md
  • docs/research/nsg-spreading-graph.md
  • docs/research/original-hnsw-malkov-yashunin.md
  • docs/research/pipnn-bulk-build.md
  • docs/research/quiver-binary-quantization.md
  • docs/research/semantic-scale-validation.md
  • docs/scratch-hnsw-source-notes.md
  • go.mod
  • internal/graph/reverse.go
  • internal/graph/store.go
  • internal/index/flat/flat.go
  • internal/index/flat/flat_test.go
  • internal/index/hnsw/candidate_shootout_test.go
  • internal/index/hnsw/candidate_soa.go
  • internal/index/hnsw/candidate_soa_test.go
  • internal/index/hnsw/delete.go
  • internal/index/hnsw/delete_regression_test.go
  • internal/index/hnsw/global_state.go
  • internal/index/hnsw/hnsw.go
  • internal/index/hnsw/hnsw_test.go
  • internal/index/hnsw/hnsw_throughput_bench_test.go
  • internal/index/hnsw/insert.go
  • internal/index/hnsw/mmap_helper.go
  • internal/index/hnsw/neighbors.go
  • internal/index/hnsw/node.go
  • internal/index/hnsw/persistence.go
  • internal/index/hnsw/quantization_test.go
  • internal/index/hnsw/raw_slot_array.go
  • internal/index/hnsw/repair.go
  • internal/index/hnsw/search.go
  • internal/index/hnsw/search_regression_test.go
  • internal/index/hnsw/segmented_array.go
  • internal/index/hnsw/semantic_scale_bench_test.go
  • internal/index/hnsw/simple_quantization_test.go
  • internal/index/hnsw/vector_store.go
  • internal/index/hnsw/vector_store_mmap.go
  • internal/index/hnsw/vector_store_slabby.go
  • internal/index/hnsw/vector_store_slabby_test.go
  • internal/index/hnsw/vector_store_test.go
  • internal/index/interfaces.go
  • internal/index/ivfpq/ivfpq.go
  • internal/quant/benchmark_test.go
  • internal/quant/errors.go
  • internal/quant/fsq.go
  • internal/quant/fsq_test.go
  • internal/quant/interfaces.go
  • internal/quant/interfaces_test.go
  • internal/quant/product.go
  • internal/quant/registry.go
  • internal/storage/fsdurability/sync_test.go
  • internal/storage/fsdurability/sync_unix.go
  • internal/storage/fsdurability/sync_windows.go
  • internal/storage/interfaces.go
  • internal/storage/singlefile/codec.go
  • internal/storage/singlefile/engine.go
  • internal/storage/singlefile/engine_test.go
  • internal/storage/singlefile/v1compat.go
  • internal/storage/singlefile/wal_benchmark_test.go
  • internal/storage/singlefile/wal_request.go
  • internal/storage/singlefile/wal_request_test.go
  • internal/util/distance.go
  • internal/util/distance_test.go
  • internal/util/simd/distance_amd64.s
  • internal/util/simd/distance_arm64.s
  • internal/util/simd/distance_batch_amd64.go
  • internal/util/simd/distance_batch_arm64.go
  • internal/util/simd/distance_batch_other.go
  • internal/util/simd/distance_test.go
  • internal/util/simd/generate.go
  • internal/util/simd/generate_directive.go
  • internal/util/simd/prefetch8_other.go
  • internal/util/simd/prefetch_amd64.go
  • internal/util/simd/stub_amd64.go
  • internal/util/simd/stub_arm64.go
  • internal/util/simd/stub_notamd64.go
  • internal/util/simd/stub_notarm64.go
  • internal/util/simd/tools.go
  • libravdb/async_index.go
  • libravdb/async_index_test.go
  • libravdb/batch.go
  • libravdb/collection.go
  • libravdb/collection_benchmark_test.go
  • libravdb/collection_config_test.go
  • libravdb/database.go
  • libravdb/index_persistence.go
  • libravdb/index_recovery_delta_test.go
  • libravdb/iteration_api_test.go
  • libravdb/migrate.go
  • libravdb/mutation_state.go
  • libravdb/mutation_state_test.go
  • libravdb/options.go
  • scripts/semanticfixture/generate.go

Comment thread go.mod Outdated
Comment thread internal/index/hnsw/delete.go
Comment thread internal/index/hnsw/hnsw.go
Comment thread internal/index/hnsw/hnsw.go
Comment thread internal/index/hnsw/neighbors.go
Comment thread libravdb/collection.go
Comment on lines +846 to +857
if c.asyncIndex != nil {
durable, err := c.asyncIndex.storage.InsertDurableRange(ctx, storageEntry)
if err != nil {
return fmt.Errorf("failed to write to storage: %w", err)
}
c.asyncIndex.commitOne(storageEntry, durable)
asyncReserved = false
if c.metrics != nil {
c.metrics.VectorInserts.Inc()
}
return nil
}

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.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Build-blocking: asyncIndexStorage is missing the durable-range methods. Both async write paths call methods that the asyncIndexStorage type does not declare (Lint typecheck failures); the methods exist on the singlefile *Collection, so the interface/type must be extended (or storage retyped).

  • libravdb/collection.go#L846-L857: c.asyncIndex.storage.InsertDurableRange is undefined — add InsertDurableRange(context.Context, *index.VectorEntry) (storage.DurableRange, error) to asyncIndexStorage.
  • libravdb/collection.go#L1006-L1022: c.asyncIndex.storage.InsertBatchDurableRange is undefined — add InsertBatchDurableRange(context.Context, []*index.VectorEntry) (storage.DurableRange, error) to asyncIndexStorage.
🧰 Tools
🪛 GitHub Check: Lint

[failure] 847-847:
c.asyncIndex.storage.InsertDurableRange undefined (type asyncIndexStorage has no field or method InsertDurableRange) (typecheck)

📍 Affects 1 file
  • libravdb/collection.go#L846-L857 (this comment)
  • libravdb/collection.go#L1006-L1022
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libravdb/collection.go` around lines 846 - 857, Add both durable-range
methods to the asyncIndexStorage interface/type used by c.asyncIndex.storage:
InsertDurableRange(context.Context, *index.VectorEntry) (storage.DurableRange,
error) for the path at libravdb/collection.go:846-857, and
InsertBatchDurableRange(context.Context, []*index.VectorEntry)
(storage.DurableRange, error) for the path at libravdb/collection.go:1006-1022.
Ensure the concrete storage implementation satisfies these methods, or retype
storage to the existing implementation that provides them.

Source: Pipeline failures

Comment thread libravdb/collection.go Outdated
Comment thread libravdb/database.go Outdated
Comment thread libravdb/index_persistence.go
Comment thread libravdb/migrate.go Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/index/hnsw/hnsw.go (1)

447-464: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Mark nodes in-flight before publishing their ID or registry entry.

insertSingleMetadata exposes the ID, node, and size before Lines 461-464 register it as in-flight. A concurrent delete can remove the ID as “stale” before h.nodes.Set, or retire the node before insertNode uses its pointer. Move in-flight registration before the first publication and unwind it on every metadata error and first-node return.

Add a barrier-based concurrent insert/delete regression test for this publication window.

Also applies to: 579-605

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/index/hnsw/hnsw.go` around lines 447 - 464, The Insert flow must
register each newly allocated node as in-flight before insertSingleMetadata
publishes its ID, node, or size. Restructure Insert and the corresponding later
insertion path around insertSingleMetadata, h.inFlightNodes, and h.nodes.Set so
registration occurs before publication, and ensure every metadata error and
first-node return unwinds the registration. Add a barrier-based concurrent
insert/delete regression test covering this publication window.
🧹 Nitpick comments (1)
internal/index/hnsw/reclamation_test.go (1)

118-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise both raw stores in the concurrent reclamation test.

This is the only test overlapping searches with delete/reinsert churn, but it omits the new in-memory pointer recycler. Table-test both RawVectorStoreMemory and RawVectorStoreSlabby.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/index/hnsw/reclamation_test.go` around lines 118 - 120, Update
TestReclamationConcurrentSearchDeleteReinsert to run as a table-driven test
covering both RawVectorStoreMemory and RawVectorStoreSlabby. Create a separate
reclamation test index for each store within the subtest and preserve the
existing concurrent search/delete-reinsert assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/index/hnsw/delete.go`:
- Around line 509-531: The guards in retireStoredVectorAt must not skip cleanup
when h.provider is configured; remove the provider condition while retaining the
existing nil, store, and sentinel checks, so valid slots are retired during
published-node deletion. Apply the corresponding provider-independent slot
release in internal/index/hnsw/hnsw.go lines 1986-2003 for the unpublished-node
rollback path.

In `@internal/index/hnsw/reclamation_test.go`:
- Around line 160-168: Update the mutation loop around index.Delete and
index.Insert to store any error and break instead of calling t.Fatalf
immediately. After the loop, cancel the context and wait for the search workers
to finish before reporting the saved error, ensuring index.Close runs only after
workers have stopped.

---

Outside diff comments:
In `@internal/index/hnsw/hnsw.go`:
- Around line 447-464: The Insert flow must register each newly allocated node
as in-flight before insertSingleMetadata publishes its ID, node, or size.
Restructure Insert and the corresponding later insertion path around
insertSingleMetadata, h.inFlightNodes, and h.nodes.Set so registration occurs
before publication, and ensure every metadata error and first-node return
unwinds the registration. Add a barrier-based concurrent insert/delete
regression test covering this publication window.

---

Nitpick comments:
In `@internal/index/hnsw/reclamation_test.go`:
- Around line 118-120: Update TestReclamationConcurrentSearchDeleteReinsert to
run as a table-driven test covering both RawVectorStoreMemory and
RawVectorStoreSlabby. Create a separate reclamation test index for each store
within the subtest and preserve the existing concurrent search/delete-reinsert
assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6254baf4-3c05-489c-8d7e-38409dde2656

📥 Commits

Reviewing files that changed from the base of the PR and between d51eed6 and 502577c.

📒 Files selected for processing (23)
  • internal/index/hnsw/delete.go
  • internal/index/hnsw/global_state.go
  • internal/index/hnsw/hnsw.go
  • internal/index/hnsw/hnsw_test.go
  • internal/index/hnsw/insert.go
  • internal/index/hnsw/neighbors.go
  • internal/index/hnsw/reclamation.go
  • internal/index/hnsw/reclamation_test.go
  • internal/index/hnsw/repair.go
  • internal/index/hnsw/search.go
  • internal/index/hnsw/vector_store.go
  • internal/index/hnsw/vector_store_slabby.go
  • internal/index/hnsw/vector_store_slabby_test.go
  • internal/index/hnsw/vector_store_test.go
  • internal/util/distance.go
  • internal/util/distance_test.go
  • internal/util/simd/stub_arm64.go
  • libravdb/async_index_test.go
  • libravdb/collection.go
  • libravdb/database.go
  • libravdb/index_persistence.go
  • libravdb/migrate.go
  • libravdb/migrate_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • internal/util/simd/stub_arm64.go
  • internal/index/hnsw/vector_store_slabby_test.go
  • libravdb/migrate.go
  • internal/index/hnsw/global_state.go
  • libravdb/async_index_test.go
  • internal/util/distance.go
  • libravdb/database.go
  • internal/index/hnsw/insert.go
  • internal/index/hnsw/repair.go
  • internal/index/hnsw/vector_store_slabby.go
  • internal/index/hnsw/hnsw_test.go
  • internal/index/hnsw/search.go
  • libravdb/index_persistence.go
  • libravdb/collection.go
  • internal/index/hnsw/neighbors.go

Comment thread internal/index/hnsw/delete.go
Comment thread internal/index/hnsw/reclamation_test.go
@xDarkicex
xDarkicex merged commit 75dc642 into master Jul 14, 2026
4 checks passed
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