Skip to content

Repros 747 - #752

Closed
jshook wants to merge 51 commits into
deferred-batch-writerfrom
repros-747
Closed

jshook wants to merge 51 commits into
deferred-batch-writerfrom
repros-747

Conversation

@jshook

@jshook jshook commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Review findings: repros and fixes

These were reviewed at ec42a94b. The repro tests and proposed fixes are on repros-747, one commit each:

# Issue Repro Fix
1 Grid with the default sequential writer serializes deferred features on one thread 16772ea7 c7470e3d
2 An interrupted or failed parallel write runs to completion 935d02fa 68afc073
3 Dead padding code in NodeRecordTask (hygiene only) — not changed
4 TestBufferedGraphCompatibility (from #744) fails intermittently under load existing test 7b467913

1. Default writer regression. Grid now always defers NVQ/FP encoding to the final write. parallelBuild defaults to false and BenchYAML has no way to change it. So plain BenchYAML and CI runs use the sequential writer, which does all of that encoding on one thread after cleanup. On Cohere-1M, build+write went from 74–76 s to 100–104 s, and on OpenAI-3072-1M from 166 s to 264 s. The PR's measurements all enable the parallel writer, so they don't show this. The fix defers only when the parallel writer is in use. Otherwise Grid encodes during insertion and calls writeFeaturesInline, as before. With the fix, Cohere-1M takes 72.8 s. The docs and release note are updated to match.

2. Interrupt or failure doesn't stop the write. awaitTasks correctly joins every task before the channel and suppliers are released, but nothing tells the tasks to stop. An interrupted or failed write still serializes every remaining record before it reports. The repro interrupts after 50 records: it stops 3.2 s later, after supplying 6000 of 6000 records. The fix adds a stop flag that awaitTasks sets on an interrupt or the first failure. Tasks check it before each record, and every task is still joined. Future.cancel is not used, because it would break the join.

3. Dead padding. payloadBytes always equals recordSize, so padding is always 0 and the padding writes never run. Remove the padding code, or replace it with an assertion.

4. Flaky #744 test. The test builds the graph on a 3-thread FJP, so the topology depends on thread timing. Under load, one BQ/DOT_PRODUCT/refine case occasionally misses its self-match. The fix builds every graph with ForkJoinPool(1). It also builds a reference graph from resident vectors and asserts that the adjacency is identical at every level. That checks directly that buffered vectors don't get clobbered, which is the test's purpose. The change touches only the test file, so it can be cherry-picked onto #744.

With the three fixes, the full native suite (-Djvector.vectorization_provider=native) passes: 429 tests, 0 failures, 2 skipped.

tlwillke and others added 30 commits October 12, 2026 14:51
…preload

BaseVectorLoading.forBenchmark(MultiConfig) returns PRELOAD for configs with
search or construction compression None even when -Djvector.dataset_loader=buffered
is set, although the docs, release note and PR description call the property an
explicit override. Fails at 87bb453 (PR #744).
jshook and others added 21 commits October 12, 2026 14:51
…er comparison

For a value-shared source, randomAccessScoreProvider's score functions read both
the anchor and the compared node on every similarityTo call: 2000 source reads
for 1000 comparisons against one anchor. Fails at 87bb453 (PR #744).
Storage.close() clears point readers' channels and buffers without taking their
leases, so in-flight point reads fail with NullPointerException and can reopen a
channel after close. Timing-dependent; fails within a few rounds here at
87bb453 (PR #744).
forBenchmark(MultiConfig) now returns an explicitly set jvector.dataset_loader
as-is, so buffered applies even to uncompressed-construction and exact-search
configurations, matching the documented explicit override. Routing still
applies when buffered input comes from the YAML useDatasetPrefetching flag.
Fixes TestDatasetLoaderOverride.
randomAccessScoreProvider's value-shared path re-read node1 on every
comparison, doubling source reads in full-precision builds from a file-backed
source. The anchor is now copied on first use and kept by the score function:
creation still reads nothing, the copy can't be overwritten by later reads
through the same views, and each comparison reads only node2.
Fixes TestBuildScoreProviderAnchorReads.
Storage.close() cleared each point reader's channel and buffer without taking
its lease, so a concurrent point read could hit a null buffer or reopen a
channel that was never closed. close() now takes each reader's lease before
closing its channel, and readPointInto checks for closure after taking a
lease, so later point reads fail with IllegalStateException.
Fixes TestFvecCloseDuringPointReads.
…n one thread

Grid now supplies NVQ/FP features at the final write for every run, but only the
parallel writer calls those suppliers from several threads. BenchYAML's default
(sequential) writer calls them on one thread, so all final-write vector reads and
NVQ encoding run single-threaded after construction. On Cohere-1M build+write
went from 74-76 s to 100-104 s, and on OpenAI-3072-1M from 165 s to 264 s.
Fails at ec42a94 (PR #747).
ParallelGraphWriter.awaitTasks joins every task but never stops them, so an
interrupted or already-failed write still serializes every remaining record
before reporting. A 3 s write interrupted after 50 records took 3.2 s to stop
and supplied all 6000 records. Fails at ec42a94 (PR #747).
Grid moved NVQ/FP feature encoding into the final write for every run, but the
sequential writer (BenchYAML's default) calls final suppliers on one thread.
Grid now defers features only when every writer is an
OnDiskParallelGraphIndexWriter; otherwise it encodes them on the insertion
threads and writes them with writeFeaturesInline, as before #747. The docs and
release note say so.
Fixes TestGridDeferredFeatureParallelism.
awaitTasks joined every task but never stopped them, so an interrupted or failed
write serialized every remaining record before reporting. writeL0Records now
shares a stop flag with its tasks: awaitTasks sets it on interruption or the
first task failure, unstarted tasks return at once, and running tasks stop
before their next record. Every task is still joined before the channel and
suppliers are released. NodeRecordTask keeps its existing constructor.
Fixes TestParallelWriterInterrupt.
The test built each graph on a 3-thread ForkJoinPool, so insertion order,
hierarchy-level draws and refinement cleanup varied with thread timing. Its
self-match check failed intermittently under full-suite load for the BQ,
768-dimension, DOT_PRODUCT, refined configuration (node 5 missing from its own
top 10).

Graphs are now built through builder.build(source) with single-worker
executors, and the file-backed graph's adjacency must equal a resident-built
reference graph at every level. That checks the test's purpose (a shared file
source must not overwrite insertion or diversity queries) edge for edge. The
self-match check is kept and is now deterministic for the fixed data.
@github-actions

Copy link
Copy Markdown
Contributor

Before you submit for review:

  • Does your PR follow guidelines from CONTRIBUTIONS.md?
  • Did you summarize what this PR does clearly and concisely?
  • Did you include performance data for changes which may be performance impacting?
  • Did you include useful docs for any user-facing changes or features?
  • Did you include useful javadocs for developer oriented changes, explaining new concepts or key changes?
  • Did you rebase your branch onto the latest main for regression testing and PR submission?
  • Did you trigger regression testing via Run Bench Main and review results?
  • Did you adhere to the code formatting guidelines (TBD)
  • Did you group your changes for easy review, providing meaningful descriptions for each commit?
  • Did you ensure that all files contain the correct copyright header?
  • Did you add documentation for this feature to the release notes directory?

If you did not complete any of these, then please explain below.

@tlwillke

Copy link
Copy Markdown
Collaborator

Thanks for your findings @jshook . I've added modified versions of your 5 commits to #747 . Please continue your review there. I am closing this repros PR.

@tlwillke tlwillke closed this Oct 13, 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.

2 participants