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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java:
##########
@@ -691,15 +785,55 @@ public void invalidateTableCache(ExternalTable 
dorisTable) {
         long catalogId = dorisTable.getCatalog().getId();
         // Typed table invalidation bypasses the name-based invalidateTable() 
entry point, so the
         // Lance access-cache retirement that used to happen there has to be 
repeated here.
-        invalidateLanceTableAccess(catalogId);
-        routeCatalogEngines(catalogId, cache -> safeInvalidate(
-                cache, catalogId, "invalidateTable", () -> 
cache.invalidateTable(dorisTable)));
+        try {
+            invalidateLanceTableAccess(catalogId);
+            routeCatalogEngines(catalogId, cache -> safeInvalidate(
+                    cache, catalogId, "invalidateTable", () -> 
cache.invalidateTable(dorisTable)));
+        } finally {
+            invalidateRowCountCache(dorisTable);
+        }
         if (LOG.isDebugEnabled()) {
             LOG.debug("invalid table cache for {}.{} in catalog {}", 
dorisTable.getRemoteDbName(),
                     dorisTable.getRemoteName(), 
dorisTable.getCatalog().getName());
         }
     }
 
+    /**
+     * Best-effort invalidation for a metadata event that carries the caller's 
DB/table spelling.
+     * Resolves canonical local identity, then fences engine caches and row 
counts at the narrowest
+     * scope that still covers the event; widens to the canonical database or 
catalog scope when the
+     * cached object has already been evicted, so caller spelling can never 
miss a canonical key.
+     */
+    public void invalidateTableByNameOrWider(long catalogId, String dbName, 
String tableName) {
+        Optional<ExternalDatabase<? extends ExternalTable>> db = 
getCachedDb(catalogId, dbName);
+        if (!db.isPresent()) {
+            invalidateCatalog(catalogId);

Review Comment:
   [P2] Keep cold known-database events scoped to that database. `getCachedDb` 
is cache-only, so ordinary DB-object eviction reaches this `invalidateCatalog` 
branch even while `getDbIdentityForReplay` still knows the canonical name and 
ID. A single TRUNCATE or whole-table/partition event then evicts unrelated hot 
DBs' engine entries and row counts; the new row-count pre-fence has the same 
catalog fallback. Resolve the retained canonical identity and invalidate that 
DB, using catalog scope only when the mapping is truly lost. Please cover a 
cold target DB with an unrelated hot DB primed. This is a separate 
event/TRUNCATE path from the already raised follower REFRESH and DROP TABLE 
cases.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java:
##########
@@ -1247,10 +1273,37 @@ public void unregisterDatabase(String dbName) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("unregister database [{}]", dbName);
         }
-        if (isInitialized()) {
-            metaCache.invalidate(dbName, Util.genIdByName(name, dbName));
+        if (!isInitialized()) {
+            Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(getId(), 
dbName);
+            return;
         }
-        Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(getId(), dbName);
+        String localDbName = getLocalDatabaseName(dbName, true);
+        if (localDbName == null) {
+            // A mode-2 remote-to-local mapping can disappear (for example 
after a names refresh)
+            // while the resident database object survives. The canonical key 
is then unknown, so
+            // treat the scope as unknown: retire every cached database object 
and flush the engine
+            // caches and row counts catalog-wide instead of evicting the 
wrong local key.
+            retireAllDatabaseObjectsWithoutEngineInvalidation();
+            
Env.getCurrentEnv().getExtMetaCacheMgr().invalidateCatalog(getId());
+            return;
+        }
+        metaCache.invalidate(localDbName, Util.genIdByName(name, localDbName));
+        Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(getId(), 
localDbName);

Review Comment:
   [P2] Preserve the known database ID through this DROP. 
`metaCache.invalidate` above removes the DB object and ID mapping, so the new 
name-based `invalidateDb` overload cannot recover its ID and falls back to 
invalidating every row count in the catalog. That evicts unrelated hot DBs and 
scans the global row-count cache for each ordinary DROP DATABASE; for a 
resident DB, the removal callback has already fenced this DB. Pass the computed 
canonical ID to the explicit-ID overload, and test that another DB's cached 
count survives.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonMetadataOps.java:
##########
@@ -408,12 +408,9 @@ public void afterDropTable(String dbName, String tblName) {
                     invalidatePaimonCatalogForUnresolvedReplay();
                 }
             } else {
-                // The database itself could not be resolved (for example a 
mode-2 mapping was lost
-                // before replay). Retire any retained legacy database object 
first so a same-name
-                // recreation cannot reuse its stale nested table-name cache, 
then flush the engine
-                // group; a failure in either is best-effort so the drop log 
is still written.
-                
dorisCatalog.retireAllDatabaseObjectsWithoutEngineInvalidation();
-                invalidatePaimonCatalogForUnresolvedReplay();
+                // A cold DB with a retained canonical mapping has a narrow 
invalidation target.
+                // Only a genuinely lost mapping requires catalog-wide 
hidden-object retirement.
+                dorisCatalog.invalidateColdDatabaseForReplay(dbName);

Review Comment:
   [P1] Retire Paimon's SDK cache on this cold known-DB path. 
`invalidateColdDatabaseForReplay` now routes name-based `invalidateDb`, but 
Paimon's name-based invalidation only walks Doris `tableEntry`; if a direct 
`getPaimonTable` populated SDK `CachingCatalog` without a Doris entry, it never 
calls the SDK database/catalog invalidator. The prior branch flushed that SDK 
cache, and the existing `testReplayDropInvalidatesSdkOnlyPaimonCache` primes 
exactly this state. After the remote DROP, the cached table handle can remain 
visible; cold REFRESH DB/TABLE replay takes the same route. Invalidate the SDK 
database scope independently of Doris table entries, or retain the Paimon 
catalog fallback, and test the cold DB-object case.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java:
##########
@@ -883,15 +892,33 @@ private void 
alterExternalCatalogPropsFenced(ExternalCatalog externalCatalog, Ca
             Integer[] sec = {metadataRefreshIntervalSec, 
metadataRefreshIntervalSec};
             Env.getCurrentEnv().getRefreshManager().addToRefreshMap(catalogId, 
sec);
         }
-        externalCatalog.modifyCatalogProps(newProps);
         // The commit reset the catalog's execution context and closed its SDK 
resources. Cached
         // base generations and projections are bound to the replaced context; 
retire them now so
         // the next statement loads a generation the planning fences accept, 
instead of retrying
-        // against an unplannable cached generation until managed refresh.
+        // against an unplannable cached generation until managed refresh. The 
properties are
+        // published before the reset's throwable cleanup, so retirement must 
run either way.
         Env currentEnv = Env.getCurrentEnv();
         ExternalMetaCacheMgr cacheMgr = currentEnv == null ? null : 
currentEnv.getExtMetaCacheMgr();
-        if (cacheMgr != null) {
-            
cacheMgr.onCatalogOperationalContextChanged(externalCatalog.getId());
+        try {
+            externalCatalog.modifyCatalogProps(newProps);
+        } catch (RuntimeException e) {
+            if (!isReplay) {
+                throw e;
+            }
+            // A follower must not terminate because a local connector cleanup 
failed while applying
+            // an already-durable ALTER record. The property publication and 
the derived-state
+            // transitions above are failure-safe, so the record is considered 
applied.
+            LOG.warn("Failed to complete local cleanup while replaying ALTER 
CATALOG for {}: {}",
+                    externalCatalog.getName(), e.getMessage(), e);
+        } finally {
+            if (cacheMgr != null) {
+                
cacheMgr.onCatalogOperationalContextChanged(externalCatalog.getId());

Review Comment:
   [P1] Publish an opening row-count fence before this ALTER exposes the new 
metadata generation. `modifyCatalogProps` resets the catalog and swaps its 
DB-object cache before this new final `onCatalogOperationalContextChanged` 
fence runs. A cold DB has no removal callback, so a concurrent query can 
rebuild a table under the new properties and read its old-target completed 
count during that interval; the later fence cannot retract the plan. Keep the 
completion fence as well, and add a latch test between `retireObjects()` and 
this call. The earlier property-commit thread covered a missing final fence; 
the explicit REFRESH CATALOG opening-fence thread is a different path.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalDatabase.java:
##########
@@ -146,8 +150,20 @@ public void resetMetaToUninitialized(boolean 
invalidateEngineCache) {
                 objectInvalidation.run();
             }
         }
-        if (invalidateEngineCache) {
-            Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(this);
+        try {
+            if (invalidateEngineCache) {
+                // Route through the typed overload: connector-specific caches 
(for example Paimon's
+                // table loader) are keyed by the database object and are not 
fully covered by the
+                // name-based scan in invalidateDb(long, String).
+                Env.getCurrentEnv().getExtMetaCacheMgr().invalidateDb(this);
+            }
+        } finally {
+            if (invalidateRowCountCache) {
+                // Independent of the routed invalidation: a connector cache 
failure (for example
+                // Paimon's CacheException) must not skip the row-count fence.
+                Env.getCurrentEnv().getExtMetaCacheMgr()
+                        .invalidateRowCountCache(extCatalog.getId(), getId());

Review Comment:
   [P1] Fence this database's row counts before swapping its table-object 
generation. Warm REFRESH DATABASE calls `resetMetaToUninitialized`, which 
installs the new table cache at `retireObjects()` and releases the DB monitor 
before the new row-count fence here runs, after routed engine invalidation. A 
concurrent query can load the new table generation and reuse the old completed 
count during that gap; the later fence cannot retract it. Retain this 
completion fence and add a latch test between `retireObjects()` and this call. 
The existing DB thread covers a throwing invalidation, while the catalog 
refresh thread covers a different reset path.



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