Indhumathi Muthumurugesh created HIVE-29708:
-----------------------------------------------
Summary: HMS OOM: FileSystem.CACHE leaks DistributedFileSystem
instances in TxnUtils.findUserToRunAs() when runAs proxy user lacks HDFS
execute permission
Key: HIVE-29708
URL: https://issues.apache.org/jira/browse/HIVE-29708
Project: Hive
Issue Type: Bug
Affects Versions: 4.2.0
Reporter: Indhumathi Muthumurugesh
h3. Background: The {{runAs}} User in Hive Compaction
When the Compactor Initiator determines that an ACID table needs compaction, it
must decide {*}which user to run the compaction job as{*}. This is done via
{{{}TxnUtils.findUserToRunAs(){}}}, which is called for every potential
compaction candidate on every Initiator cycle.
The method works as follows:
{code:java}
TxnUtils.findUserToRunAs(location, table, conf)
│
├── Step 1: Check if hive.compactor.run.as.user is configured
│ └── If set → return that user immediately (no HDFS access needed)
│
├── Step 2: Try as the HMS server user (e.g. "hive")
│ └── fs.getFileStatus(tableLocation)
│ ├── SUCCESS → return directory owner as runAs user
│ └── AccessControlException → fall through to Step 3
│
└── Step 3: Try as the table owner (proxy user)
├── UserGroupInformation.createProxyUser(table.getOwner(), loginUser)
└── ugi.doAs(() -> {
FileSystem proxyFs = p.getFileSystem(conf) ← BUG: CACHE entry
added here
proxyFs.getFileStatus(tableLocation)
├── SUCCESS → return owner, then closeAllForUGI (cleanup)
└── Exception → closeAllForUGI SKIPPED ← THE LEAK
}) {code}
The proxy user path (Step 3) is entered when:
* hive.compactor.run.as.user is NOT configured (default — no fixed runAs user)
* The HMS server user does NOT have permission to stat the table's storage
location
This is a common scenario in multi-tenant clusters where:
* ACID tables are created by end users in HDFS paths outside the managed
warehouse
* The HDFS parent directory has restrictive permissions (e.g., drwx------) not
accessible to the HMS service account
* The HMS service account is not a superuser and does not have blanket read
access to all data directories
h3. Problem Statement
HiveMetaStore (HMS) processes experience periodic OutOfMemoryError crashes over
time. The crash is caused by {{org.apache.hadoop.conf.Configuration}} objects
accumulating unboundedly in the heap via
{{{}org.apache.hadoop.fs.FileSystem.CACHE{}}}, a static {{HashMap}} that caches
{{DistributedFileSystem}} instances.
The issue is triggered by the Compactor Initiator specifically in the *proxy
user fallback path* of {{{}TxnUtils.findUserToRunAs(){}}}. Disabling compaction
({{{}hive.compactor.initiator.on=false{}}}) or configuring
{{hive.compactor.run.as.user}} prevents the OOM.
h3. Heap Dump Analysis
The {{FileSystem.CACHE}} retains the leaked {{DistributedFileSystem}} instances
as GC roots:
{code:java}
org.apache.hadoop.conf.Configuration (×N*2 instances)
└── org.apache.hadoop.hdfs.DFSClient.conf
└── org.apache.hadoop.hdfs.DistributedFileSystem.dfs (×N instances)
└── java.util.HashMap$Node.value
└── java.util.HashMap (N elements)
└── org.apache.hadoop.fs.FileSystem$Cache.map
└── static CACHE in
org.apache.hadoop.fs.FileSystem
└── [GC ROOT — never collected] {code}
Each leaked DistributedFileSystem retains approximately 300-400 KB of heap (2×
Configuration objects, DFSClient, SaslDataTransferClient,
SaslPropertiesResolver). After weeks of uptime with many affected tables, this
accumulates to gigabytes of retained heap.
h3. Steps to Reproduce
*Prerequisites:*
* ACID table under an HDFS directory where neither the HMS server user nor the
table owner has execute permission on the parent
*Setup:*
{code:java}
{code}
*# 1. Create a restricted HDFS parent directory
hdfs dfs -mkdir /test_restricted_parent
hdfs dfs -chown hdfs:hdfs /test_restricted_parent
hdfs dfs -chmod 700 /test_restricted_parent
# Now only the hdfs superuser can traverse this directory#*
*2. Create the table location inside
hdfs dfs -mkdir /test_restricted_parent/test_table
hdfs dfs -chmod 777 /test_restricted_parent/test_table*
{code:java}
{code}
*-- 3. Create ACID table pointing to restricted location
-- (or use ALTER TABLE SET LOCATION after creation)
CREATE TABLE test_leak (id INT)
STORED AS ORC
LOCATION 'hdfs:///test_restricted_parent/test_table'
TBLPROPERTIES ('transactional'='true');*
*-- 4. Insert enough rows to create delta files (exceed compaction threshold)
INSERT INTO test_leak VALUES (1);
INSERT INTO test_leak VALUES (2);
... -- repeat 11+ times to exceed hive.compactor.delta.num.threshold
(default=10)*
{code:java}
{code}
*<!-- 5. Speed up Initiator for faster reproduction -->
<property>
<name>hive.compactor.check.interval</name>
<value>30s</value>
</property>
<property>
<name>hive.compactor.initiator.on</name>
<value>true</value>
</property>*
*Observation:*
On each Initiator cycle, the following sequence occurs in
{{{}TxnUtils.findUserToRunAs(){}}}:
# {{fs.getFileStatus(tablePath)}} → {{AccessControlException}} (HMS user lacks
permission)
# {{createProxyUser(tableOwner)}} → new {{Subject}} object created
# Inside {{{}doAs(){}}}: {{p.getFileSystem(conf)}} → new
{{DistributedFileSystem}} added to {{FileSystem.CACHE}}
# {{proxyFs.getFileStatus(tablePath)}} → {{AccessControlException}} (table
owner also lacks permission)
# {{doAs()}} throws → {{closeAllForUGI(ugi)}} *never called* → cache entry
leaks permanently
{{FileSystem.CACHE}} size grows by 1 per affected table per Initiator cycle.
h3. Reproduction Results
With 500 ACID tables in a restricted directory and HMS heap set to 512 MB:
||Time (elapsed)||CACHE leaked entries||Heap used||Heap free||
|0:00|461|359 MB|121 MB|
|0:31|561|353 MB|158 MB|
|2:04|916|477 MB|35 MB|
|3:21|1,166|509 MB|3 MB|
|3:36|1,166|512 MB|0 MB → *OutOfMemoryError*|
* Growth rate: ~200 entries/minute → ~1.3 MB/second
* Time to OOM: *~3.5 minutes* (with 500 affected tables, 30s interval)
--
This message was sent by Atlassian Jira
(v8.20.10#820010)