Propagate storage HTTP client settings to S3FileIO for table operations#5004
Conversation
|
|
||
| // Apply polaris.storage.* HTTP client settings to Iceberg S3FileIO (and other AWS FileIOs). | ||
| // Previously only applied to the STS client pool. | ||
| if (s3AccessConfig != null) { |
There was a problem hiding this comment.
LGTM overall. 👀
The null guard here has the same effect as above.. the new HTTP settings logic gets skipped for anyone using the no-arg constructor (WasbTranslatingFileIOFactory, MeasuredFileIOFactory, and the test spy all go through that path). This means the fix is really only active for the CDI-created "default" instance.
It might be worth clarifying in the code comment that this fix targets the CDI "default" path specifically - not every place FileIOFactory gets instantiated. Just helps set the right expectations and avoids someone assuming it's a global fix.
There was a problem hiding this comment.
agreed. updated the comments
Wire S3AccessConfig into WasbTranslatingFileIOFactory so production non-default factories also propagate polaris.storage.* HTTP settings to S3FileIO. Clarify that the no-arg DefaultFileIOFactory path is for tests and fixtures only.
|
LGTM.. Thanks for the quick follow-up! cc: @dimas-b |
|
Are we fixing #4704 with this PR? |
| if (s3AccessConfig != null) { | ||
| s3AccessConfig | ||
| .maxHttpConnections() | ||
| .ifPresent(v -> props.put("http-client.apache.max-connections", String.valueOf(v))); |
There was a problem hiding this comment.
This will configure the Http Client inside the FileIO with the same parameters as the Http Client used by our StsClientsPool. This is good, but it will be another Http Client instance.
However, ideally we should probably reuse the same client that is produced by sdkHttpClient().
Would that be possible?
Cf.
There was a problem hiding this comment.
Possible, but not via properties alone — Iceberg only configures HTTP clients from http-client.apache.* strings; it does not accept an external SdkHttpClient.
Reusing the singleton would need a custom AwsClientFactory (or custom S3 client supplier) wired to ServiceProducers.sdkHttpClient(), plus lifecycle care so FileIO close does not dispose the shared client (httpClient(instance) is caller-owned).
Trade-off: STS and S3 FileIO would then compete for one connection pool under load. This PR keeps separate pools with the same polaris.storage.* tuning (Iceberg already shares among FileIOs via HttpClientCache). Suggest treating full reuse as a follow-up unless you prefer expanding this PR.
There was a problem hiding this comment.
thanks for checking! on reusing sdkHttpClient(), Iceberg’s FileIO path only takes http-client.apache.* properties, so we can’t pass the existing client through...sharing would need a custom AwsClientFactory (or S3 client supplier) wired to the singleton, plus care so FileIO close doesn’t dispose it.
also STS and S3 FileIO would then share one connection pool and could contend under load.
here the same polaris.storage.* settings to both I did, but kept separate clients (Iceberg still shares among FileIOs via HttpClientCache).
happy to follow up on true reuse. wdyt?
There was a problem hiding this comment.
Fair enough. Thanks for the info 👍
db64ba2 to
f5b1118
Compare
|
|
||
| // Get subcoped creds | ||
| properties = new HashMap<>(properties); | ||
| Map<String, String> props = new HashMap<>(properties); |
There was a problem hiding this comment.
I'd rather rename the method parameter as opposed to the local var to minimize non-functional changes below.
Why do we need to use a different variable for this anyway?
There was a problem hiding this comment.
sure, renamed the parameter to tableProperties and kept the local as properties so the rest of the method stays the same.
| // Apply polaris.storage.* HTTP client settings to Iceberg S3FileIO (and other AWS FileIOs). | ||
| // Previously only applied to the STS client pool. Skipped when constructed without config | ||
| // (tests / fixtures); production CDI paths always inject S3AccessConfig. | ||
| if (s3AccessConfig != null) { |
There was a problem hiding this comment.
It should be easy to add @PolarisImmutable to S3AccessConfig and create a default (empty) instance from it to avoid null config.
Only one constructor should be sufficient for all use cases, I guess... WDYT?
There was a problem hiding this comment.
yes, makes sense.. added @PolarisImmutable on S3AccessConfig with S3AccessConfig.empty(), dropped the no-arg constructor, and always take a config.. tests/fixtures use S3AccessConfig.empty() (empty optionals → no HTTP props applied)
| s3AccessConfig | ||
| .readTimeout() | ||
| .ifPresent( | ||
| d -> props.put("http-client.apache.socket-timeout-ms", String.valueOf(d.toMillis()))); |
There was a problem hiding this comment.
Could we reuse property names from Iceberg's HttpClientProperties?
There was a problem hiding this comment.
sure, done. thanks!
Address review feedback: use S3AccessConfig.empty() via @PolarisImmutable instead of null, rename loadFileIO tableProperties param, and reuse Iceberg HttpClientProperties constants. Keep regenerated polaris.storage config docs in the same commit for CI.
f5b1118 to
6f0fd02
Compare
|
Targeting Jul 13 for the merge date, unless fresh comments appear. |
nandorKollar
left a comment
There was a problem hiding this comment.
Overall the change looks good, my only concern is that now S3-specific configs are leaking into the otherwise storage-agnostic DefaultFileIOFactory. I doubt that this causes real-word problems, though look a bit awkward. Are these configs applied now on Azure and GCP storage too? What happens, when S3 is not used at all?
I noticed that too. My reading that such a leak existed before too, but was realized in untyped FileIO property bags. This PR merely makes it more visible by using a strongly typed config class. All in all, I think it's fine for now. |
The S3 config will be "empty". It is ultimately produced by Quarkus at the server start time (once per instance) and should not cause too much overhead. |
Thanks @dimas-b for the explanation. Agree, this is fine for now. |
|
@iprithv would you mind resolve the merge conflict on the changelog? |
|
I guess someone changed CHANGELOG on |

polaris.storage.max-http-connections(and the related read/connect/acquisition/idle/TTL timeouts andexpect-continuesetting) had no effect on the HTTP clients actually used for table operations.Users who set
polaris.storage.max-http-connections=128(plus higherpolaris.tasks.max-concurrent-tasks) still observed the AWS SDK default of 50 connections. This caused connection pool exhaustion ("Timeout waiting for connection from pool") during table creation/commits, manifest cleanup, data file operations, and background tasks.Fixes #4704
The settings were only wired into the
SdkHttpClientproduced forStsClientsPool(used by credential vending /AwsCredentialsStorageIntegration).All real table I/O goes through Iceberg
S3FileIO:DefaultFileIOFactory.loadFileIO→CatalogUtil.loadFileIO(..., "org.apache.iceberg.aws.s3.S3FileIO", properties)→ IcebergS3FileIO/DefaultAwsClientFactory→HttpClientProperties.applyHttpClientConfigurations→HttpClientCache→ Apache HTTP client →ExceptionMappingFileIO→ catalog commits / task cleanupNo
http-client.apache.*properties were ever placed into the map passed to Iceberg, so it always built clients with SDK defaults (visible via Arthas asMaxConnections=50inside theManagedHttpClient).