github-actions[bot] commented on code in PR #67417:
URL: https://github.com/apache/doris/pull/67417#discussion_r3910799318


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java:
##########
@@ -1266,16 +1272,14 @@ private String getLocalDatabaseName(String dbName, 
boolean isReplay) {
             finalName = dbName.toLowerCase();
         } else if (mode == 2) {
             // Mode 2: Case-insensitive comparison
-            finalName = lowerCaseToDatabaseName.get(dbName.toLowerCase());
-            if (finalName == null && !isReplay) {
-                // Refresh database list and try again
+            if (!isReplay) {
                 try {
-                    getFilteredDatabaseNames();
-                    finalName = 
lowerCaseToDatabaseName.get(dbName.toLowerCase());
+                    metaCache.listNames();

Review Comment:
   [P1] Keep the routing-map clear atomic with names invalidation
   
   `resetToUninitialized()` clears `lowerCaseToDatabaseName` under the catalog 
monitor, releases that monitor, and only afterward invalidates `metaCache` 
through `onRefreshCache()`. A concurrent `getDbNullable()` can reinitialize in 
that gap; this call then hits the still-complete old names entry without 
republishing the cleared map, so a following differently-cased mode-2 lookup 
misses. This is separate from the stale-loader thread because no load runs in 
this interleaving. Please order the map clear and names invalidation under the 
same initialization fence (or republish one atomic snapshot), and add a paused 
reset test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalDatabase.java:
##########
@@ -432,15 +437,10 @@ public DatabaseProperty getDbProperties() {
     public boolean isTableExist(String tableName) {
         String remoteTblName = tableName;
         if (this.isTableNamesCaseInsensitive()) {
+            metaCache.listNames();

Review Comment:
   [P1] Initialize the database before reading its names cache
   
   On a cold object-cache miss, a database returned by 
`ExternalCatalog.getDbNullable()` is newly constructed but has not run its own 
`makeSureInitialized()`, so its `metaCache` is still null. 
`CreateTableCommand.targetTableExists()` immediately calls the generic 
`DatabaseIf.isTableExist()` path; with `lower_case_table_names=2`, this new 
call therefore throws before reaching the connector. Please establish database 
initialization (and handle initialization failure) before using `metaCache`, 
and cover a cold mode-2 existence probe.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCache.java:
##########
@@ -74,24 +130,110 @@ public MetaCache(String name,
                 maxSize,
                 true,
                 null);
-        namesCache = namesCacheFactory.buildCache(namesCacheLoader, executor);
+        namesCache = namesCacheFactory.buildCache();
         // Use sync removal listener to prevent deadlock (removal listener 
calls invalidateAll)
         // NOTE: This cache should NOT use refreshAfterWrite, as it would 
become synchronous
         metaObjCache = 
objCacheFactory.buildCacheWithSyncRemovalListener(metaObjCacheLoader, 
removalListener);
     }
 
     public List<String> listNames() {
-        return 
Objects.requireNonNull(namesCache.get("")).stream().map(Pair::value).collect(Collectors.toList());
+        return 
getNames().stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    private List<Pair<String, String>> getNames() {
+        while (true) {
+            NamesCacheValue value = namesCache.getIfPresent("");
+            if (value == null || !value.complete) {
+                value = loadNames(false);
+                if (value == null) {

Review Comment:
   [P1] Bound retries when metadata keeps mutating
   
   Every `updateCache()` or `invalidate()` advances the cache-wide generation, 
and any advance during a slow enumeration makes `loadNames()` return null to 
this unconditional loop. With even one unrelated HMS event spanning each 
enumeration, a foreground `listNames()` can issue remote loads forever and 
never return; the old same-key Caffeine ordering let the load finish before 
applying the mutation. Please replay a bounded mutation journal (including 
deletions) into a completed snapshot, or otherwise provide a bounded 
convergence/fallback policy, and test several consecutive generation changes.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalDatabase.java:
##########
@@ -509,26 +509,21 @@ private String getLocalTableName(String tableName, 
boolean isReplay) {
             finalName = tableName.toLowerCase();
         }
         if (this.isTableNamesCaseInsensitive()) {
+            if (!isReplay) {
+                metaCache.listNames();

Review Comment:
   [P2] Use the known event entry before requiring a full listing
   
   On a cold mode-2 cache, `registerTable()` has already installed the table 
object and its lowercase routing entry, but 
`HMSExternalDatabase.registerTable()` immediately calls `getTableNullable()` to 
apply the event update time. This unconditional `listNames()` sees the 
incomplete entry and forces a full remote enumeration first; if that 
enumeration fails, the authoritative event itself fails even though the 
requested table is already cached. The base path consulted the specific routing 
entry before listing. Please allow a known incremental hit here and enumerate 
only on a miss, with a cold registration/loader-failure test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCache.java:
##########
@@ -74,24 +130,110 @@ public MetaCache(String name,
                 maxSize,
                 true,
                 null);
-        namesCache = namesCacheFactory.buildCache(namesCacheLoader, executor);
+        namesCache = namesCacheFactory.buildCache();
         // Use sync removal listener to prevent deadlock (removal listener 
calls invalidateAll)
         // NOTE: This cache should NOT use refreshAfterWrite, as it would 
become synchronous
         metaObjCache = 
objCacheFactory.buildCacheWithSyncRemovalListener(metaObjCacheLoader, 
removalListener);
     }
 
     public List<String> listNames() {
-        return 
Objects.requireNonNull(namesCache.get("")).stream().map(Pair::value).collect(Collectors.toList());
+        return 
getNames().stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    private List<Pair<String, String>> getNames() {
+        while (true) {
+            NamesCacheValue value = namesCache.getIfPresent("");
+            if (value == null || !value.complete) {
+                value = loadNames(false);
+                if (value == null) {
+                    continue;
+                }
+            }
+            boolean current;
+            synchronized (namesMutationLock) {
+                current = value.generation == namesGeneration.get();
+            }
+            if (current) {
+                scheduleNamesRefresh(value);
+                return value.names;
+            }
+        }
+    }
+
+    private NamesCacheValue loadNames(boolean forceRefresh) {
+        synchronized (namesLoadLock) {

Review Comment:
   [P1] Let the post-invalidation generation load independently
   
   `namesLoadLock` is held across connector I/O for automatic refreshes. If one 
starts before `invalidateAll()`, invalidation advances the generation and 
returns, but the next `listNames()` blocks here behind that obsolete refresh; 
if its RPC hangs, refresh can never restore metadata access. The base Caffeine 
refresh ran asynchronously, so an invalidated synchronous miss could load 
independently. Please deduplicate loads per generation (or otherwise 
cancel/abandon the obsolete generation), and test a second lookup while a 
pre-invalidation automatic refresh remains paused.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCache.java:
##########
@@ -74,24 +130,110 @@ public MetaCache(String name,
                 maxSize,
                 true,
                 null);
-        namesCache = namesCacheFactory.buildCache(namesCacheLoader, executor);
+        namesCache = namesCacheFactory.buildCache();
         // Use sync removal listener to prevent deadlock (removal listener 
calls invalidateAll)
         // NOTE: This cache should NOT use refreshAfterWrite, as it would 
become synchronous
         metaObjCache = 
objCacheFactory.buildCacheWithSyncRemovalListener(metaObjCacheLoader, 
removalListener);
     }
 
     public List<String> listNames() {
-        return 
Objects.requireNonNull(namesCache.get("")).stream().map(Pair::value).collect(Collectors.toList());
+        return 
getNames().stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    private List<Pair<String, String>> getNames() {
+        while (true) {
+            NamesCacheValue value = namesCache.getIfPresent("");
+            if (value == null || !value.complete) {
+                value = loadNames(false);
+                if (value == null) {
+                    continue;
+                }
+            }
+            boolean current;
+            synchronized (namesMutationLock) {
+                current = value.generation == namesGeneration.get();
+            }
+            if (current) {
+                scheduleNamesRefresh(value);
+                return value.names;
+            }
+        }
+    }
+
+    private NamesCacheValue loadNames(boolean forceRefresh) {
+        synchronized (namesLoadLock) {
+            List<Pair<String, String>> incompleteNames;
+            long loadGeneration;
+            synchronized (namesMutationLock) {
+                NamesCacheValue cached = namesCache.getIfPresent("");
+                if (!forceRefresh && cached != null && cached.complete
+                        && cached.generation == namesGeneration.get()) {
+                    return cached;
+                }
+                incompleteNames = cached != null && !cached.complete
+                        ? Lists.newArrayList(cached.names) : 
Lists.newArrayList();
+                loadGeneration = namesGeneration.get();
+            }
+            List<Pair<String, String>> loadedNames;
+            try {
+                loadedNames = 
Objects.requireNonNull(namesCacheLoader.load(""));
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new CompletionException(e);
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new CompletionException(e);
+            }
+            synchronized (namesMutationLock) {
+                if (loadGeneration != namesGeneration.get() || loadGeneration 
< minimumLoadGeneration) {
+                    return null;
+                }
+                List<Pair<String, String>> names = 
Lists.newArrayList(loadedNames);
+                incompleteNames.forEach(pair -> 
NameMutation.update(pair.key(), pair.value()).apply(names));
+                NamesCacheValue value = new 
NamesCacheValue(namesGeneration.get(), names, true);
+                namesCache.put("", value);
+                publishNames(value);
+                return value;
+            }
+        }
+    }
+
+    private void scheduleNamesRefresh(NamesCacheValue value) {
+        if (System.nanoTime() - value.writeNanos < namesRefreshAfterWriteNanos
+                || !namesRefreshRunning.compareAndSet(false, true)) {
+            return;
+        }
+        startNamesRefresh();
+    }
+
+    private void startNamesRefresh() {
+        try {
+            namesRefreshExecutor.execute(() -> {
+                try {
+                    loadNames(true);

Review Comment:
   [P2] Contain refresh failures inside the shared executor task
   
   `loadNames(true)` can throw a runtime or wrapped checked exception, but this 
raw `execute` task only has `finally`, so a normal connector refresh failure 
escapes and terminates a worker in the shared `commonRefreshExecutor`. Since 
the old value remains refresh-eligible, repeated accesses during an outage can 
repeatedly kill and replace workers shared by the legacy catalog/database 
caches and engine metadata caches. Please catch and log loader failures inside 
the runnable while clearing the flag and retaining the old value, and test that 
a failed refresh is retried without an uncaught task failure.



-- 
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]

Reply via email to