Hi Huaxin,

The general direction of PR 4912 looks good to me.

I'll review the exact changes in GH.

Cheers,
Dmitri.

On Tue, Jun 30, 2026 at 1:16 PM huaxin gao <[email protected]> wrote:

> Hi Dmitri, all,
>
> Following up on the early thread and Dmitri’s review on the POC (PR #4912
> <https://github.com/apache/polaris/pull/4912>): the POC explores a
> different path: stamp the key into the entity’s internalProperties in the
> same transaction as the create, so the key and the table commit atomically.
>
> That closes the record-after-success gap for operations that leave a
> surviving entity (e.g. createTable). Trade-off: keys ride on the entity hot
> path (size / write amplification on busy tables), vs. a separate store
> where retention and size are independent.
>
> POC scope today is createTable only, behind polaris.idempotency.enabled.
> I’ve added microbenchmarks on the read/write cost. Posting here since this
> is a meaningful shift from the separate-store plan, would appreciate input
> on whether we should pursue this.
>
> Thanks,
>
> Huaxin
>
> On Tue, Jun 2, 2026 at 3:00 PM huaxin gao <[email protected]> wrote:
>
> > Hi Dmitri,
> >
> > Sounds reasonable, and yes, that matches the plan.
> >
> > The idempotency persistence will be its own set of Java interfaces (a
> > standalone IdempotencyStore SPI), separate from the metastore persistence
> > interfaces, and wired and configured independently. So in principle you
> > could pair a NoSQL metastore with a JDBC idempotency store.
> >
> > I also agree with the failure-mode framing. A retry can be confirmed as
> > "previously processed" only if both the metastore change and the
> > idempotency record committed. If the metastore commits but the
> > idempotency record is lost, that retry won't be idempotent. That's the
> > same record-after-success gap we discussed under Model B, and as you note
> > it exists even with a shared persistence layer, so decoupling doesn't
> > make it worse. I think it's a reasonable assumption, and I'll document
> it.
> >
> > I'll get the PR up. Thanks again for the thorough discussion!
> >
> > Huaxin
> >
> > On Tue, Jun 2, 2026 at 12:17 PM Dmitri Bourlatchkov <[email protected]>
> > wrote:
> >
> >> Hi Huaxin,
> >>
> >> That sounds like a good plan to me. Looking forward to a PR.
> >>
> >> I think we still have time to adjust the impl. in the PR if other points
> >> are raised during review in GH or by email.
> >>
> >> With this approach my main ask regarding Persistence impl. is (repeating
> >> my
> >> previous comments) to isolate the Idempotency persistence java
> interfaces
> >> from "meta store" persistence interfaces. Ideally one should be able to
> >> combine a NoSQL MetaStore with a JDBC Idempotency persistence (in
> theory).
> >>
> >> This will naturally create a situation when a request retry can be
> >> confirmed as "previuosly processed" only if the original MetaStore
> change
> >> and the related Idempotency record insert both committed successfully. I
> >> believe this is a reasonable assumption.
> >>
> >> If we commit the MetaStore change but lose the Idempotency record, the
> >> client will not be able to re-try idempotently. However, this failure
> mode
> >> is still possible even if the MetaStore and Idempotency share the same
> >> Persistence layer.
> >>
> >> Does this sound reasonable to you?
> >>
> >> Thanks,
> >> Dmitri.
> >>
> >> On Tue, Jun 2, 2026 at 3:05 PM huaxin gao <[email protected]>
> wrote:
> >>
> >> > Hi Dmitri,
> >> >
> >> > Thanks! I think we're converging.
> >> >
> >> > On 422: you're right.  It isn't specified in the IRC spec. I'd added
> it
> >> > as a safeguard, not because the spec requires it, so I'm fine making
> it
> >> > best-effort.
> >> >
> >> > On performance: that's a fair point. For a fast-changing table, a late
> >> > retry would usually fail on stale update requirements anyway, so a
> long
> >> > retention window doesn't add much practical value there. So I agree we
> >> > can cap retention and size in practice without losing much.
> >> >
> >> > Since you're fine with a specialized persistence impl for idempotency
> >> > keys, I'd propose we go that route. It keeps keys off the entity hot
> >> path
> >> > and lets us tune retention independently of entity size, so we get the
> >> > simplicity without the write-amplification and cache cost on busy
> >> tables.
> >> >
> >> > I'll proceed with the separate idempotency persistence in the PR
> unless
> >> > others object. Thanks for working through this with me!
> >> >
> >> > Huaxin
> >> >
> >> > On Tue, Jun 2, 2026 at 11:12 AM Dmitri Bourlatchkov <[email protected]
> >
> >> > wrote:
> >> >
> >> > > Hi Huaxin,
> >> > >
> >> > > Re: Question 1 (422 responses).
> >> > >
> >> > > I do not actually see any mention of 422 responses WRT
> >> Idempotency-Key in
> >> > > the IRC spec. Did I miss it?
> >> > >
> >> > > So, if that behaviour is not strictly specified, Polaris can choose
> to
> >> > > produce those response it in any way that makes sense for the
> >> > > Polaris implementation, I think.
> >> > >
> >> > > Re: Performance.
> >> > >
> >> > > Yes, it is a valid concern. My previous suggestion for this approach
> >> was
> >> > > not a strict requirement, but a point for discussion. Let's see if
> >> other
> >> > > reviewers comment on this aspect too.
> >> > >
> >> > > Thinking about the quick-changing table use case, I believe the more
> >> > > frequent are the updates to a table the less retention period is
> >> required
> >> > > by the client. If a client takes too long to re-try and other
> clients
> >> > make
> >> > > many in-between updates, that client is unlikely to commit
> >> successfully
> >> > > anyway due to table metadata having evolved beyond its expectations.
> >> > >
> >> > > All in all, in practice it should be possible to cap the size of
> >> > > idempotency data, I think. This may be a bit at odds with the
> current
> >> IRC
> >> > > spec language regarding idempotency key retention, but it might
> still
> >> > work
> >> > > effectively.
> >> > >
> >> > > Also, UUID v7 has a time component. The server should be able to
> >> > recognize
> >> > > keys created outside the range of "recent" entries and flag those
> >> cases
> >> > > (e.g. in log) for the Polaris Admin user to note and take corrective
> >> > > actions.
> >> > >
> >> > > As for me, I'm fine with a specialized persistence impl. for
> >> idempotency
> >> > > keys too.
> >> > >
> >> > > Cheers,
> >> > > Dmitri.
> >> > >
> >> > > On Tue, Jun 2, 2026 at 1:14 PM huaxin gao <[email protected]>
> >> > wrote:
> >> > >
> >> > > > Hi Dmitri,
> >> > > >
> >> > > > You're right: DELETE is a weak case for the idempotency key, so
> I'll
> >> > > > drop that objection.
> >> > > >
> >> > > > Two reasons I agree. First, a client can handle DELETE failures on
> >> its
> >> > > > own: reload the table, see that it's gone, and stop. Second, your
> >> > > > identity point is the deeper one. The DELETE API uses the table
> >> name,
> >> > not
> >> > > > a physical table id. If a name is dropped and recreated, it points
> >> to a
> >> > > > different table over time, so the key can't give a clean guarantee
> >> for
> >> > > > DELETE anyway.
> >> > > >
> >> > > > So I'm fine scoping idempotency to operations that leave a
> surviving
> >> > > > entity: create, commit/update, register, rename. For those,
> storing
> >> the
> >> > > > key in the entity properties works, and the delete-storage problem
> >> goes
> >> > > > away.
> >> > > >
> >> > > > That leaves two things I'd want to settle before we pick
> >> > entity-property
> >> > > > storage over a separate store:
> >> > > >
> >> > > > 1. Key reuse (422). With keys in entity properties there's no
> global
> >> > > >    (realm, key) index. If a client reuses one key for a different
> >> > > >    resource, we can't detect it and return 422. Are we okay
> treating
> >> > that
> >> > > >    as best-effort?
> >> > > >
> >> > > > 2. Performance on the hot path. I want to second Yufei's concern.
> >> The
> >> > > >    number of keys per entity is roughly (write rate × retention
> >> > window),
> >> > > >    and the cost concentrates on the busiest tables. Since entity
> >> > > >    properties are serialized as one blob and rewritten on each
> >> update,
> >> > > >    every commit rewrites all the stored keys, not just the new
> one,
> >> so
> >> > a
> >> > > >    hot table pays growing write amplification on its commit path,
> >> plus
> >> > > >    larger loads and a heavier cache footprint, even for plain
> table
> >> > > >    loads. We can bound this with a tight retention cap, but that
> >> > directly
> >> > > >    shrinks the idempotency window, which is the part clients
> >> actually
> >> > > >    rely on. A separate store keeps this off the hot path and lets
> >> > > >    retention be tuned independently of entity size.
> >> > > >
> >> > > > So my main questions are: are we okay with best-effort 422, and
> how
> >> do
> >> > we
> >> > > > want to handle the hot-path cost? If both have good answers, I
> agree
> >> > > > entity-property storage is the simpler choice. WDYT?
> >> > > >
> >> > > > Thanks,
> >> > > > Huaxin
> >> > > >
> >> > > > On Tue, Jun 2, 2026 at 6:59 AM Dmitri Bourlatchkov <
> >> [email protected]>
> >> > > > wrote:
> >> > > >
> >> > > > > Hi Huaxin,
> >> > > > >
> >> > > > > Good point about handling DELETE idempotently!
> >> > > > >
> >> > > > > However, I wonder whether it is a critical use case?
> >> > > > >
> >> > > > > Do you expect DELETE to benefit a lot from the Idempotency Key?
> >> > > > >
> >> > > > > I'd think it should be fairly straightforward for the client to
> >> > reload
> >> > > > the
> >> > > > > table to be deleted in case of failures, discover that it is
> gone,
> >> > and
> >> > > > not
> >> > > > > retry. WDYT?
> >> > > > >
> >> > > > > There's still the question of whether the client is deleting the
> >> > table
> >> > > it
> >> > > > > actually intends to delete. Another client could delete the
> >> current
> >> > > table
> >> > > > > and create a new table under the same name while the first
> client
> >> is
> >> > > > > "deliberating". The IRC API does not provide for unique table
> >> > > > > identification in DELETE operations, as far as I know. The
> >> operation
> >> > is
> >> > > > > invoked simply on the name, which can map to different physical
> >> > tables
> >> > > at
> >> > > > > different times. Adding Idempotency Keys does not help in this
> >> > > context, I
> >> > > > > think.
> >> > > > >
> >> > > > > Thanks,
> >> > > > > Dmitri.
> >> > > > >
> >> > > > > On Mon, Jun 1, 2026 at 9:59 PM huaxin gao <
> [email protected]
> >> >
> >> > > > wrote:
> >> > > > >
> >> > > > > > Hi Dmitri,
> >> > > > > >
> >> > > > > > I like the idea — the atomic key write closes the in-flight
> gap,
> >> > and
> >> > > it
> >> > > > > > avoids the Iceberg metadata and spec issues. Agreed too that
> >> losing
> >> > > > keys
> >> > > > > > on already-deleted entities is harmless.
> >> > > > > >
> >> > > > > > But I think the harder case is delete operations themselves.
> For
> >> > drop
> >> > > > > > table/view/namespace, the operation removes the entity, so
> >> there is
> >> > > no
> >> > > > > > surviving entity to hold the key. A retry of a successful drop
> >> > should
> >> > > > > > return an equivalent success, but with entity-property storage
> >> the
> >> > > key
> >> > > > > > has nowhere to live — so the retry would just see "not found"
> >> and
> >> > > > behave
> >> > > > > > differently. Where would a drop's key live in this model?
> >> > > > > >
> >> > > > > > Thanks,
> >> > > > > > Huaxin
> >> > > > > >
> >> > > > > > On Mon, Jun 1, 2026 at 6:13 PM Yufei Gu <[email protected]
> >
> >> > > wrote:
> >> > > > > >
> >> > > > > > > One concern I have with storing idempotency records as
> entity
> >> > > > > properties
> >> > > > > > is
> >> > > > > > > the potential performance impact. Over time, an entity could
> >> > have a
> >> > > > > large
> >> > > > > > > number of idempotency key/value pairs. That would increase
> the
> >> > > > entity's
> >> > > > > > > size, which may affect load, update, serialization, and
> >> caching
> >> > > costs
> >> > > > > for
> >> > > > > > > normal catalog operations, even when idempotency is not
> >> involved.
> >> > > Use
> >> > > > > > cases
> >> > > > > > > such as table loading and entity in-memory caching could be
> >> > > affected.
> >> > > > > > > Before moving in that direction, I think it would be useful
> to
> >> > > better
> >> > > > > > > understand and measure the performance implications. If the
> >> > entity
> >> > > > size
> >> > > > > > > growth turns out to be negligible in practice, the approach
> >> may
> >> > > still
> >> > > > > be
> >> > > > > > > attractive because of its transactional simplicity.
> >> > > > > > >
> >> > > > > > > Yufei
> >> > > > > > >
> >> > > > > > >
> >> > > > > > > On Mon, Jun 1, 2026 at 2:17 PM Dmitri Bourlatchkov <
> >> > > [email protected]
> >> > > > >
> >> > > > > > > wrote:
> >> > > > > > >
> >> > > > > > > > Hi Huaxin,
> >> > > > > > > >
> >> > > > > > > > How about storing idempotency keys in the Polaris Entity
> >> > > properties
> >> > > > > > (not
> >> > > > > > > > Iceberg metadata)?
> >> > > > > > > >
> >> > > > > > > > I understand that entities can be deleted thus discarding
> >> > > > previously
> >> > > > > > > > recorded keys, but based on the use cases discussed so
> far,
> >> it
> >> > > does
> >> > > > > not
> >> > > > > > > > look like deleted entities should be a functional concern.
> >> > > > > > > >
> >> > > > > > > > Storing idempotency keys inside the entity will ensure
> that
> >> > their
> >> > > > > > updates
> >> > > > > > > > are processed in the same logical change set as the entity
> >> > > changes
> >> > > > > from
> >> > > > > > > the
> >> > > > > > > > IRC request payload.
> >> > > > > > > >
> >> > > > > > > > This will ensure uniform operations across all Persistence
> >> > > > > > > implementations
> >> > > > > > > > and will not require any Idempotency-specific Persistence
> >> > > changes.
> >> > > > > > > >
> >> > > > > > > > WDYT?
> >> > > > > > > >
> >> > > > > > > > Thanks,
> >> > > > > > > > Dmitri.
> >> > > > > > > >
> >> > > > > > > > On Sun, May 31, 2026 at 2:35 PM huaxin gao <
> >> > > [email protected]
> >> > > > >
> >> > > > > > > wrote:
> >> > > > > > > >
> >> > > > > > > > > Hi Dmitri, Robert,
> >> > > > > > > > >
> >> > > > > > > > > Thanks both.
> >> > > > > > > > >
> >> > > > > > > > > Dmitri — I agree with both of your points.
> >> > > > > > > > >
> >> > > > > > > > >   - Idempotency storage will stay separate from the
> >> > metastore.
> >> > > It
> >> > > > > > will
> >> > > > > > > > >     be separate in code and in transactions. We make the
> >> > > > > idempotency
> >> > > > > > > > >     decision before the handler runs, or after it
> commits
> >> —
> >> > > never
> >> > > > > > > inside
> >> > > > > > > > >     the metastore transaction.
> >> > > > > > > > >   - I'll document the assumption you raised. Model B is
> >> only
> >> > as
> >> > > > > > strict
> >> > > > > > > as
> >> > > > > > > > >     the spec wants if the client builds the request so
> >> that
> >> > at
> >> > > > most
> >> > > > > > one
> >> > > > > > > > >     try can commit (for example, update requirements).
> The
> >> > > > > catalog's
> >> > > > > > > > >     optimistic concurrency makes sure of this. Model B
> >> just
> >> > > > records
> >> > > > > > the
> >> > > > > > > > >     result on top of it. I'll say this clearly in the
> >> Polaris
> >> > > > docs.
> >> > > > > > > > >
> >> > > > > > > > > Robert — I see why the operation-id-in-metadata idea is
> >> > > > appealing.
> >> > > > > If
> >> > > > > > > we
> >> > > > > > > > > write the id inside the commit, it is atomic with the
> >> change.
> >> > > > That
> >> > > > > > > would
> >> > > > > > > > > close the in-flight gap for table and view operations.
> >> That
> >> > is
> >> > > a
> >> > > > > real
> >> > > > > > > > > plus.
> >> > > > > > > > >
> >> > > > > > > > > But I don't think we should put the idempotency key in
> >> table
> >> > > > > > metadata.
> >> > > > > > > > > Here is why:
> >> > > > > > > > >
> >> > > > > > > > > 1. It only works for table and view operations. It can't
> >> help
> >> > > > > > namespace
> >> > > > > > > > >    operations, grants, or other writes. A separate store
> >> > > handles
> >> > > > > all
> >> > > > > > of
> >> > > > > > > > >    them with one mechanism.
> >> > > > > > > > >
> >> > > > > > > > > 2. It mixes two concerns. Idempotency is a REST/catalog
> >> > > concern.
> >> > > > > > Table
> >> > > > > > > > >    metadata should describe the table — schema,
> snapshots,
> >> > > > > > > partitioning,
> >> > > > > > > > >    sort order. A per-request id is not table state. I'd
> >> > rather
> >> > > > not
> >> > > > > > mix
> >> > > > > > > > >    the two.
> >> > > > > > > > >
> >> > > > > > > > > 3. It bloats the metadata. To support retries we'd have
> to
> >> > keep
> >> > > > > > > > >    operation-ids with some retention/TTL. metadata.json
> is
> >> > > > > rewritten
> >> > > > > > on
> >> > > > > > > > >    every commit and read on every table load. For tables
> >> with
> >> > > > many
> >> > > > > > > > >    writes, this adds real cost. And every client and
> >> engine
> >> > > that
> >> > > > > > reads
> >> > > > > > > > >    the table pays it, not just the idempotency path.
> >> > > > > > > > >
> >> > > > > > > > > 4. It doesn't match the spec. The Iceberg REST spec
> >> defines
> >> > > > > > idempotency
> >> > > > > > > > >    at the protocol layer — an Idempotency-Key header
> with
> >> a
> >> > > > > > server-side
> >> > > > > > > > >    contract. It does not store idempotency in table
> >> metadata.
> >> > > > > Putting
> >> > > > > > > an
> >> > > > > > > > >    operation-id there would be a new mechanism that
> isn't
> >> in
> >> > > the
> >> > > > > spec
> >> > > > > > > > >    today. So it's a change to how the spec
> >> > > > > > > > >    works, and a cross-project change too.
> >> > > > > > > > >
> >> > > > > > > > > So I'd prefer to keep the record in a separate
> idempotency
> >> > > store.
> >> > > > > We
> >> > > > > > > > > accept the in-flight gap, but it is bounded. The
> catalog's
> >> > > > > optimistic
> >> > > > > > > > > concurrency stops a duplicate commit from landing. And
> >> once a
> >> > > > > record
> >> > > > > > > > > exists, retries replay cleanly.
> >> > > > > > > > >
> >> > > > > > > > > Thanks,
> >> > > > > > > > > Huaxin
> >> > > > > > > > >
> >> > > > > > > > > On Sat, May 30, 2026 at 3:15 AM Robert Stupp <
> >> [email protected]
> >> > >
> >> > > > > wrote:
> >> > > > > > > > >
> >> > > > > > > > > > Hi all,
> >> > > > > > > > > >
> >> > > > > > > > > > Thanks for the clarifications. Russell's explanation
> is
> >> > > > > especially
> >> > > > > > > > > useful.
> >> > > > > > > > > > I agree, ambiguous request outcomes, for example,
> >> timeouts
> >> > or
> >> > > > > > network
> >> > > > > > > > > > connections being reset, are hard to reason about.
> >> > > > > > > > > >
> >> > > > > > > > > > Clients often cannot reliably reconcile from the
> current
> >> > > state
> >> > > > > > alone
> >> > > > > > > > for
> >> > > > > > > > > > table/view state mutating operations.
> >> > > > > > > > > >
> >> > > > > > > > > > I wonder whether the idempotency key should be
> recorded
> >> in
> >> > > the
> >> > > > > > > > table/view
> >> > > > > > > > > > metadata as an "operation-id", with an explicit
> >> retention
> >> > > > > > guarantee,
> >> > > > > > > > > maybe
> >> > > > > > > > > > tied to a server-provided minimum TTL.
> >> > > > > > > > > > The approach could reduce or change the role of a
> >> separate
> >> > > > > > > > > > idempotency-record table and handling of it.
> >> > > > > > > > > >
> >> > > > > > > > > > Request handling could roughly look like this:
> >> > > > > > > > > >   if the current history/metadata already contains
> that
> >> > > > > > > "operation-id",
> >> > > > > > > > > >     return equivalent-enough response without
> re-running
> >> > the
> >> > > > > > > operation.
> >> > > > > > > > > >
> >> > > > > > > > > >   try the committing operation:
> >> > > > > > > > > >   if the commit succeeds:
> >> > > > > > > > > >     record the "operation-id" in the table/view
> >> metadata,
> >> > and
> >> > > > > > > > > >     return the successful response.
> >> > > > > > > > > >   if the commit runs into a conflict:
> >> > > > > > > > > >     re-check whether the current metadata/history
> >> contains
> >> > > that
> >> > > > > > > > > > "operation-id"
> >> > > > > > > > > >     if so:
> >> > > > > > > > > >       return equivalent-enough response.
> >> > > > > > > > > >     otherwise:
> >> > > > > > > > > >       return the conflict response.
> >> > > > > > > > > >
> >> > > > > > > > > > This is not perfect either and needs spec work,
> >> retention
> >> > > > rules,
> >> > > > > > and
> >> > > > > > > > may
> >> > > > > > > > > > only work for table and view operations.
> >> > > > > > > > > >
> >> > > > > > > > > > I mostly want to separate the questions:
> >> > > > > > > > > > 1. What guarantees do clients actually need after an
> >> > > ambiguous
> >> > > > > > > outcome?
> >> > > > > > > > > > 2. Where should the durable evidence for the guarantee
> >> > live?
> >> > > > > > > > > >
> >> > > > > > > > > > Robert
> >> > > > > > > > > >
> >> > > > > > > > > > On Sat, May 30, 2026 at 4:30 AM Dmitri Bourlatchkov <
> >> > > > > > > [email protected]>
> >> > > > > > > > > > wrote:
> >> > > > > > > > > >
> >> > > > > > > > > > > Hi Russell,
> >> > > > > > > > > > >
> >> > > > > > > > > > > Thanks for the information! It clarifies the use
> case
> >> a
> >> > lot
> >> > > > (at
> >> > > > > > > least
> >> > > > > > > > > for
> >> > > > > > > > > > > me :)
> >> > > > > > > > > > >
> >> > > > > > > > > > > In short, I'd say the main benefit is allowing
> >> clients to
> >> > > > avoid
> >> > > > > > > > > conflicts
> >> > > > > > > > > > > (409) on re-submitting changes that got committed by
> >> the
> >> > > > server
> >> > > > > > > > without
> >> > > > > > > > > > the
> >> > > > > > > > > > > client receiving confirmation of the success.
> >> > > > > > > > > > >
> >> > > > > > > > > > > I believe the Iceberg REST Catalog spec [1] is
> >> formally
> >> > > > > stricter
> >> > > > > > > than
> >> > > > > > > > > > Model
> >> > > > > > > > > > > B when it states "the server ensures no additional
> >> > effects
> >> > > > for
> >> > > > > > > > requests
> >> > > > > > > > > > > that carry the same Idempotency-Key". Since Model B
> >> > permits
> >> > > > > > request
> >> > > > > > > > > > > re-execution, the possibility of additional side
> >> effects
> >> > > > cannot
> >> > > > > > be
> >> > > > > > > > > ruled
> >> > > > > > > > > > > out completely based on the proposed server-side
> >> > algorithm
> >> > > > > alone.
> >> > > > > > > The
> >> > > > > > > > > > > server must assume that the client forms the
> (change)
> >> > > request
> >> > > > > in
> >> > > > > > > > such a
> >> > > > > > > > > > way
> >> > > > > > > > > > > that only one execution attempt can succeed (e.g. by
> >> > using
> >> > > > > > "update
> >> > > > > > > > > > > requirements"). This is also mentioned in  comments
> on
> >> > the
> >> > > > doc
> >> > > > > > [2].
> >> > > > > > > > > > >
> >> > > > > > > > > > > This is probably worth mentioning in the Polaris
> docs
> >> > > related
> >> > > > > to
> >> > > > > > > > > > > our Idempotency-Key implementation.
> >> > > > > > > > > > >
> >> > > > > > > > > > > Assuming this kind of cooperation on the client
> side,
> >> I
> >> > > > believe
> >> > > > > > > > Model B
> >> > > > > > > > > > can
> >> > > > > > > > > > > be considered compliant with the spec [1].
> >> > > > > > > > > > >
> >> > > > > > > > > > > In anticipation of fresh implementation PRs for this
> >> > > feature,
> >> > > > > I'd
> >> > > > > > > > like
> >> > > > > > > > > to
> >> > > > > > > > > > > re-emphasize (IIRC I mentioned this before) that, I
> >> > think,
> >> > > we
> >> > > > > > > should
> >> > > > > > > > > > avoid
> >> > > > > > > > > > > coupling Idempotency persistence with MetaStore
> >> > persistence
> >> > > > > (both
> >> > > > > > > > > > code-wise
> >> > > > > > > > > > > and transaction-wise). Model B processes
> >> > > Idempotency-related
> >> > > > > data
> >> > > > > > > > > outside
> >> > > > > > > > > > > the original change request's execution scope.
> >> > Idempotency
> >> > > > > > > decisions
> >> > > > > > > > > are
> >> > > > > > > > > > > made either before the request starts executing or
> >> after
> >> > it
> >> > > > is
> >> > > > > > > > > committed
> >> > > > > > > > > > to
> >> > > > > > > > > > > the MetaStore.
> >> > > > > > > > > > >
> >> > > > > > > > > > > [1]
> >> > > > > > > > > > >
> >> > > > > > > > > > >
> >> > > > > > > > > >
> >> > > > > > > > >
> >> > > > > > > >
> >> > > > > > >
> >> > > > > >
> >> > > > >
> >> > > >
> >> > >
> >> >
> >>
> https://github.com/apache/polaris/blob/4e4eaf840bf71d431b13034b0dd6f338261d8e8b/spec/iceberg-rest-catalog-open-api.yaml#L2098
> >> > > > > > > > > > >
> >> > > > > > > > > > > [2]
> >> > > > > > > > > > >
> >> > > > > > > > > > >
> >> > > > > > > > > >
> >> > > > > > > > >
> >> > > > > > > >
> >> > > > > > >
> >> > > > > >
> >> > > > >
> >> > > >
> >> > >
> >> >
> >>
> https://docs.google.com/document/d/1hqTejVyYXDpL5MJcVc7NyhCslKaGH82QoqMEcUYPvkE/edit?tab=t.0
> >> > > > > > > > > > >
> >> > > > > > > > > > > Cheers,
> >> > > > > > > > > > > Dmitri.
> >> > > > > > > > > > >
> >> > > > > > > > > > > On Fri, May 29, 2026 at 8:26 PM Russell Spitzer <
> >> > > > > > > > > > [email protected]
> >> > > > > > > > > > > >
> >> > > > > > > > > > > wrote:
> >> > > > > > > > > > >
> >> > > > > > > > > > > > The problem with a client attempting to determine
> if
> >> > it’s
> >> > > > > > > > operations
> >> > > > > > > > > > > > succeeded via  load table, and the reason all this
> >> work
> >> > > has
> >> > > > > > > > > proceeded,
> >> > > > > > > > > > is
> >> > > > > > > > > > > > that there is no way for a client to guaranteed
> >> path to
> >> > > > > > actually
> >> > > > > > > > > > > determine
> >> > > > > > > > > > > > if a commit occurred. There are too many
> legitimate
> >> > > > > mechanisms
> >> > > > > > to
> >> > > > > > > > > erase
> >> > > > > > > > > > > > history from an Iceberg table to guarantee an
> >> operation
> >> > > > > > occurred.
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > For example, you could check if your snapshot
> >> exists in
> >> > > > > > snapshot
> >> > > > > > > > > > history
> >> > > > > > > > > > > > but this could have been erased by expire
> snapshots.
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > Or you could check if the schema was modified
> >> according
> >> > > to
> >> > > > > your
> >> > > > > > > > > update,
> >> > > > > > > > > > > but
> >> > > > > > > > > > > > this too could have been undone by another
> >> operation.
> >> > > > Client
> >> > > > > A
> >> > > > > > > adds
> >> > > > > > > > > > > column
> >> > > > > > > > > > > > but gets time out, Client B removes the Column,
> >> Client
> >> > A
> >> > > > > > retries
> >> > > > > > > > and
> >> > > > > > > > > > adds
> >> > > > > > > > > > > > the column again.
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > Because of this the Iceberg client usually just
> >> bails
> >> > out
> >> > > > to
> >> > > > > he
> >> > > > > > > > user
> >> > > > > > > > > > with
> >> > > > > > > > > > > > an exception if it doesn’t get an actual
> >> confirmation
> >> > > that
> >> > > > > the
> >> > > > > > > > commit
> >> > > > > > > > > > > > succeeded from the server. This leaves the “can I
> >> retry
> >> > > or
> >> > > > > not”
> >> > > > > > > as
> >> > > > > > > > an
> >> > > > > > > > > > > > exercise to the end user.
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > In practice, actual Iceberg users work around this
> >> sort
> >> > > of
> >> > > > > > thing
> >> > > > > > > by
> >> > > > > > > > > > > adding
> >> > > > > > > > > > > > all sorts of custom metadata to hopefully persist
> >> > history
> >> > > > in
> >> > > > > > the
> >> > > > > > > > > table
> >> > > > > > > > > > > > itself in some way that can’t be touched by expire
> >> > > > snapshots,
> >> > > > > > but
> >> > > > > > > > > this
> >> > > > > > > > > > is
> >> > > > > > > > > > > > usually very fragile and also relies on all
> clients
> >> > > > behaving
> >> > > > > > > well.
> >> > > > > > > > > I’ve
> >> > > > > > > > > > > > seen folks use custom table properties for example
> >> > > > “batch-5:
> >> > > > > > > > > committed”
> >> > > > > > > > > > > > then manually have their own retry logic check
> >> whether
> >> > > this
> >> > > > > > > > property
> >> > > > > > > > > is
> >> > > > > > > > > > > > set. Then, of course, they also have to add a
> bunch
> >> > > custom
> >> > > > > > logic
> >> > > > > > > to
> >> > > > > > > > > > make
> >> > > > > > > > > > > > sure they clean up this state as well.
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > This is why Iceberg added the Idempotency path in
> >> the
> >> > > first
> >> > > > > > > place,
> >> > > > > > > > it
> >> > > > > > > > > > > gives
> >> > > > > > > > > > > > us a guaranteed way for clients to retry in case
> of
> >> a
> >> > > > network
> >> > > > > > > issue
> >> > > > > > > > > or
> >> > > > > > > > > > > > catalog issue with a guarantee they will not do
> >> > duplicate
> >> > > > > work
> >> > > > > > be
> >> > > > > > > > > > > retrying.
> >> > > > > > > > > > > > With this in place the client can now cleanly
> retry
> >> > > (within
> >> > > > > the
> >> > > > > > > > > > > idempotency
> >> > > > > > > > > > > > window) the same operation over and over without
> >> > throwing
> >> > > > an
> >> > > > > > > > > exception
> >> > > > > > > > > > to
> >> > > > > > > > > > > > the end user. Only in a situation where the
> catalog
> >> > > cannot
> >> > > > > > > respond
> >> > > > > > > > > > over a
> >> > > > > > > > > > > > very long time will the user actually have to do
> >> some
> >> > > sort
> >> > > > of
> >> > > > > > > > > > > > reconciliation. You can look at the history of the
> >> > > Iceberg
> >> > > > > > > client’s
> >> > > > > > > > > > retry
> >> > > > > > > > > > > > behavior with ambiguous server side or network
> >> errors
> >> > to
> >> > > > see
> >> > > > > > how
> >> > > > > > > > this
> >> > > > > > > > > > has
> >> > > > > > > > > > > > been a problem in the past.
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > On Fri, May 29, 2026 at 1:24 PM huaxin gao <
> >> > > > > > > [email protected]
> >> > > > > > > > >
> >> > > > > > > > > > > wrote:
> >> > > > > > > > > > > >
> >> > > > > > > > > > > > > Hi Robert,
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > Thanks for your reply!
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > You're right that Model B does not prevent
> >> duplicate
> >> > > > > > execution.
> >> > > > > > > > The
> >> > > > > > > > > > > > > record is written only after success. So if a
> >> client
> >> > > > times
> >> > > > > > out
> >> > > > > > > > > while
> >> > > > > > > > > > > the
> >> > > > > > > > > > > > > first request is still running, a retry can run
> >> the
> >> > > > handler
> >> > > > > > > > again.
> >> > > > > > > > > > > There
> >> > > > > > > > > > > > > is no record yet to stop it. So Model B is
> >> "remember
> >> > > and
> >> > > > > > > replay a
> >> > > > > > > > > > > > > successful result," not "run exactly once."
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > On the trade-off: Model A gives a stronger
> >> guarantee,
> >> > > but
> >> > > > > it
> >> > > > > > > > needs
> >> > > > > > > > > > > > > reserve/heartbeat/purge state, which adds
> >> complexity
> >> > > and
> >> > > > > > > > overhead.
> >> > > > > > > > > > > Model
> >> > > > > > > > > > > > > B is simpler and cheaper. The window it leaves
> >> open
> >> > is
> >> > > > > small,
> >> > > > > > > > and a
> >> > > > > > > > > > > > > client only retries after a timeout, so racing
> >> first
> >> > > > > requests
> >> > > > > > > > > should
> >> > > > > > > > > > be
> >> > > > > > > > > > > > > rare in practice. Every design is a trade-off,
> >> and my
> >> > > > view
> >> > > > > is
> >> > > > > > > > that
> >> > > > > > > > > > > Model
> >> > > > > > > > > > > > > B is the right one here.
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > It also helps to be clear about where
> >> duplicate-work
> >> > > > > > protection
> >> > > > > > > > > > really
> >> > > > > > > > > > > > > comes from. It comes from the catalog itself,
> not
> >> > from
> >> > > > > > > > idempotency.
> >> > > > > > > > > > The
> >> > > > > > > > > > > > > catalog uses optimistic concurrency. If wo first
> >> > > attempts
> >> > > > > > race,
> >> > > > > > > > at
> >> > > > > > > > > > most
> >> > > > > > > > > > > > > one commit wins and the other gets a 409.
> >> Idempotency
> >> > > > sits
> >> > > > > on
> >> > > > > > > top
> >> > > > > > > > > of
> >> > > > > > > > > > > > that.
> >> > > > > > > > > > > > > It does not replace it.
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > So what does Model B add over "the client just
> >> calls
> >> > > > > > loadTable
> >> > > > > > > > and
> >> > > > > > > > > > > > > reconciles"? Two things that I think are real:
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > >   1. The 422 check. loadTable can tell a client
> >> that
> >> > a
> >> > > > > table
> >> > > > > > > > > exists.
> >> > > > > > > > > > It
> >> > > > > > > > > > > > >      cannot tell the client that the table THEY
> >> > created
> >> > > > > with
> >> > > > > > > THIS
> >> > > > > > > > > key
> >> > > > > > > > > > > is
> >> > > > > > > > > > > > >      the one that succeeded. The record binds
> the
> >> key
> >> > > to
> >> > > > > > > > > (principal,
> >> > > > > > > > > > > > >      operation, resource). If the same key is
> >> reused
> >> > > for
> >> > > > a
> >> > > > > > > > > different
> >> > > > > > > > > > > > >      request, the server returns 422. The client
> >> > cannot
> >> > > > > > detect
> >> > > > > > > > this
> >> > > > > > > > > > on
> >> > > > > > > > > > > > >      its own.
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > >   2. One server-side behavior for all mutating
> >> ops.
> >> > > > > > > create-table
> >> > > > > > > > > > > happens
> >> > > > > > > > > > > > >      to reconcile cleanly with loadTable. But
> the
> >> > point
> >> > > > of
> >> > > > > > the
> >> > > > > > > > > > > > >      Idempotency-Key header is that the client
> >> should
> >> > > not
> >> > > > > > have
> >> > > > > > > to
> >> > > > > > > > > > write
> >> > > > > > > > > > > > >      reconciliation logic for every operation.
> >> For a
> >> > > > known
> >> > > > > > key,
> >> > > > > > > > the
> >> > > > > > > > > > > > >      server turns what would be a 409 into an
> >> > > equivalent
> >> > > > > 2xx
> >> > > > > > > > > replay.
> >> > > > > > > > > > > The
> >> > > > > > > > > > > > >      client gets a clean success instead of an
> >> error
> >> > it
> >> > > > has
> >> > > > > > to
> >> > > > > > > > > > special-
> >> > > > > > > > > > > > >      case.
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > There is a third, weaker benefit: once a record
> >> > exists,
> >> > > > > > retries
> >> > > > > > > > > stop
> >> > > > > > > > > > > > > seeing flip-flopping results. But that only
> helps
> >> > > after a
> >> > > > > > > record
> >> > > > > > > > > > > exists,
> >> > > > > > > > > > > > > which is exactly the window you pointed out is
> >> > > > unprotected.
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > So I'll correct my earlier wording. This is not
> >> > > > convergence
> >> > > > > > on
> >> > > > > > > > > > exactly-
> >> > > > > > > > > > > > > once idempotency. It is a narrower guarantee:
> >> replay
> >> > a
> >> > > > > > recorded
> >> > > > > > > > > > result,
> >> > > > > > > > > > > > > plus detect key misuse. It sits on top of the
> >> > catalog's
> >> > > > > > > existing
> >> > > > > > > > > > > > > concurrency control. The real question for the
> >> list
> >> > is
> >> > > > > > simple:
> >> > > > > > > is
> >> > > > > > > > > > that
> >> > > > > > > > > > > > > narrower guarantee worth shipping on its own? Or
> >> do
> >> > we
> >> > > > need
> >> > > > > > > Model
> >> > > > > > > > > A's
> >> > > > > > > > > > > > > in-flight protection to have a strong
> idempotency
> >> > > > > guarantee?
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > My view is that the narrow version is worth it
> for
> >> > now:
> >> > > > > it's
> >> > > > > > > the
> >> > > > > > > > > > > > > behavior the spec asks for, the 422 check can't
> be
> >> > done
> >> > > > > > > > > client-side,
> >> > > > > > > > > > > and
> >> > > > > > > > > > > > > it's a small change we can strengthen toward
> >> Model A
> >> > > > later
> >> > > > > > if a
> >> > > > > > > > > real
> >> > > > > > > > > > > use
> >> > > > > > > > > > > > > case needs it. Happy to hear what others think.
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > Best,
> >> > > > > > > > > > > > > Huaxin
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > On Fri, May 29, 2026 at 7:36 AM Robert Stupp <
> >> > > > > [email protected]
> >> > > > > > >
> >> > > > > > > > > wrote:
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > > > > Hi Huaxin,
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > Thanks for writing this up and moving the
> design
> >> > > > > discussion
> >> > > > > > > > back
> >> > > > > > > > > to
> >> > > > > > > > > > > > dev@
> >> > > > > > > > > > > > > .
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > Since you’re asking before locking in the
> >> > > > > implementation, I
> >> > > > > > > > think
> >> > > > > > > > > > we
> >> > > > > > > > > > > > > should
> >> > > > > > > > > > > > > > clarify one point.
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > Model B is certainly simpler than the
> >> lease-based
> >> > > > > approach,
> >> > > > > > > but
> >> > > > > > > > > I’m
> >> > > > > > > > > > > not
> >> > > > > > > > > > > > > > sure I fully understand what problem it still
> >> > solves.
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > As I read it, if a client times out while the
> >> > > original
> >> > > > > > > request
> >> > > > > > > > is
> >> > > > > > > > > > > still
> >> > > > > > > > > > > > > > running, a retry with the same key may not see
> >> an
> >> > > > > > idempotency
> >> > > > > > > > > > record
> >> > > > > > > > > > > > yet
> >> > > > > > > > > > > > > > and could run the handler again.
> >> > > > > > > > > > > > > > So this feels less like preventing duplicate
> >> > > execution
> >> > > > > and
> >> > > > > > > more
> >> > > > > > > > > > like
> >> > > > > > > > > > > > > > remembering a successful result after the
> fact.
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > For the create-table case, couldn’t a client
> >> > achieve
> >> > > > > > roughly
> >> > > > > > > > the
> >> > > > > > > > > > same
> >> > > > > > > > > > > > > > recovery by calling loadTable after an
> ambiguous
> >> > > > timeout
> >> > > > > > and
> >> > > > > > > > > > > > reconciling
> >> > > > > > > > > > > > > > from there?
> >> > > > > > > > > > > > > > Since Model B also rebuilds the response from
> >> > current
> >> > > > > > catalog
> >> > > > > > > > > > state,
> >> > > > > > > > > > > > I’m
> >> > > > > > > > > > > > > > trying to understand what it gives us beyond
> >> that.
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > I’m not against simplifying the design, but I
> >> think
> >> > > we
> >> > > > > > should
> >> > > > > > > > be
> >> > > > > > > > > > > clear
> >> > > > > > > > > > > > > > about the narrower guarantee before calling
> this
> >> > > > > > convergence.
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > Best,
> >> > > > > > > > > > > > > > Robert
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > On Fri, May 29, 2026 at 12:29 AM huaxin gao <
> >> > > > > > > > > > [email protected]>
> >> > > > > > > > > > > > > > wrote:
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > Hi all,
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > I've simplified the proposed design for
> >> > > > Idempotency-Key
> >> > > > > > > > support
> >> > > > > > > > > > in
> >> > > > > > > > > > > > > > Polaris
> >> > > > > > > > > > > > > > > (Iceberg REST spec — retries with the same
> key
> >> > must
> >> > > > not
> >> > > > > > > > produce
> >> > > > > > > > > > > > > > additional
> >> > > > > > > > > > > > > > > side effects), and I'd like a wider review
> >> before
> >> > > > > > updating
> >> > > > > > > > the
> >> > > > > > > > > > > > > > > implementation PR (#4269 <
> >> > > > > > > > > > > > https://github.com/apache/polaris/pull/4269
> >> > > > > > > > > > > > > >).
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > What changed
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > >   - Before (Model A, lease-based): reserve
> an
> >> > > > > idempotency
> >> > > > > > > row
> >> > > > > > > > > > > before
> >> > > > > > > > > > > > > > doing
> >> > > > > > > > > > > > > > > work → IN_PROGRESS / heartbeat → finalize
> >> after.
> >> > > > > > > > > > > > > > >   - After (Model B, optimistic commit): run
> >> the
> >> > > > handler
> >> > > > > > > > first →
> >> > > > > > > > > > > > record
> >> > > > > > > > > > > > > > only
> >> > > > > > > > > > > > > > > after a successful (2xx) outcome. The record
> >> > stores
> >> > > > > > > binding +
> >> > > > > > > > > > > status,
> >> > > > > > > > > > > > > not
> >> > > > > > > > > > > > > > > the HTTP response body. Retries with the
> same
> >> key
> >> > > > > > re-derive
> >> > > > > > > > an
> >> > > > > > > > > > > > > equivalent
> >> > > > > > > > > > > > > > > response from current catalog state
> >> > > > > > > > > > > > > > >     instead of replaying a stored payload.
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > The design doc still compares Model A and
> >> Model B
> >> > > > > > > > side-by-side
> >> > > > > > > > > so
> >> > > > > > > > > > > the
> >> > > > > > > > > > > > > > > trade-offs are explicit. So far the
> discussion
> >> > has
> >> > > > been
> >> > > > > > > > leaning
> >> > > > > > > > > > > > toward
> >> > > > > > > > > > > > > > > Model B — mutating REST operations only,
> >> 2xx-only
> >> > > > > > > > persistence,
> >> > > > > > > > > no
> >> > > > > > > > > > > > > > > response-body storage, and the known
> >> > > > > > > > > > > > > > > trade-offs (e.g. concurrent first-request
> >> races;
> >> > > see
> >> > > > > the
> >> > > > > > > > NOTES
> >> > > > > > > > > > > > section
> >> > > > > > > > > > > > > in
> >> > > > > > > > > > > > > > > the doc).
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > Does this direction look right before we
> lock
> >> in
> >> > > the
> >> > > > > > > > > > > implementation?
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > Comments on the doc
> >> > > > > > > > > > > > > > > <
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > >
> >> > > > > > > > > > >
> >> > > > > > > > > >
> >> > > > > > > > >
> >> > > > > > > >
> >> > > > > > >
> >> > > > > >
> >> > > > >
> >> > > >
> >> > >
> >> >
> >>
> https://docs.google.com/document/d/1hqTejVyYXDpL5MJcVc7NyhCslKaGH82QoqMEcUYPvkE/edit?tab=t.0
> >> > > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > or replies on this thread both work.
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > > > Thanks,
> >> > > > > > > > > > > > > > > Huaxin
> >> > > > > > > > > > > > > > >
> >> > > > > > > > > > > > > >
> >> > > > > > > > > > > > >
> >> > > > > > > > > > > >
> >> > > > > > > > > > >
> >> > > > > > > > > >
> >> > > > > > > > >
> >> > > > > > > >
> >> > > > > > >
> >> > > > > >
> >> > > > >
> >> > > >
> >> > >
> >> >
> >>
> >
>

Reply via email to