Skip to content

Report per-record errors in batch_operate instead of aborting the batch#19

Open
zuchmanski wants to merge 1 commit into
v5-developfrom
fix/batch-operate-per-record-result-code
Open

Report per-record errors in batch_operate instead of aborting the batch#19
zuchmanski wants to merge 1 commit into
v5-developfrom
fix/batch-operate-per-record-result-code

Conversation

@zuchmanski

Copy link
Copy Markdown

Problem

Client#batch_operate (with BatchWrite / BatchUDF / BatchDelete / BatchRead records) raised Aerospike::Exceptions::Aerospike and abandoned the entire batch as soon as one record came back with a non-OK result code (e.g. RECORD_TOO_BIG, PARAMETER_ERROR, BIN_TYPE_ERROR).

As a result:

  1. A single bad record failed every other record in the batch — successful writes were lost / unreadable.
  2. The caller couldn't do partial-failure handling: the exception carried no per-record context and BatchRecord#result_code was never populated for the failing (or any subsequent) record.

Per-record failures should be reported on each BatchRecord#result_code, leaving successful records applied/returned and the call succeeding — the point of a batch, and how the Java/C/Go clients behave (respond_all_keys).

Root cause

MultiCommand#parse_group raised on any per-record code that wasn't OK / KEY_NOT_FOUND_ERROR / FILTERED_OUT, before parse_row recorded the code. BatchOperateCommand#parse_row already stores the per-record code, but never ran on the failing record because parse_group raised first; the exception then propagated out via threads.each(&:join) in execute_batch_operate_commands.

Fix

  • Extract the raise decision from MultiCommand#parse_group into an overridable handle_result_code(result_code). Base behavior is unchanged (raises on unexpected codes).
  • Override handle_result_code as a no-op in BatchOperateCommand, so non-OK codes fall through to parse_row and are captured on each BatchRecord#result_code. The call returns normally and the caller inspects status per record.

Scan/query (StreamCommand, which has its own parse_group) and legacy batch_get (BatchIndexCommand) keep the existing raising behavior.

Testing

Added a spec that mixes a healthy BatchWrite with one that deterministically fails (Operation.add on a pre-existing string bin → BIN_TYPE_ERROR), asserting the call doesn't raise, the good record is OK with its record set, the bad record carries BIN_TYPE_ERROR, and the good write is durably readable.

Verified against a live Aerospike 7.1 server:

  • New test fails without the fix (raises out of batch_operate) and passes with it.
  • batch_operate_spec.rb + batch_spec.rb: 33 examples, 0 failures (legacy batch_get raising path intact).

🤖 Generated with Claude Code

MultiCommand#parse_group raised Exceptions::Aerospike on any per-record
result code other than OK/KEY_NOT_FOUND_ERROR/FILTERED_OUT, before
parse_row could record the code. For batch_operate this meant a single
bad record (e.g. RECORD_TOO_BIG, BIN_TYPE_ERROR) aborted the entire
batch: successful writes were lost and no per-record context reached the
caller.

Extract the raise decision into an overridable handle_result_code and
override it as a no-op in BatchOperateCommand, so non-OK codes fall
through to parse_row and are captured on each BatchRecord#result_code.
The call now returns normally and the caller inspects status per record,
matching respond_all_keys semantics in the Java/C/Go clients.

Scan/query (StreamCommand) and legacy batch_get (BatchIndexCommand) keep
the existing raising behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zuchmanski
zuchmanski force-pushed the fix/batch-operate-per-record-result-code branch from bc82c94 to a2fbf02 Compare July 3, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant