This is an automated email from the ASF dual-hosted git repository.

924060929 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 90540d32bcb [fix](hive) invalidate stale file cache on partition 
refresh when partition cache misses (#65334)
90540d32bcb is described below

commit 90540d32bcb7cdef54aff5502d41bd627a3583a0
Author: 924060929 <[email protected]>
AuthorDate: Mon Jul 13 09:55:41 2026 +0800

    [fix](hive) invalidate stale file cache on partition refresh when partition 
cache misses (#65334)
    
    Problem Summary:
    
    Partition-level refresh / invalidation
    (`HiveExternalMetaCache.invalidatePartitionCache`) returned early when
    the target partition was absent from the partition metadata cache —
    evicted under `max_hive_partition_cache_num` pressure, or simply never
    loaded — and left that partition's file listing cache untouched.
    
    The partition cache (`partition`) and the file listing cache (`file`)
    are independent caches with independent size limits
    (`max_hive_partition_cache_num` vs `max_external_file_cache_num`) and
    evict separately, so the partition entry can be evicted while its file
    listing survives. In that state a partition-level refresh cannot rebuild
    the exact `FileCacheKey` (it needs the partition path / input format
    that only the cached `HivePartition` carries), so the old code gave up
    and the stale file listing remained. A later query would reload the
    partition metadata and re-hit the same stale file listing — i.e. "the
    partition exists, but Doris keeps seeing the old files/data".
    
    Fix: when the exact `FileCacheKey` cannot be rebuilt, fall back to a
    predicate-based invalidation on the file cache keyed by `table id +
    partition values`, so the stale listing for that partition is cleared
    without over-invalidating other partitions. This mirrors table-level
    invalidation, which already invalidates the file cache by predicate.
    
    The partition invalidation coordinator is also decoupled from
    `ExternalTable` to `NameMapping` (mirroring the existing
    `invalidateTableCache(NameMapping)`) so the path can be unit tested
    directly.
    
    This behavior has existed since partition-level invalidation was first
    introduced in #15702 (the file cache was only invalidated inside the `if
    (partition != null)` branch), and was later carried into the current
    `HiveExternalMetaCache` by the #60937 cache-framework refactor.
    
    ### Release note
    
    Fix a Hive external catalog consistency issue where a partition-level
    refresh could leave a stale file listing cached when the partition
    metadata cache had been evicted, causing queries to keep seeing outdated
    files for that partition.
---
 .../datasource/hive/HiveExternalMetaCache.java     | 41 +++++++-------
 .../datasource/hive/HiveMetaStoreCacheTest.java    | 62 ++++++++++++++++++++++
 2 files changed, 85 insertions(+), 18 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveExternalMetaCache.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveExternalMetaCache.java
index abc68febcba..54a6eb75b98 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveExternalMetaCache.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveExternalMetaCache.java
@@ -582,7 +582,11 @@ public class HiveExternalMetaCache extends 
AbstractExternalMetaCache {
     }
 
     public void invalidatePartitionCache(ExternalTable dorisTable, String 
partitionName) {
-        partitionCacheCoordinator.invalidatePartitionCache(dorisTable, 
partitionName);
+        invalidatePartitionCache(dorisTable.getOrBuildNameMapping(), 
partitionName);
+    }
+
+    public void invalidatePartitionCache(NameMapping nameMapping, String 
partitionName) {
+        partitionCacheCoordinator.invalidatePartitionCache(nameMapping, 
partitionName);
     }
 
     /**
@@ -614,33 +618,34 @@ public class HiveExternalMetaCache extends 
AbstractExternalMetaCache {
     }
 
     private final class PartitionCacheCoordinator {
-        private void invalidatePartitionCache(ExternalTable dorisTable, String 
partitionName) {
-            NameMapping nameMapping = dorisTable.getOrBuildNameMapping();
+        private void invalidatePartitionCache(NameMapping nameMapping, String 
partitionName) {
             long catalogId = nameMapping.getCtlId();
 
-            MetaCacheEntry<PartitionValueCacheKey, HivePartitionValues> 
partitionValuesEntry =
-                    partitionValuesEntryIfInitialized(catalogId);
             MetaCacheEntry<PartitionCacheKey, HivePartition> partitionEntry = 
partitionEntryIfInitialized(catalogId);
             MetaCacheEntry<FileCacheKey, FileCacheValue> fileEntry = 
fileEntryIfInitialized(catalogId);
-            if (partitionValuesEntry == null || partitionEntry == null || 
fileEntry == null) {
+            if (partitionEntry == null || fileEntry == null) {
                 return;
             }
 
             long tableId = Util.genIdByName(nameMapping.getLocalDbName(), 
nameMapping.getLocalTblName());
-            PartitionValueCacheKey key = new 
PartitionValueCacheKey(nameMapping, null);
-            HivePartitionValues partitionValues = 
partitionValuesEntry.getIfPresent(key);
-            if (partitionValues == null) {
-                return;
-            }
-
-            List<String> values = 
partitionValues.nameToPartitionValues.get(partitionName);
-            if (values == null) {
-                return;
-            }
+            // Derive the partition values directly from the partition name 
(the partition-values cache is
+            // populated the same way, via HiveUtil.toPartitionValues) instead 
of reading them from that
+            // cache, so file cache invalidation still runs when the table's 
partition-values cache entry has
+            // been evicted while its file listings are still cached.
+            List<String> values = HiveUtil.toPartitionValues(partitionName);
 
             PartitionCacheKey partKey = new PartitionCacheKey(nameMapping, 
values);
             HivePartition partition = partitionEntry.getIfPresent(partKey);
             if (partition == null) {
+                // Partition metadata cache miss: the exact FileCacheKey 
cannot be rebuilt here because it
+                // needs the partition path and input format carried by 
HivePartition. Invalidate this
+                // table's cached file listings for the partition by (table id 
+ partition values). Scoping
+                // by table id is intentional: matching partition values alone 
would also drop other tables'
+                // listings that merely share the same partition value names 
(e.g. dt=...) at a different
+                // location, forcing needless re-listing. The exact-key path 
below (taken when the partition
+                // is cached) already clears a listing regardless of which 
table id populated it.
+                fileEntry.invalidateIf(k -> k.isSameTable(tableId) && 
Objects.equals(k.getPartitionValues(), values));
+                partitionEntry.invalidateKey(partKey);
                 return;
             }
 
@@ -685,7 +690,7 @@ public class HiveExternalMetaCache extends 
AbstractExternalMetaCache {
                 List<String> modifiedPartNames,
                 List<String> newPartNames) {
             for (String partitionName : modifiedPartNames) {
-                invalidatePartitionCache(table, partitionName);
+                invalidatePartitionCache(table.getOrBuildNameMapping(), 
partitionName);
             }
 
             List<String> mergedPartNames = 
Lists.newArrayList(modifiedPartNames);
@@ -772,7 +777,7 @@ public class HiveExternalMetaCache extends 
AbstractExternalMetaCache {
                 nameToPartitionValues.remove(partitionName);
 
                 if (invalidPartitionCache) {
-                    invalidatePartitionCache(dorisTable, partitionName);
+                    invalidatePartitionCache(nameMapping, partitionName);
                 }
             }
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveMetaStoreCacheTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveMetaStoreCacheTest.java
index 931c7ac7e5a..62f70b77982 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveMetaStoreCacheTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveMetaStoreCacheTest.java
@@ -86,6 +86,68 @@ public class HiveMetaStoreCacheTest {
         Assertions.assertEquals(0, entrySize(partitionValuesCache));
     }
 
+    @Test
+    public void 
testInvalidatePartitionCacheClearsStaleFileCacheOnPartitionMiss() {
+        ThreadPoolExecutor executor = 
ThreadPoolManager.newDaemonFixedThreadPool(
+                1, 1, "refresh", 1, false);
+        ThreadPoolExecutor listExecutor = 
ThreadPoolManager.newDaemonFixedThreadPool(
+                1, 1, "file", 1, false);
+        try {
+            HiveExternalMetaCache cache = new HiveExternalMetaCache(executor, 
listExecutor);
+            cache.initCatalog(0, new HashMap<>());
+
+            MetaCacheEntry<HiveExternalMetaCache.FileCacheKey, 
HiveExternalMetaCache.FileCacheValue> fileCache =
+                    cache.entry(0, HiveExternalMetaCache.ENTRY_FILE,
+                            HiveExternalMetaCache.FileCacheKey.class,
+                            HiveExternalMetaCache.FileCacheValue.class);
+
+            String dbName = "db";
+            String tbName = "tb";
+            NameMapping nameMapping = NameMapping.createForTest(dbName, 
tbName);
+            long catalogId = nameMapping.getCtlId();
+            long tableId = Util.genIdByName(dbName, tbName);
+            long otherTableId = Util.genIdByName(dbName, "tb2");
+
+            String targetPartName = "dt=2024-01-01";
+            List<String> targetValues = 
Collections.singletonList("2024-01-01");
+            String otherPartName = "dt=2024-01-02";
+            List<String> otherValues = Collections.singletonList("2024-01-02");
+
+            // Neither the `partition` cache nor the `partition_values` cache 
is populated for this table,
+            // simulating entries that were evicted or never loaded. 
invalidatePartitionCache must still
+            // clear the stale file listing: it derives the partition values 
from the partition name and
+            // cannot rebuild the exact FileCacheKey (which needs the 
partition path / input format).
+            HiveExternalMetaCache.FileCacheKey targetFileKey = new 
HiveExternalMetaCache.FileCacheKey(
+                    catalogId, tableId, "/wh/db/tb/" + targetPartName, "orc", 
targetValues);
+            // Same table, a different partition -> must be kept.
+            HiveExternalMetaCache.FileCacheKey otherPartFileKey = new 
HiveExternalMetaCache.FileCacheKey(
+                    catalogId, tableId, "/wh/db/tb/" + otherPartName, "orc", 
otherValues);
+            // A different table that merely shares the same partition value 
names at a different location
+            // -> must be kept (the fallback is intentionally scoped by table 
id, not by values alone).
+            HiveExternalMetaCache.FileCacheKey otherTableFileKey = new 
HiveExternalMetaCache.FileCacheKey(
+                    catalogId, otherTableId, "/wh/db/tb2/" + targetPartName, 
"orc", targetValues);
+            fileCache.put(targetFileKey, new 
HiveExternalMetaCache.FileCacheValue());
+            fileCache.put(otherPartFileKey, new 
HiveExternalMetaCache.FileCacheValue());
+            fileCache.put(otherTableFileKey, new 
HiveExternalMetaCache.FileCacheValue());
+            Assertions.assertEquals(3, entrySize(fileCache));
+
+            // Partition-level refresh for the target partition. Even though 
its `partition` cache entry
+            // is missing, the stale file listing for that partition must 
still be invalidated.
+            cache.invalidatePartitionCache(nameMapping, targetPartName);
+
+            Assertions.assertNull(fileCache.getIfPresent(targetFileKey),
+                    "stale file cache for the refreshed partition must be 
cleared even on partition cache miss");
+            Assertions.assertNotNull(fileCache.getIfPresent(otherPartFileKey),
+                    "file cache for other partitions of the same table must 
NOT be affected");
+            Assertions.assertNotNull(fileCache.getIfPresent(otherTableFileKey),
+                    "file cache for other tables sharing the same partition 
values must NOT be affected");
+            Assertions.assertEquals(2, entrySize(fileCache));
+        } finally {
+            executor.shutdownNow();
+            listExecutor.shutdownNow();
+        }
+    }
+
     @Test
     public void testDefaultSpecsFollowConfig() {
         ThreadPoolExecutor executor = 
ThreadPoolManager.newDaemonFixedThreadPool(


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to