Skip to content

Core: Bound Idempotency-Key retries to the advertised key lifetime - #18070

Draft
HotSushi wants to merge 4 commits into
apache:mainfrom
HotSushi:idempotency-key-expiry
Draft

Core: Bound Idempotency-Key retries to the advertised key lifetime#18070
HotSushi wants to merge 4 commits into
apache:mainfrom
HotSushi:idempotency-key-expiry

Conversation

@HotSushi

Copy link
Copy Markdown
Contributor

What / Why

Follow-up to #17947 (client-side Idempotency-Key retries). #17947 auto-retries keyed POSTs on retriable errors but never honors the server-advertised key lifetime, so a slow retry can fire after the key has expired — when the server no longer dedupes — risking a duplicate mutation.

The REST spec obligation on the client:

Clients SHOULD NOT reuse an Idempotency-Key after this window elapses.

Change

  • ExponentialHttpRequestRetryStrategy now accepts the advertised key lifetime and declines a retry for a keyed request once the elapsed time since the first attempt plus the next retry interval would reach the lifetime window. Enforced on both the response and network-exception retry paths; non-keyed / idempotent requests are unaffected.
  • The lifetime advertised in GET /v1/config (ISO-8601, e.g. PT30M) is plumbed to the HTTP client through the client properties and parsed once in HTTPClient; a malformed value is ignored (disables enforcement) rather than failing client construction.
  • Extracted a shared isRetrySafe(...) predicate to remove duplication between the two retry paths (addresses a review nit; also clarifies that "retry-safe" is not the same as strictly idempotent).

Tests

Added unit tests in TestExponentialHttpRequestRetryStrategy: keyed retry abandoned once the window is exceeded (both response and network-exception paths), keyed retry honored within the window and when no lifetime is advertised, and an idempotent GET unaffected by the key lifetime. :iceberg-core rest.* tests pass locally.

Notes

ExponentialHttpRequestRetryStrategy only applied the idempotent retriable
status codes (408, 500, 502, 503, 504) when the HTTP method itself was
idempotent. Mutation endpoints are POST, so a request that already carries
an Idempotency-Key header - which the server guarantees is safe to replay -
was not retried on 502/504 or on 503 without a Retry-After header.

Treat a request carrying the Idempotency-Key header as retry-safe for these
codes, in both the response and network-exception retry paths. Requests
without the header keep the previous conservative behavior.
- Update testIdempotentCreateReplayAfterSimulated503: the client now
  auto-retries a keyed POST on 503 and the server replays the finalized
  200, so the call succeeds transparently. Assert success directly
  instead of expecting a thrown 503 followed by a manual retry.
- Add testIdempotentCreateRetryCarriesSameKey pinning the invariant that
  every transport attempt of a retried keyed POST carries the identical
  Idempotency-Key.
- Clarify the retry-safety comment in ExponentialHttpRequestRetryStrategy
  (idempotent method OR Idempotency-Key header), which no longer means
  strict HTTP idempotency.
- Import CopyOnWriteArrayList instead of using its fully-qualified name.
- Drop the redundant equality assertion in testIdempotentCreateRetryCarriesSameKey;
  the filter already guarantees equality, so hasSize(2) is the real guard.
When a server advertises an Idempotency-Key lifetime (e.g. PT30M) in the
config response, a keyed retry that fires after the window has elapsed would be
treated as a brand-new mutation by the server, defeating the deduplication
guarantee. This change tracks the wall-clock time of the first attempt in the
HTTP context and declines a retry whose next scheduled interval would land at or
after the advertised lifetime. The lifetime string is plumbed from
ConfigResponse through RESTSessionCatalog into the HTTPClient properties, and
from there into ExponentialHttpRequestRetryStrategy via a new two-argument
constructor; the single-argument constructor delegates to it with a null
lifetime, preserving backward compatibility.
@github-actions github-actions Bot added the core label Sep 11, 2026
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.

1 participant