HIVE-29818: ServletSecurity needs a UGI cache - #6704
Conversation
The REST Catalog creates a fresh proxy UserGroupInformation per request via UserGroupInformation.createProxyUser. Hadoop's FileSystem.CACHE retains a reference to every such UGI (and its RPC/IPC resources), so under proxy authentication these short-lived UGIs accumulate and eventually exhaust memory in long-running deployments. Cache the proxy UGI in ServletSecurity with a bounded, idle-evicting Caffeine cache keyed by (realUser, loginUser). Evicted entries release their resources via FileSystem.closeAllForUGI. Two new config vars tune the cache: - metastore.catalog.servlet.ugi.cache.size (default 1000) - metastore.catalog.servlet.ugi.cache.expiry (default 3600s, 0 disables) Add TestServletSecurity covering per-user caching, distinct proxies per user, eviction-triggered FileSystem cleanup, and disabled expiry.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (4)
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:123
- The
UgiKeyfield namerealUseris misleading given the Javadoc: this value represents the effective/proxy user being impersonated, while the real user is the login user. Renaming the record components to something likeeffectiveUserandloginUser(orloginUserName) would reduce confusion and make logs likekey.realUser()accurate.
/**
* Cache key for a proxy {@link UserGroupInformation}. A proxy UGI is bound to both the effective user it
* impersonates and the server login user acting as its real user, so both participate in identity.
*/
record UgiKey(String realUser, String loginUser) {}
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:137
- Casting the configured cache size from
longtointcan truncate large configured values (or wrap negative), leading to an incorrectmaximumSizeand unexpected behavior. Consider keeping this as alongend-to-end (Caffeine’smaximumSizeaccepts along) and validating the value (e.g., reject negatives).
this.proxyUserCache = createCacheWithConfig(
MetastoreConf.getTimeVar(conf, MetastoreConf.ConfVars.CATALOG_SERVLET_UGI_CACHE_EXPIRY, TimeUnit.MILLISECONDS),
(int) MetastoreConf.getLongVar(conf, MetastoreConf.ConfVars.CATALOG_SERVLET_UGI_CACHE_SIZE));
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:170
- Using
.executor(Runnable::run)makes the removal listener run inline on the calling thread (likely a request thread). Since the listener callsFileSystem.closeAllForUGI, eviction can add noticeable latency or block request handling during bursts/evictions. Consider using the default executor or a dedicated bounded executor for removals so cleanup work doesn’t run on latency-sensitive threads.
Caffeine<UgiKey, UserGroupInformation> builder = Caffeine.<UgiKey, UserGroupInformation>newBuilder()
.maximumSize(maxSize)
.executor(Runnable::run)
.removalListener(cleanupListener);
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:196
- Logging the effective username at
INFOcan leak user identity information into logs and may be considered sensitive in some deployments. Since this log happens for cache misses (and potentially many distinct users), consider downgrading it toDEBUGor making it configurable/redacted.
UserGroupInformation getProxyUser(String userName, UserGroupInformation loginUser) {
return proxyUserCache.get(new UgiKey(userName, loginUser.getUserName()), key -> {
LOG.info("Creating proxy user for: {}", key.realUser());
return UserGroupInformation.createProxyUser(key.realUser(), loginUser);
});
}
ayushtkn
left a comment
There was a problem hiding this comment.
Thanx @henrib this overall looks good to me, there are some suppressed comments from co-pilot but I feel they are minor and maybe worth addressing, can u give a check once
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:123
The UgiKey field name realUser is misleading given the Javadoc: this value represents the effective/proxy user being impersonated, while the real user is the login user. Renaming the record components to something like effectiveUser and loginUser (or loginUserName) would reduce confusion and make logs like key.realUser() accurate.
/**
* Cache key for a proxy {@link UserGroupInformation}. A proxy UGI is bound to both the effective user it
* impersonates and the server login user acting as its real user, so both participate in identity.
*/
record UgiKey(String realUser, String loginUser) {}
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:137
Casting the configured cache size from long to int can truncate large configured values (or wrap negative), leading to an incorrect maximumSize and unexpected behavior. Consider keeping this as a long end-to-end (Caffeine’s maximumSize accepts a long) and validating the value (e.g., reject negatives).
this.proxyUserCache = createCacheWithConfig(
MetastoreConf.getTimeVar(conf, MetastoreConf.ConfVars.CATALOG_SERVLET_UGI_CACHE_EXPIRY, TimeUnit.MILLISECONDS),
(int) MetastoreConf.getLongVar(conf, MetastoreConf.ConfVars.CATALOG_SERVLET_UGI_CACHE_SIZE));
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:170
Using .executor(Runnable::run) makes the removal listener run inline on the calling thread (likely a request thread). Since the listener calls FileSystem.closeAllForUGI, eviction can add noticeable latency or block request handling during bursts/evictions. Consider using the default executor or a dedicated bounded executor for removals so cleanup work doesn’t run on latency-sensitive threads.
Caffeine<UgiKey, UserGroupInformation> builder = Caffeine.<UgiKey, UserGroupInformation>newBuilder()
.maximumSize(maxSize)
.executor(Runnable::run)
.removalListener(cleanupListener);
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ServletSecurity.java:196
Logging the effective username at INFO can leak user identity information into logs and may be considered sensitive in some deployments. Since this log happens for cache misses (and potentially many distinct users), consider downgrading it to DEBUG or making it configurable/redacted.
UserGroupInformation getProxyUser(String userName, UserGroupInformation loginUser) {
return proxyUserCache.get(new UgiKey(userName, loginUser.getUserName()), key -> {
LOG.info("Creating proxy user for: {}", key.realUser());
return UserGroupInformation.createProxyUser(key.realUser(), loginUser);
});
}
- Rename UgiKey.realUser to effectiveUser (impersonated user, not the Hadoop real user) - Keep cache size as long end-to-end, dropping the truncating int cast - Run removal-listener cleanup on ForkJoinPool.commonPool() instead of the request thread; add a test-only constructor to inject a synchronous executor - Downgrade proxy-user creation log from INFO to DEBUG
|



What changes were proposed in this pull request?
The REST Catalog creates a fresh proxy
UserGroupInformationper request viaUserGroupInformation.createProxyUser. Hadoop'sFileSystem.CACHEretains a reference to every such UGI (and its RPC/IPC resources), so under proxy authentication these short-lived UGIs accumulate and eventually exhaust memory in long-running deployments.This PR caches the proxy UGI in
ServletSecurityusing a bounded, idle-evicting Caffeine cache keyed by(realUser, loginUser). Evicted entries release their resources viaFileSystem.closeAllForUGI. Two new config vars tune the cache:metastore.catalog.servlet.ugi.cache.size(default 1000)metastore.catalog.servlet.ugi.cache.expiry(default 3600s, 0 disables expiry)A note in the code documents why eviction-while-in-use is not reference-counted: eviction is idle-based (
expireAfterAccess) and both the expiry window and max size are expected to be kept well above the longest operation / peak concurrent distinct users.Why are the changes needed?
To prevent the
OutOfMemoryErrorcaused by unbounded accumulation of proxy UGIs and their associated FileSystem/IPC resources in long-running REST Catalog deployments.Does this PR introduce any user-facing change?
Two new (optional) metastore configuration properties, both with sensible defaults.
How was this patch tested?
Added
TestServletSecuritycovering per-user caching, distinct proxies per user, eviction-triggeredFileSystem.closeAllForUGIcleanup, and disabled expiry. All 4 tests pass.