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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalDatabase.java:
##########
@@ -128,7 +128,7 @@ public void resetMetaToUninitialized() {
                 metaCache.invalidateAll();
             }
         }
-        
Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(extCatalog.getId(), 
getFullName());
+        
Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(extCatalog.getId(), 
getId(), getFullName());

Review Comment:
   [P1] Avoid a global row-count scan for every DB removed by a catalog reset. 
The catalog DB cache can hold 1,000 objects and its removal listener runs 
synchronously; `invalidateAll()` therefore calls this method once per DB, and 
each call now scans both global row-count registries under 
`publicationLock.writeLock()`. With up to 100,000 row-count entries (including 
other catalogs), a scheduled/full catalog refresh can do tens of millions of 
checks while repeatedly blocking every row-count reader, before the 
catalog-scope scan runs as well. Please bulk-fence the catalog once while 
suppressing per-DB scans during catalog reset, or maintain scope indexes.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java:
##########
@@ -669,15 +736,32 @@ public ExternalRowCountCache getRowCountCache() {
     }
 
     public void invalidateTableCache(ExternalTable dorisTable) {
-        invalidateTable(dorisTable.getCatalog().getId(),
-                dorisTable.getDbName(),
-                dorisTable.getName());
+        long catalogId = dorisTable.getCatalog().getId();
+        try {
+            routeCatalogEngines(catalogId, cache -> safeInvalidate(
+                    cache, catalogId, "invalidateTableCache",
+                    () -> cache.invalidateTable(catalogId, 
dorisTable.getDbName(), dorisTable.getName())));
+        } finally {
+            invalidateRowCountCache(dorisTable);

Review Comment:
   [P1] Make this row-count fence reachable from cold TRUNCATE replay. A 
follower dispatches `TruncateTableInfo` to 
`HiveMetadataOps.afterTruncateTable`, but that callback uses cache-only 
DB/table lookups and silently does nothing when the table object has been 
evicted. The table-object cache holds 1,000 entries with no removal listener 
while row counts retain up to 100,000, so the old count can survive; TRUNCATE 
preserves the table ID, and subsequent reads reuse it. Add a name-based 
fallback for an absent table and a cold follower replay test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalRowCountCache.java:
##########
@@ -87,6 +103,59 @@ protected Optional<Long> doLoad(RowCountKey rowCountKey) {
         }
     }
 
+    private final class InvalidationAwareLoader implements 
AsyncCacheLoader<RowCountKey, Optional<Long>> {
+        private final RowCountCacheLoader delegate;
+
+        private InvalidationAwareLoader(RowCountCacheLoader delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public CompletableFuture<Optional<Long>> asyncLoad(RowCountKey key, 
Executor executor) {
+            return loadWithInvalidationFence(key, executor, () -> 
delegate.doLoad(key));
+        }
+    }
+
+    private static final class LoadFence {
+        private boolean invalidated;
+    }
+
+    private CompletableFuture<Optional<Long>> loadWithInvalidationFence(
+            RowCountKey key, Executor executor, Supplier<Optional<Long>> 
loader) {
+        LoadFence fence = new LoadFence();
+        publicationLock.readLock().lock();
+        try {
+            inFlightLoads.compute(key, (ignored, fences) -> {
+                Set<LoadFence> currentFences = fences == null ? 
ConcurrentHashMap.newKeySet() : fences;
+                currentFences.add(fence);
+                return currentFences;
+            });
+        } finally {
+            publicationLock.readLock().unlock();
+        }
+
+        CompletableFuture<Optional<Long>> publishedFuture = new 
CompletableFuture<>();
+        CompletableFuture.supplyAsync(loader, executor).whenComplete((value, 
throwable) -> {

Review Comment:
   [P1] Remove this fence when task submission is rejected. The fence is 
inserted before `supplyAsync`, but the only removal is in the future's 
`whenComplete`; if `executor.execute` throws, no future exists and the fence 
remains forever. The production row-count pool has a bounded queue and a 
zero-second rejection policy while this cache permits 100,000 entries, so a 
load burst followed by retries can grow `inFlightLoads` without bound and make 
every scope invalidation scan retained junk. Please clean up this exact fence 
on synchronous submission failure and add a rejecting-executor test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalRowCountCache.java:
##########
@@ -87,6 +103,59 @@ protected Optional<Long> doLoad(RowCountKey rowCountKey) {
         }
     }
 
+    private final class InvalidationAwareLoader implements 
AsyncCacheLoader<RowCountKey, Optional<Long>> {
+        private final RowCountCacheLoader delegate;
+
+        private InvalidationAwareLoader(RowCountCacheLoader delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public CompletableFuture<Optional<Long>> asyncLoad(RowCountKey key, 
Executor executor) {
+            return loadWithInvalidationFence(key, executor, () -> 
delegate.doLoad(key));
+        }
+    }
+
+    private static final class LoadFence {
+        private boolean invalidated;
+    }
+
+    private CompletableFuture<Optional<Long>> loadWithInvalidationFence(
+            RowCountKey key, Executor executor, Supplier<Optional<Long>> 
loader) {
+        LoadFence fence = new LoadFence();
+        publicationLock.readLock().lock();
+        try {
+            inFlightLoads.compute(key, (ignored, fences) -> {

Review Comment:
   [P1] Track the full scope on each in-flight fence. This map uses 
`RowCountKey.equals`, which compares only `tableId`, but catalog/db 
invalidation later filters the stored key's `catalogId`/`dbId`. If a same-name 
catalog is dropped and recreated while an old load remains in flight, a 
new-generation fence joins the equal old map entry without replacing its 
representative key; a later invalidation of the new catalog misses that fence, 
and Caffeine can republish its refresh after eviction. Key this registry by 
full identity (or filter scope stored on each fence) and cover same-table-ID 
catalog recreation.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java:
##########
@@ -452,6 +458,7 @@ public void removeCatalogPermanently(long catalogId) {
                 }
             }
         } finally {
+            rowCountCache.invalidateCatalog(catalogId);

Review Comment:
   [P1] Apply this fence to tentative-property rollback as well. Legacy 
validators publish `newProps` before `checkProperties`, while lock-free catalog 
lookup and lazy initialization do not take this lifecycle stripe. A concurrent 
row-count load can therefore initialize the catalog/client and start against 
the temporary endpoint. If validation fails, `rollbackCatalogProperties` below 
only restores `CatalogProperty` and routed engine groups; it neither resets 
candidate-built catalog/DB/table state nor fences row counts, so the rejected 
target's client/count can remain under unchanged IDs. Reset that legacy state 
and add the catalog-scope completion fence during rollback, with a latching 
regression test.



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