Skip to content

AWS: Cache LakeFormation vended credentials to prevent rate exceeded errors - #17789

Open
jarosmpost wants to merge 1 commit into
apache:mainfrom
jarosmpost:aws-cache-lakeformation-credentials
Open

AWS: Cache LakeFormation vended credentials to prevent rate exceeded errors#17789
jarosmpost wants to merge 1 commit into
apache:mainfrom
jarosmpost:aws-cache-lakeformation-credentials

Conversation

@jarosmpost

Copy link
Copy Markdown

Problem

When Iceberg reads LakeFormation-governed tables at high executor counts (e.g. 50× G.2X Spark workers), LakeFormationCredentialsProvider calls GetTemporaryGlueTableCredentials once per resolveCredentials() invocation. Because this is called on every S3 file open, hundreds of concurrent tasks simultaneously hit the LakeFormation API, causing:

LakeFormationException: Rate exceeded

This was observed in production: a job that succeeded at 25× G.1X (12,281 API calls) started failing after scaling to 50× G.2X. Reducing executors back below the rate limit was the only workaround.

Solution

Wrap the GetTemporaryGlueTableCredentials call in a CachedSupplier<AwsCredentials> (the same AWS SDK mechanism already used by VendedCredentialsProvider) so each executor reuses a credential for its full TTL window instead of fetching on every file open.

The staleTime is set to expiration - refreshLeadTimeMs (default 60 s), ensuring credentials are refreshed before they expire.

Two new AwsProperties constants control the behaviour:

Property Default Description
lakeformation.cache.enabled true Set to false to disable caching (e.g. for debugging)
lakeformation.cache.refresh-lead-time-ms 60000 Ms before expiry at which the cache is proactively refreshed

Caching is on by default — no config change required for existing users. The fix is contained entirely within the existing LakeFormationCredentialsProvider inner class and LakeFormationAwsClientFactory; no new classes are introduced.

Validation

Validated on a 50× G.2X Spark job against a LakeFormation-governed cross-account Iceberg table:

API calls Result
Before fix 12,281 LakeFormationException: Rate exceeded
After fix 598 Job succeeded in 3,393 s

Changes

  • AwsProperties: 2 new constants (LAKE_FORMATION_CACHE_ENABLED, LAKE_FORMATION_CACHE_REFRESH_LEAD_TIME_MS) with defaults
  • LakeFormationAwsClientFactory: read new properties in initialize(); pass to LakeFormationCredentialsProvider constructor; add CachedSupplier caching path in the inner class
  • TestLakeFormationCredentialsProvider: new test class (5 tests) covering cache hit, cache miss after stale time, and cache-disabled path

AI Disclosure

  • Model: claude-sonnet-4.6
  • Platform/Tool: OpenCode
  • Human Oversight: fully reviewed
  • Prompt Summary: Implement credential caching in LakeFormationAwsClientFactory to prevent LakeFormation rate exceeded errors at high Spark executor counts

…errors

LakeFormationCredentialsProvider called GetTemporaryGlueTableCredentials
on every resolveCredentials() invocation, which happens once per S3 file
open on Spark executors. At high DPU counts (e.g. 50x G.2X workers) this
exhausts the LakeFormation API rate limit causing job failures with
LakeFormationException: Rate exceeded.

Wrap the credential fetch in a CachedSupplier<AwsCredentials> so each
executor reuses a credential for its full TTL window. The stale time is
set to expiration minus a configurable lead time (default 60 s), matching
the pattern already used by VendedCredentialsProvider.

Two new AwsProperties constants control the behaviour:
- lakeformation.cache.enabled (default true)  - disable for debugging
- lakeformation.cache.refresh-lead-time-ms (default 60000) - how many ms
  before expiry the cache is proactively refreshed

Validated internally: reduced GetTemporaryGlueTableCredentials call count
from 12,281 (failing) to 598 (succeeding) on a 50x G.2X Spark job.

Generated-by: claude-sonnet-4.6 (OpenCode)
@github-actions github-actions Bot added the AWS label Aug 24, 2026
.credentialsProvider(
new LakeFormationCredentialsProvider(lakeFormation(), buildTableArn()))
new LakeFormationCredentialsProvider(
lakeFormation(), buildTableArn(), cacheEnabled, cacheRefreshLeadTimeMs))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[orthogonal] wow, we take props rather than dynamically inferring like VendedCredProvider ? so if there is more than > 1 what would one do ?

private final CachedSupplier<AwsCredentials> cache;

LakeFormationCredentialsProvider(LakeFormationClient lakeFormationClient, String tableArn) {
LakeFormationCredentialsProvider(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we structure this more like VendedCredProvider in terms of cache construction and modularization
https://github.com/apache/iceberg/blob/main/aws/src/main/java/org/apache/iceberg/aws/s3/VendedCredentialsProvider.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants