Repository navigation
Conversation
__send_batch built the wholesale-failure error map with chunk-local enumerate() keys while BatchObjectReturn.__add__ merges with dict.update, so keys from separate chunks collided and failures were dropped or attributed to the wrong input object.
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. |
|
I have read and I agree with the Contributor License Agreement. |
|
Hi, CI is green (unit tests + lint on the branch) and the CLA comment is on this PR. The failure being fixed: |
|
This PR already is the construction-side fix: Offsetting at the reader can't work for objects. Three producers already key globally and On the other helpers with the same shape: after this change there are none for objects. For references the chunk-local shape is deliberate, because |
Problem
Client-side batching reports fewer object failures than actually occur, and can attribute them
to the wrong input rows.
The wholesale-failure handler in
_BatchBase.__send_batchkeyed its error map by the positioninside the chunk being sent:
but
BatchObjectReturn.__add__merges chunk results with a plaindict.updateand does nore-keying (
classes/batch.py:231). Withbatch_sizesmaller than the number of objects, everychunk contributes keys
0..len(chunk)-1, so later chunks overwrite earlier ones.This contradicts the documented contract on
BatchObjectReturn("The keys of the
errorsanduuidsdictionaries will always be equivalent to theoriginal_indexof the objects as you added them to the batching loop") and every other failuresite in the same flow, which already uses the global index (
grpc_batch.py:157,sync.py:367,sync.py:410,async_.py:409,async_.py:452).Observed on
main@142d798, four objects withfixed_size(batch_size=2), both chunks rejected:The sharper variant is a successful chunk followed by a wholesale failure:
uuidskeys[0, 1]and
errorskeys[0, 1]end up describing different objects, so a caller that retries"the failed indices" re-sends rows that already landed and skips rows that did not.
Fixes #2165.
Change
BatchObject.indexis a requiredintassigned once in_add_object(base.py:826) and is notreset by
flush(), so it is always present and stable across retries. Chunks are popped underself.__lock, so no object can appear in two in-flight chunks and produce a duplicate key.Why the references sibling is untouched
The next handler down,
errors_ref = {idx: ... for idx, ref in enumerate(refs)}, looks like thesame bug and I deliberately left it alone.
BatchReferenceReturn.__add__merges with an offset(
classes/batch.py:293-299):That arithmetic expects dense, chunk-local keys: two chunks of two currently produce
{0: ref0, 1: ref1, 2: ref2, 3: ref3}. Switching it toref.indexwould yield{0, 1, 4, 5}—indices 2 and 3 would vanish and keys past the end would appear. Verified by running the two
merge paths. References do have an index problem, but it comes from elsewhere (the REST
referencespath returns sparse chunk-local keys, and the non-wholesale handlers feedalready-global keys into this offset merger), so it needs its own change rather than a symmetric
edit here.
Verification
One venv, Python 3.11.15,
-e .plusrequirements-devel.txtandrequirements-test.txt; everycommand run identically on
main@142d798as a control.python -m pytest mock_tests/ test/ -q2 failed, 556 passed, 1 skipped— both failures are the new tests558 passed, 1 skippedmock_tests/test_batch_error_indices.pydrives the real client against a mock gRPCservice, no network and no server:
test_wholesale_failed_chunks_report_every_object— asserts all four indices appear andthat each key describes the object it names, plus
len(failed_objects) == len(errors)test_failed_and_successful_chunks_do_not_share_keys— the uuid/error overlap abovetest_successful_chunks_keep_original_indices— passes on base too; it pins the conventionthe fix aligns to rather than testing the fix
python -m ruff check weaviate test mock_tests→ all checks passedpython -m ruff format --check weaviate test mock_tests→421 files already formatted(420 on base, so the new file is clean under the CI command at
.github/workflows/main.yaml:50)python -m flake8on both changed files → cleanintegration/,embedded/,journey/andtest-packageneed Docker and a realWeaviate;
pyrightis not installed in this venv. A repo-wide grep found no test asserting theold chunk-local behaviour, but those gates are CI's to judge.
One known knock-on, left alone deliberately
base.py:716-720filters the deprecatedall_responseslist by position(
enumerate(all_responses)) againstreadded_objects, which holds global-index objects. Underthe old keys that filter matched by accident; with correct keys a retried chunk keeps its
re-added objects instead of dropping them. That attribute is deprecated and emits a warning on
access, and the same mismatch already exists on the main
grpc_batch.pypath (:143isposition-indexed while
:157usesobj.index), so widening scope here would be a second,separate change. Flagging it so it is not mistaken for an oversight.
Disclosure: prepared with an AI coding assistant. Every number above comes from a command run
against
main@142d798, repeated on the unpatched tree with the identical command line so thedelta is checkable. The references analysis in particular was checked by executing both merge
strategies, not by reading them.