morningman commented on PR #63068:
URL: https://github.com/apache/doris/pull/63068#issuecomment-4463795501
# Improvement Suggestions for PR #63068
## `sessionId` is regenerated on every forwarded request, defeating
Iceberg's session cache on the master FE
**Where**:
`fe/fe-core/src/main/java/org/apache/doris/datasource/SessionContext.java:43-48`
and
`fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java:842-851`
```java
// SessionContext.java
public static SessionContext of(DelegatedCredential delegatedCredential) {
if (delegatedCredential == null) return empty();
return new SessionContext(UUID.randomUUID().toString(),
delegatedCredential);
}
```
```java
// ConnectProcessor.java
static void restoreForwardedSessionContext(ConnectContext context,
TMasterOpRequest request) {
if (!request.isSetDelegatedCredentialToken()) return;
...
context.setSessionContext(SessionContext.of(new
DelegatedCredential(...)));
}
```
On the follower FE, `SessionContext.of()` is called once at login and the
sessionId is stable for the connection's lifetime. But on the master FE,
`restoreForwardedSessionContext` runs once *per forwarded request*, so the same
user-credential-on-master gets a fresh sessionId every time.
Iceberg REST's `CatalogProperties.AUTH_SESSION_TIMEOUT_MS` (which this PR
plumbs through via `iceberg.rest.session-timeout`) keys its OAuth session cache
by sessionId. So:
- Token-exchange mode: every forwarded request triggers a full OAuth
token-exchange round-trip on the master. This will be both slow and easy to
rate-limit at the IDP.
- Plain access-token mode: every request opens a fresh internal
`RESTSessionCatalog` session. REST servers that count active sessions (e.g. for
accounting) will see one session per query rather than one per user.
Suggested fix: serialise the originating sessionId across Thrift alongside
the token, so master reuses it. Add a Thrift field
`delegated_credential_session_id` and pass it into `SessionContext` rather than
minting a new UUID.
```thrift
struct TMasterOpRequest {
...
1007: optional string delegated_credential_session_id
}
```
```java
// ConnectProcessor.java
String sessionId = request.isSetDelegatedCredentialSessionId()
? request.getDelegatedCredentialSessionId()
: UUID.randomUUID().toString();
context.setSessionContext(SessionContext.of(sessionId, new
DelegatedCredential(...)));
```
## `IcebergRestProperties.initCatalog` pulls a session from `ConnectContext`
at catalog-create time
**Where**:
`fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/IcebergRestProperties.java:211-218`
```java
@Override
public Catalog initCatalog(String catalogName, Map<String, String>
catalogProps,
List<StorageProperties> storagePropertiesList) {
catalogProps.putAll(getIcebergRestCatalogPropertiesForCatalogInit(currentSessionContext()));
...
}
```
`initCatalog` is called during catalog *lifecycle initialization* (e.g. on
`CREATE CATALOG` or on first lazy load), not per-request. Whatever happens to
be in `ConnectContext.get()` at that moment leaks into the catalog's
initialization properties. In `user-session` + ACCESS_TOKEN mode, this means
the catalog can end up with the *first user's token* baked into
`OAuth2Properties.TOKEN`, permanently.
In practice the per-request
`IcebergSessionCatalogAdapter.delegatedCatalog(ctx)` bypasses this global
catalog, so this only matters when code falls back to the plain `catalog` field
— but several places in `IcebergMetadataOps` still do that (the
`catalog(SessionContext)` method only switches when
`isIcebergRestUserSessionEnabled()`, not when the ctx itself carries a
credential).
Suggested fix: pass `SessionContext.empty()` from `initCatalog`. The init
path should never inherit a user context — by construction it represents
catalog-level bootstrap state.
```java
@Override
public Catalog initCatalog(String catalogName, Map<String, String>
catalogProps,
List<StorageProperties> storagePropertiesList) {
catalogProps.putAll(getIcebergRestCatalogPropertiesForCatalogInit(SessionContext.empty()));
...
}
```
---
## Internal callers that pass `SessionContext.empty()` will trip `requires
delegated credential` when user-session is on
**Where**:
`fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java:1222-1244`
```java
private Catalog catalog(SessionContext ctx) {
if (isIcebergRestUserSessionEnabled()) {
return sessionCatalogAdapter.delegatedCatalog(ctx); // requires
non-empty ctx
}
return catalog;
}
```
There are still legacy overloads like `tableExist(String, String)` that call
`tableExist(SessionContext.empty(), ...)`. When user-session is enabled, every
such call ends up in `delegatedCatalog(SessionContext.empty())` →
`requireDelegatedCredential(...)` → throws.
In normal query paths this is fine (the calling user always has a context).
But Doris has many *background* paths that may invoke catalog ops without an
active ConnectContext:
- meta cache refresh in `ExternalMetaCacheMgr`
- partition info preloading
- DDL replay on follower restart
These will explode the first time someone enables user-session, in ways that
are hard to reproduce in unit tests.
Suggested fix: decide explicitly per call site whether it's a user request
or a system request:
- User requests → require a ctx; assert on `SessionContext.empty()`.
- System requests → bypass `delegatedCatalog` and go through the bootstrap
`catalog` (which uses the catalog's own service credential, not a user's
token). The execution authenticator is already wired in for this — extend that
mechanism.
Either way, the legacy no-ctx overloads should be deprecated or removed so
the choice is made consciously at the call site.
## Dead code: `IcebergMetadataOps.useSessionCatalog`
**Where**:
`fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java:1246-1248`
```java
private boolean useSessionCatalog(SessionContext ctx) {
return ctx != null && ctx.hasDelegatedCredential() &&
isIcebergRestUserSessionEnabled();
}
```
Defined, never called. Either delete it, or — better — use it inside
`catalog(ctx)` / `namespaces(ctx)` so the empty-ctx legacy callers (see
MEDIUM-4) take a sensible fallback path.
---
## Six near-identical copies of `currentSessionContext()`
**Where**: `ExternalDatabase.java`, `IcebergExternalCatalog.java`,
`IcebergExternalTable.java`, `IcebergMetadataOps.java`,
`IcebergRestProperties.java`, `IcebergUtils.java`
Every copy is the same six lines:
```java
private static SessionContext currentSessionContext() {
ConnectContext context = ConnectContext.get();
if (context == null) return SessionContext.empty();
SessionContext sessionContext = context.getSessionContext();
return sessionContext == null ? SessionContext.empty() : sessionContext;
}
```
Move it to `SessionContext.current()` (or
`SessionContext.fromCurrentConnect()`) so the next person who needs to add
logging, telemetry, or a non-ThreadLocal source doesn't have to chase six files.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]