Federico Mariani created CAMEL-26662:
----------------------------------------
Summary: camel-aws2-s3 - consumer gets every object of a poll
before routing the first, and ignoreBody=true still downloads them
Key: CAMEL-26662
URL: https://issues.apache.org/jira/browse/CAMEL-26662
Project: Camel
Issue Type: Improvement
Components: camel-aws2-s3
Affects Versions: 4.18.5, 4.23.0, 4.22.2
Reporter: Federico Mariani
h3. Problem
* *Every object of a poll is got before the first one is routed.*
{{createExchanges}} calls GetObject for each listed key
({{components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Consumer.java}}
line 298 on main, commit 7978db3d42dd). With the default {{includeBody=true}},
it also reads each body into a byte[] (line 569). Only then does {{poll()}}
call {{processBatch}} (line 207).
** The heap of a poll holds every body of the batch (default
{{maxMessagesPerPoll=10}}), and the first exchange waits for the last download.
** With {{includeBody=false}}, every stream of the poll stays open, each
holding a pooled connection, while the earlier exchanges are routed.
** Since SDK 2.32.32, a {{ResponseInputStream}} that is not read within 60 s is
aborted. So a later exchange of the poll fails once the earlier ones together
took longer than 60 s.
** With {{maxMessagesPerPoll}} above 50, the pool runs out: that is CAMEL-26661.
* *ignoreBody=true still downloads every object.*
** The consumer does a GetObject and closes the stream without reading it
(lines 606-607). The producer {{getObject}} with ignoreBody does the same
({{AWS2S3Producer.java}} lines 685-686 and 732-733).
** With the Apache HTTP client Camel uses by default, {{close()}} reads the
rest of the body so that the connection can be reused (the SDK javadoc of
{{ResponseInputStream}}; httpclient5 5.6.4
{{ResponseEntityProxy.streamClosed}}). So the whole object is transferred
anyway.
* The docs say otherwise:
{{components/camel-aws/camel-aws2-s3/src/main/docs/aws2-s3-consumer-examples.adoc}}
line 228 reads "Use {{ignoreBody: true}} to get only the metadata."
h3. How to reproduce
An in-process fake S3 (JDK {{HttpServer}}) is used with the client Camel builds
(Apache5, httpclient5 5.6.4). Measured on main:
||Case||Observed||
|10 objects, maxMessagesPerPoll=10, includeBody=true|all 10 GetObject answered
completely, then the 10 exchanges routed|
|10 objects of 8 MiB, includeBody=false|all 10 GetObject sent before the first
exchange is routed; at that point 9 of the 10 responses are still being sent|
|1 object of 64 MiB, ignoreBody=true (consumer, and producer
getObject)|67,108,864 of 67,108,864 bytes sent, body null|
|Control: the same object, SDK ResponseInputStream.abort()|0.6 to 1.8 MB sent
before the connection is closed|
|2 objects, includeBody=false, the route takes 61 s on the first|the second
exchange fails with IOException: Attempted read on closed stream|
h3. Proposed change
* Move the GetObject into {{processBatch}}: list in {{poll()}}, then get each
listed object right before its exchange is routed. At most the objects of the
exchanges in flight are then in memory or open.
* Keep what CAMEL-25558 added:
** an object that no longer exists is skipped;
** an object that cannot be got is skipped and given to the exception handler;
** the poll fails only when none of the objects could be got.
* {{CamelBatchSize}} and {{CamelBatchComplete}} are known only after the gets,
so it must be decided what they count. This needs care.
* For ignoreBody, use HeadObject instead of GetObject. HeadObject returns every
field the consumer maps to headers (ETag, VersionId, ContentType,
ContentLength, ContentEncoding, ContentDisposition, CacheControl,
ServerSideEncryption, Expiration, ReplicationStatus, StorageClass, Metadata,
LastModified; S3 service model), and it needs the same {{s3:GetObject}}
permission.
* The alternative is {{abort()}} instead of {{close()}}. It also stops the
transfer (measured above), but it closes the connection, so the next request
opens a new one.
* Fix the example at line 228 of {{aws2-s3-consumer-examples.adoc}} along with
the change.
* This also fixes CAMEL-26661 (includeBody=false with maxMessagesPerPoll above
50), and the 60 s abort of the later streams of a poll in general.
h3. Impact / upgrade
* No option and no default changes. Two changes are worth a line in the 4.24
upgrade guide:
** objects are downloaded one by one while the batch is routed, instead of all
before the first exchange;
** ignoreBody no longer downloads the objects. It sends HEAD requests, so the
request mix changes.
* 4.18.5, 4.22.2 and 4.23.0 get every object in {{createExchanges}} in the same
way. This was checked in the source; it was measured on main only.
* The {{fileName}} path (one object) and the producer {{getObject}} with
ignoreBody take the same ignoreBody change.
h3. Related
* CAMEL-26661: the camel-aws2-s3 consumer with includeBody=false and
maxMessagesPerPoll above 50 exhausts the HTTP pool. This change fixes it.
* CAMEL-25558 (commit ed9e3b7cd277): the skip handling that must be kept.
* CAMEL-16727 added ignoreBody "for metadata only". CAMEL-14929 added the
stream body with includeBody=false.
_Claude Code on behalf of Federico Mariani_
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)