Federico Mariani created CAMEL-26660:
----------------------------------------
Summary: camel-aws2-kinesis - batch producer stops at a partly
failed chunk, re-sends from record 0 on retry, no 10 MiB split
Key: CAMEL-26660
URL: https://issues.apache.org/jira/browse/CAMEL-26660
Project: Camel
Issue Type: Bug
Components: camel-aws2-kinesis
Affects Versions: 4.18.5, 4.23.0, 4.22.2
Reporter: Federico Mariani
h3. Problem
When the body is an {{Iterable}}, {{Kinesis2Producer}} sends it as PutRecords
requests of at most 500 records
({{components/camel-aws/camel-aws2-kinesis/src/main/java/org/apache/camel/component/aws2/kinesis/Kinesis2Producer.java}}
lines 40 and 144 on main, commit 7978db3d42dd). The component page says
"Messages with batch size more than 500 is allowed. Producer will split them
into multiple requests."
* *A partly failed chunk stops the send.* PutRecords is not atomic: the
response can have {{FailedRecordCount > 0}} while the other records of the
request are written. The producer then throws {{RuntimeException("Failed to
send records N of M")}} (lines 100-103).
** The chunks after that one are never sent.
** The records of the earlier chunks, and the good records of that chunk, stay
written.
** The exception does not say which records were written.
{{CamelAwsKinesisRecordCount}} and {{CamelAwsKinesisFailedRecordCount}} are
only set after the loop (lines 106-108), so
{{CamelAwsKinesisFailedRecordCount}} can only ever be 0.
* *A retry re-sends from record 0.* The producer never re-sends by itself, but
it does not remember what was written. Any retry of the exchange sends the
whole Iterable again, so every record already written is written twice. A retry
is either a Camel redelivery (off by default) or a redelivery by the source of
the route (for example a transacted JMS or an SQS consumer).
* *No split by size.* Chunks are cut at 500 records only. The SDK 2.57.7
service model limits PutRecords to 500 records and 10 MiB per request (and 10
MiB per record). A bigger chunk is rejected as a whole.
h3. How to reproduce
A Mockito {{KinesisClient}} is bound as {{amazonKinesisClient}} and used by
{{from("direct:in").to("aws2-kinesis:s?amazonKinesisClient=#client")}}. The
body is a {{List}} of 1200 strings with a {{CamelAwsKinesisPartitionKey}}
header. The fake answers the 2nd PutRecords call with its first entry failed
({{FailedRecordCount=1}}) and writes every other entry. Measured on main:
||Error handler||PutRecords calls (records each)||Records written||Records
written twice||Outcome||
|default (no redelivery)|500, 500|999|0|exception, records 1000-1199 never sent|
|maximumRedeliveries(1)|500, 500, 500, 500, 200|2199|999|exchange completes|
Size: a List of 400 records of 100 KiB goes out as a single PutRecords call of
40,961,490 bytes.
h3. Proposed change
* Cut the chunks at 500 records *and* at the request size limit (data plus
partition key). Reject a single record above the record limit before anything
is sent.
* On a partial failure, retry only the failed entries of the chunk, with a
bounded number of attempts and a backoff, then go on with the next chunks. Each
response entry matches the request entry at the same position and carries an
{{ErrorCode}} when it failed (service model).
* If entries still fail, there are two options, and which one is the default
needs a decision:
** fail the exchange with an exception that tells which records were and were
not written;
** or complete the exchange with {{CamelAwsKinesisFailedRecordCount}} and the
failed entries in the message, as the SQS producer does for SendMessageBatch.
* Set {{CamelAwsKinesisRecordCount}} and {{CamelAwsKinesisFailedRecordCount}}
on failure too.
* Risk: PutRecords has no idempotency token (its input is only Records,
StreamName, StreamARN, StreamId and DryRun). A retry after a timeout, whose
outcome is unknown, can still write a record twice. This should be documented.
h3. Impact / upgrade
* An internal retry of the failed entries, with the exception kept as the
outcome, changes no default. Completing the exchange instead of throwing on a
partial failure would change a default and needs an entry in the 4.24 upgrade
guide.
* The same code is on camel-4.18.x (4.18.5), camel-4.22.x (4.22.2) and 4.23.0.
This was checked in the source; it was reproduced on main only.
* Single-record sends (non-Iterable body, PutRecord) are not affected.
h3. Related
* CAMEL-20794 (commit b52830b560fa) added the batch producer and this error
handling.
* CAMEL-24197 (commit 48a28da60ef1) added per-record partition keys.
_Claude Code on behalf of Federico Mariani_
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)