CalvinKirs commented on code in PR #66717:
URL: https://github.com/apache/doris/pull/66717#discussion_r3829771254


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalMetaCache.java:
##########
@@ -86,41 +114,239 @@ public Table getPaimonTable(NameMapping nameMapping) {
 
     public PaimonSnapshotCacheValue getSnapshotCache(ExternalTable dorisTable) 
{
         NameMapping nameMapping = dorisTable.getOrBuildNameMapping();
-        return 
tableEntry.get(nameMapping.getCtlId()).get(nameMapping).getLatestSnapshotCacheValue();
+        MetaCacheEntry<NameMapping, PaimonTableCacheValue> tables = 
tableEntry.get(nameMapping.getCtlId());
+        PaimonTableCacheValue tableValue = tables.get(nameMapping);
+        PaimonSnapshot fence = loadLatestSnapshotFence(nameMapping, 
tableValue).getSnapshot();
+        if (!tables.isEffectivelyEnabled()) {
+            // Projections are keyed by the synthetic generation of a 
published table handle. An
+            // ineffective table entry publishes nothing, so nothing keyed by 
this load could ever
+            // be looked up again: serve it directly instead of churning the 
snapshot entry.
+            return executeForGeneration(tableValue, nameMapping,
+                    () -> latestSnapshotProjectionLoader.loadAtFence(
+                            nameMapping, fence, tableValue.getGeneration()));
+        }
+        // Order fence observations, not snapshot ids: a rollback moves the 
latest snapshot
+        // backwards, and a concurrent call may finish after a later 
observation (reversed
+        // completion). Either way the most recently observed fence is the one 
future lookups read.
+        long observation = fenceObservations.incrementAndGet();
+        PaimonSnapshotEntryKey key = PaimonSnapshotEntryKey.of(
+                nameMapping, fence, tableValue.getGeneration());
+        MetaCacheEntry<PaimonSnapshotEntryKey, PaimonSnapshotCacheValue> entry 
=
+                snapshotEntry.get(nameMapping.getCtlId());
+        AtomicBoolean loaded = new AtomicBoolean();
+        PaimonSnapshotCacheValue snapshotValue = entry.get(key,
+                ignored -> executeForGeneration(tableValue, nameMapping, () -> 
{
+                    loaded.set(true);
+                    return latestSnapshotProjectionLoader.loadAtFence(
+                            nameMapping, fence, tableValue.getGeneration());
+                }));
+        LatestFenceOwner owner = new LatestFenceOwner(nameMapping, 
tableValue.getGeneration());
+        ObservedFence latest = latestObservedFences.compute(owner, (ignored, 
current) ->
+                current == null || current.observation < observation ? new 
ObservedFence(observation, key) : current);
+        if (loaded.get()) {
+            retireSupersededLatestProjections(entry, owner, latest.key);
+        }
+        if (!isCurrentTableGeneration(nameMapping, 
tableValue.getGeneration())) {
+            entry.invalidateKeyIfSame(key, snapshotValue);
+            // A generation that is not published (rejected admission, 
replaced or invalidated
+            // mid-load) can never be observed again; drop the owner this call 
registered so
+            // persistently rejected tables cannot grow the map, and so a 
delayed old-generation
+            // load cannot resurrect an owner that catalog cleanup already 
removed.
+            latestObservedFences.remove(owner);
+        }
+        return snapshotValue;
+    }
+
+    /**
+     * Only the projection of the most recently observed latest fence of a 
table generation is
+     * reachable: every later call re-reads the fence and looks up that key. 
After a load, retire
+     * every other projection of the generation, including this load itself 
when a concurrent call
+     * observed a later fence and finished first, so a busy table never 
accumulates projections.
+     */
+    private static void retireSupersededLatestProjections(
+            MetaCacheEntry<PaimonSnapshotEntryKey, PaimonSnapshotCacheValue> 
entry,
+            LatestFenceOwner owner, PaimonSnapshotEntryKey latestKey) {
+        entry.invalidateIf(key -> owner.owns(key) && !key.equals(latestKey));
+    }
+
+    private void forgetObservedFences(NameMapping nameMapping, 
java.util.function.LongPredicate retiredGeneration) {
+        latestObservedFences.keySet().removeIf(owner -> 
owner.nameMapping.equals(nameMapping)
+                && retiredGeneration.test(owner.generation));
+    }
+
+    private static final class LatestFenceOwner {
+        private final NameMapping nameMapping;
+        private final long generation;
+
+        private LatestFenceOwner(NameMapping nameMapping, long generation) {
+            this.nameMapping = nameMapping;
+            this.generation = generation;
+        }
+
+        private boolean owns(PaimonSnapshotEntryKey key) {
+            return key.getTableGeneration() == generation && 
key.getNameMapping().equals(nameMapping);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (!(object instanceof LatestFenceOwner)) {
+                return false;
+            }
+            LatestFenceOwner that = (LatestFenceOwner) object;
+            return generation == that.generation && 
nameMapping.equals(that.nameMapping);
+        }
+
+        @Override
+        public int hashCode() {
+            return java.util.Objects.hash(nameMapping, generation);
+        }
+    }
+
+    private static final class ObservedFence {
+        private final long observation;
+        private final PaimonSnapshotEntryKey key;
+
+        private ObservedFence(long observation, PaimonSnapshotEntryKey key) {
+            this.observation = observation;
+            this.key = key;
+        }
     }
 
     public PaimonSnapshotCacheValue loadSnapshotProjection(ExternalTable 
dorisTable, Table effectiveTable) {
-        return 
latestSnapshotProjectionLoader.load(dorisTable.getOrBuildNameMapping(), 
effectiveTable);
+        NameMapping nameMapping = dorisTable.getOrBuildNameMapping();
+        return executeAuthenticated(nameMapping,
+                () -> latestSnapshotProjectionLoader.load(nameMapping, 
effectiveTable));
     }
 
     public PaimonSnapshotCacheValue loadLatestSnapshotFence(ExternalTable 
dorisTable) {
         NameMapping nameMapping = dorisTable.getOrBuildNameMapping();
-        Table table = 
tableEntry.get(nameMapping.getCtlId()).get(nameMapping).getPaimonTable();
-        return latestSnapshotProjectionLoader.loadFence(nameMapping, table);
+        PaimonTableCacheValue tableValue = 
tableEntry.get(nameMapping.getCtlId()).get(nameMapping);
+        return loadLatestSnapshotFence(nameMapping, tableValue);
     }
 
     public PaimonSnapshotCacheValue loadSnapshotAtFence(
             ExternalTable dorisTable, PaimonSnapshot fence) {
         NameMapping nameMapping = dorisTable.getOrBuildNameMapping();
-        return latestSnapshotProjectionLoader.loadAtFence(nameMapping, fence);
+        return executeAuthenticated(nameMapping,

Review Comment:
   Fixed in ad5a42de6c5: PaimonSnapshotCacheValue now carries the 
ExecutionAuthenticator captured from its table generation. Fence capture, 
cached and direct projections bind it; loadSnapshotAtFence (both overloads), 
the relation-options projection (which now captures the table generation once 
and derives the base handle and execution context from that single value) and 
generation-zero schema resolution all execute under the captured context and 
propagate it to the hydrated value, falling back to the catalog context only 
when nothing was captured. Regression 
testFenceAndRelationHydrationStayOnCapturedGenerationAcrossAlter covers capture 
-> ALTER (context swap + generation retirement) -> hydration across all these 
paths, with the post-ALTER catalog context asserting it is never consulted.



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