Skip to content

REST: Send If-None-Match on table refresh - #18188

Open
raunaqmorarka wants to merge 1 commit into
apache:mainfrom
raunaqmorarka:rest-conditional-refresh
Open

raunaqmorarka wants to merge 1 commit into
apache:mainfrom
raunaqmorarka:rest-conditional-refresh

Conversation

@raunaqmorarka

@raunaqmorarka raunaqmorarka commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

RESTTableOperations.refresh() always issued an unconditional GET and parsed the full metadata body, even though the same catalog already does freshness-aware loading for loadTable through RESTTableCache. Servers that honour If-None-Match (for example Polaris) answer with 304 Not Modified, so a refresh of an unchanged table only needs a header round trip.

RESTTableOperations now takes the ETag of the load response through its constructor, updates it from every successful refresh and commit response, sends it as If-None-Match on refresh(), and returns current() on a 304 without parsing a body. When the server sends no ETag, refresh behaves as before. The RESTTableCache entry is left untouched, since the ETag there pairs with the metadata captured by the cached supplier.

RESTSessionCatalog.newTableOps gets a new overload with an eTag parameter; the existing one is deprecated and delegates with null.

Note: commit() now uses the RESTClient.post overload that returns response headers. BaseHTTPClient supports it; a custom RESTClient relying only on the interface default would need to implement it.

Tests cover a conditional refresh returning 304, a changed table still refreshing fully and adopting the new ETag, a commit ETag being reused, servers that send no ETag, and the 304 path over the Jetty-backed test server.

@github-actions github-actions Bot added the core label Sep 20, 2026
@raunaqmorarka
raunaqmorarka force-pushed the rest-conditional-refresh branch from 9098d9d to 52e3d86 Compare September 20, 2026 08:52
@gaborkaszab

Copy link
Copy Markdown
Contributor

Isn't this solving the same as #17832?

@raunaqmorarka

Copy link
Copy Markdown
Contributor Author

Isn't this solving the same as #17832?

Different problem. #17832 restores the lazy snapshots supplier and the snapshots query parameter on refresh in refs mode. This PR makes refresh conditional with If-None-Match so an unchanged table gets a 304 and no metadata body is parsed. Both change refresh() and updateCurrentMetadata, so one will need a rebase after the other merges, but they are independent. They also fit together: with #17832 the refresh request carries the same query parameters as the load, so the ETag from loadTable matches on the first refresh, and a 304 leaves current() and its snapshots supplier untouched.

@gaborkaszab gaborkaszab left a comment

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.

Hi @raunaqmorarka ,

I'm trying to refresh my memories since I was involved with this. Couple of questions:

  1. Isn't the ETag redundant now: There is one kept in the table cache and one in ops. Also, they can get out of sync, right?
  2. In terms of the table cache, this is an in-place operation, right? Executing ops.refresh() also refreshes the TableMetadata in the table cache through ops, right?
  3. I recall one complication might be that credentials and configs are also returned by the get request not just table metadata and ETag. Since these are incorporated into FileIO and RESTClient, they are not refreshed, right? Since the age of the table in the cache is not affected here, probably we are fine with this, just want to double check.

cc @nastra @danielcweeks Might be a useful addition. I always dragged this as a potential future addition to freshness-aware loading. Would you mind also taking a look?

Comment thread core/src/main/java/org/apache/iceberg/rest/RESTTableOperations.java Outdated
Comment thread core/src/main/java/org/apache/iceberg/rest/RESTTableOperations.java Outdated
Comment thread core/src/main/java/org/apache/iceberg/rest/RESTTableOperations.java Outdated
Comment thread core/src/main/java/org/apache/iceberg/rest/RESTTableOperations.java Outdated
Comment thread core/src/main/java/org/apache/iceberg/rest/RESTTableOperations.java Outdated
Reuse the ETag from loadTable and commit responses so an unchanged
table refreshes with a 304 instead of a full metadata body.
@raunaqmorarka

Copy link
Copy Markdown
Contributor Author
  1. The two ETags validate different metadata. The cache ETag belongs to the metadata captured in the cached supplier, and the ops ETag belongs to ops.current(). They diverge after a refresh or commit, but each stays paired with its own metadata. Writing the refreshed ETag back into the cache would be wrong, since the cached supplier still returns the load-time metadata and a 304 on loadTable would then hand out stale metadata. The cost of divergence is one extra full load on the next loadTable, which then replaces the cache entry.

  2. Correct, it is not in place. The cached supplier creates a new RESTTableOperations on every get(), so ops.refresh() only updates that instance. The cache entry keeps the load-time metadata and ETag.

  3. refresh() never consumed config or credentials from the response before this change either. Only loadTable feeds them into the table FileIO. A 304 has no body, so nothing changes on that path, and the cache entry with its FileIO is untouched.

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