fix(batch): require both counts unchanged before the idle flush (#2105) - #2109
Open
Anai-Guo wants to merge 1 commit into
Open
fix(batch): require both counts unchanged before the idle flush (#2105)#2109Anai-Guo wants to merge 1 commit into
Anai-Guo wants to merge 1 commit into
Conversation
The wait loop in the batching threads breaks out early when a second has
passed and nothing new arrived, but the guard ORs the two "unchanged"
checks:
if time.time() - start >= 1 and (
len_o == len(self.__batch_objects) or len_r == len(self.__batch_references)
):
In an objects-only batch -- what `add_object` without `add_reference`
produces -- `len_r` and `len(self.__batch_references)` are both always 0,
so the right operand is always True and the whole guard collapses to
`if time.time() - start >= 1`. The loop then stops waiting on the first
tick past one second no matter how fast objects are still streaming in,
so the batch is flushed well short of its configured size. A
references-only batch has the mirror-image problem.
The comment above the break says "no new objects were added in the last
second", i.e. the batch is idle -- which is true only when *both* counts
are unchanged. Switch `or` to `and` at the three places that share this
loop shape (sync.py, async_.py, base.py).
Fixes weaviate#2105
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Infrastructure as Code | View in Orca | ||
| SAST | View in Orca | ||
| Secrets | View in Orca | ||
| Vulnerabilities | View in Orca |
|
To avoid any confusion in the future about your contribution to Weaviate, we work with a Contributor License Agreement. If you agree, you can simply add a comment to this PR that you agree with the CLA so that we can merge. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2105.
The bug
The wait loop that lets a batch accumulate before it is sent breaks out early once a second has passed and nothing new has arrived. The guard for "nothing new has arrived" ORs the two unchanged-checks:
In an objects-only batch — what
add_objectwithoutadd_referenceproduces, i.e. the common case —len_randlen(self.__batch_references)are both permanently0, so the right operand is alwaysTrueand the whole condition collapses toif time.time() - start >= 1. The loop then stops waiting on the first tick past one second regardless of how fast objects are still streaming in, and the batch is sent well short of its configured size. A references-only batch has the mirror-image problem.The comment on the
breakstates the intent — the batch is idle — which holds only when both counts are unchanged.The fix
or→andat the three places that share this loop shape:weaviate/collections/batch/sync.py:180weaviate/collections/batch/async_.py:228weaviate/collections/batch/base.py:437Measured effect
The loop needs a live connection and a background thread, so I reproduced it standalone: the exact wait-loop body, a fake object queue, and a producer adding objects at a steady rate with no references. Both variants run the same producer for 8 s; the only difference is
orvsand.Same objects delivered, but
andreaches a batch size near the configured limit instead of whatever landed in the first second — 2.7× fewer round trips in the first row. The rates that matter are those where filling a batch takes longer than the one-second threshold; above that the batch fills before the guard is ever consulted and the two variants are identical, which is why the defaultbatch_size=100at high throughput does not show it.The
andform cannot stall: the outerwhilestill bounds the wait bybatch_size, and the guard still fires as soon as a tick passes with no new objects.On tests
I did not add one. The guard lives inside a private background-thread loop behind a live gRPC connection, and it is keyed on wall-clock (
time.time() - start >= 1), so a test that exercises it would be timing-dependent and flaky in CI — the existingtest/collection/test_batch.pyis pure-function tests only. Happy to add one undermock_tests/if you would like it and can point me at the shape you would accept.Verified
ruff format --checkandruff checkclean with the pinnedruff 0.14.7and the repo'sruff.toml.🤖 Generated with Claude Code