Skip to content

branch-4.1: [fix](catalog) re-auth Iceberg REST catalog on 401 instead of wedging until FE restart - #70029

Merged
yiguolei merged 1 commit into
apache:branch-4.1from
CalvinKirs:backport-64966-branch-4.1
Nov 4, 2026
Merged

yiguolei merged 1 commit into
apache:branch-4.1from
CalvinKirs:backport-64966-branch-4.1

Conversation

@CalvinKirs

Copy link
Copy Markdown
Member

… until FE restart (apache#64966)

Problem Summary:

An Iceberg REST catalog using oauth2 client credentials obtains its
token when the catalog client is built and keeps it refreshed in the
background. If a refresh permanently fails (for example the auth server
is briefly unreachable or overloaded right when the refresh fires), the
client is left holding a stale token, and every subsequent request fails
with `NotAuthorizedException: Not authorized` (HTTP 401) — forever. The
client has no re-authentication path of its own, and neither `REFRESH
CATALOG` nor metadata-cache invalidation rebuilds it, so the catalog
stays unusable until the FE process is restarted.

We hit this in production against an Apache Polaris REST catalog. The
failure mode is nasty to diagnose because the 401 is swallowed:
`getDbNullable`/`buildDbForInit` report `Database [<db>] does not
exist`, `SHOW DATABASES` returns a partial list, and since DDL
statements (`CREATE VIEW`/`CREATE MTMV`/CTAS/`INSERT`) are
`ForwardToMaster`, one wedged master FE breaks all DDL cluster-wide
while plain SELECTs on follower FEs keep working. Only an FE restart
recovered.

Recovery now lives entirely at the REST layer (per review feedback —
thanks @CalvinKirs): a new `ReauthenticatingRestSessionCatalog` (a
`BaseViewSessionCatalog`) that `IcebergRestProperties` wraps around the
`RESTSessionCatalog` it already builds and owns. On a 401 raised by a
request made under the catalog's own identity, the wrapper rebuilds the
delegate through the same build seam (fresh HTTP client, fresh OAuth2
token fetch), closes the wedged client, and retries the operation once.
If the retry also fails, the error propagates unchanged.

Design notes:

- **REST-only.** Only `IcebergRestProperties` constructs the wrapper; no
other catalog type sees this logic. `IcebergMetadataOps` is untouched
apart from widening two declared types from `RESTSessionCatalog` to
`BaseViewSessionCatalog`.
- **All paths covered, no call-site changes, no stale references.** The
default catalog (`asCatalog(empty)`), the view catalog
(`asViewCatalog(empty)`), and per-user delegated sessions are all thin
views that call back into the session catalog, so they inherit the
recovery automatically — and because the swap happens below them, no
holder of any of those references ever points at a dead client after
recovery.
- **Reads and mutations both retried.** A 401 is rejected by the server
before the request is processed, so retrying after re-authentication
cannot double-apply an operation.
- **Per-user delegated sessions are excluded.** A 401 on a request
carrying a delegated (per-user) credential means that user's token is
invalid; rebuilding the shared client cannot fix it and must not be
triggered by it. Likewise, when catalog initialization itself runs under
a delegated credential (`iceberg.rest.session=user`), no wrapper is
installed, so the rebuild path can never capture a user token into the
shared client.
- **Typed detection only.** `NotAuthorizedException` anywhere in the
cause chain; no message-string matching.
- **Bounded.** One rebuild + one retry per failed request; concurrent
401s coalesce into a single rebuild (a request that raced with a
completed rebuild just retries on the fresh client).

Boundary: recovery triggers on catalog-level operations. Objects already
handed out (a loaded `Table`/`View`, a `buildTable`/`buildView` builder)
keep the client they were created with; if the token expires between
obtaining and using one, that single use can still 401, and the next
catalog-level operation heals the client.

- Iceberg REST catalogs recover automatically from an expired/rejected
OAuth2 token (401) by re-authenticating and retrying once, instead of
failing every request until the FE restarts. Other catalog types and
non-401 failures are unaffected.

Signed-off-by: Raghvendra Singh <[email protected]>
Co-authored-by: Claude <[email protected]>
(cherry picked from commit 3a75387)

Backport to branch-4.1: retain the existing RESTCatalog facade for table,
view, transaction and close operations. Wrap its session through the
protected construction hook because delegated-session support is absent
on this branch. No delegated-session feature is introduced.

Validation: changed-file Checkstyle passes; nine focused recovery and
RESTCatalog adapter tests pass against Iceberg 1.11.0.
@CalvinKirs
CalvinKirs requested a review from yiguolei as a code owner November 3, 2026 04:32
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@CalvinKirs

Copy link
Copy Markdown
Member Author

run buildall

@yiguolei
yiguolei merged commit 1c2c5c9 into apache:branch-4.1 Nov 4, 2026
28 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants