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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonUtils.java:
##########
@@ -64,6 +64,11 @@ public static PaimonSnapshotCacheValue 
getSnapshotCacheValue(Optional<MvccSnapsh
 
     public static PaimonSchemaCacheValue getSchemaCacheValue(ExternalTable 
dorisTable,
             PaimonSnapshotCacheValue snapshotValue) {
+        if (snapshotValue.getTableGeneration() > 0L) {

Review Comment:
   [P1] Resolve generation-zero fences from their retained table
   
   `loadLatestSnapshotFence()`, `loadSnapshotAtFence()`, and explicit 
historical projections retain the exact physical table but leave 
`tableGeneration` at zero. This branch sends those values to the name/schema-id 
overload, which reloads the current base table instead of reading schema 
history from `snapshotValue.getSnapshot().getTable()`. If the table is 
dropped/recreated under the same name and schema IDs restart between fence 
capture and descriptor construction, Doris can bind the replacement 
schema/partition columns to the old retained scan handle. Please use the 
retained-table overload for generation zero as well (it already performs an 
authenticated uncached load), or carry a generation through these fences, and 
test a same-name recreation with a reused schema ID.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCacheEntry.java:
##########
@@ -198,62 +551,713 @@ public MetaCacheEntryStats stats() {
                 failureCount,
                 totalLoadTime,
                 totalLoadCount == 0 ? 0D : (double) totalLoadTime / 
totalLoadCount,
-                cacheStats.evictionCount(),
+                MetaCacheWeightUtils.saturatedAdd(
+                        cacheStats.evictionCount(), localEvictionCount.get()),
                 invalidateCount.get(),
                 lastLoadSuccessTimeMs.get(),
                 lastLoadFailureTimeMs.get(),
-                lastError.get());
+                lastError.get(),
+                weightBounded,
+                weightBounded ? cacheSpec.getMaxWeight().getAsLong() : -1L,
+                weightBounded ? entryBudget.getUsedWeight() : -1L,
+                weightBounded ? MetaCacheWeightUtils.saturatedAdd(
+                        automaticEvictionWeight.get(), 
localEvictionWeight.get()) : -1L,
+                weightBounded ? weightAdmissionRejectedCount.get() : -1L,
+                weightBounded ? entryBudget.getCatalogMaxWeight() : -1L,
+                weightBounded ? entryBudget.getCatalogUsedWeight() : -1L,
+                weightBounded ? entryBudget.getGlobalMaxWeight() : -1L,
+                weightBounded ? entryBudget.getGlobalUsedWeight() : -1L,
+                weightBounded ? lastWeightRejectReason.get() : "");
+    }
+
+    public boolean isWeightBounded() {
+        return weightBounded;
+    }
+
+    /** True when this entry stores values at all (enabled with a positive 
capacity or weight). */
+    public boolean isEffectivelyEnabled() {
+        return effectiveEnabled;
+    }
+
+    /**
+     * True when publication sizing can lead to a weighted admission: an entry 
may be configured
+     * with a weight bound yet be ineffective (max-weight 0, disabled, zero 
TTL or capacity), in
+     * which case preparing values for publication is pure waste.
+     */
+    public boolean isWeightAccounting() {
+        return weightBounded && effectiveEnabled;
+    }
+
+    private AdmissionResult admitWeightedValue(
+            K key, V value, @Nullable V expectedCurrent, boolean 
requireExpected,
+            @Nullable KeyMutationToken expectedMutation, long 
expectedReservationGeneration,
+            boolean advanceMutationOnAdmission) {
+        if (closed.get()) {
+            return AdmissionResult.DISABLED;
+        }
+        MetaCacheSizeEstimate estimate;
+        try {
+            estimate = Objects.requireNonNull(sizeEstimator.estimate(key, 
value), "size estimate");
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (RuntimeException e) {
+            rejectWeight("invalid_estimate");
+            return AdmissionResult.REJECTED;
+        }
+        if (!estimate.isComplete()) {
+            rejectWeight(estimate.getIncompleteReason());
+            return AdmissionResult.REJECTED;
+        }
+
+        long estimatedPayloadBytes = estimate.getBytes();
+        // A retained non-null key/value plus Caffeine node can never consume 
zero bytes. Treat a
+        // complete zero as an estimator contract violation so an omitted 
formula cannot bypass
+        // every quota and admit an unbounded number of zero-weight entries.
+        if (estimatedPayloadBytes == 0L) {
+            rejectWeight("invalid_estimate");
+            return AdmissionResult.REJECTED;
+        }
+        long newWeight = MetaCacheWeightUtils.saturatedAdd(
+                estimatedPayloadBytes, FIXED_ENTRY_ACCOUNTING_OVERHEAD_BYTES);
+        synchronized (admissionLock) {
+            if (closed.get()) {
+                return AdmissionResult.DISABLED;
+            }
+            if (expectedMutation != null && !isKeyMutationCurrent(key, 
expectedMutation)) {
+                return AdmissionResult.NOT_CURRENT;
+            }
+            V oldValue = data.asMap().get(key);
+            ReservationRecord record = reservations.get(key);
+            if (expectedReservationGeneration >= 0L
+                    && (record == null || record.generation != 
expectedReservationGeneration)) {
+                return AdmissionResult.NOT_CURRENT;
+            }
+            if (requireExpected && oldValue != expectedCurrent) {
+                return AdmissionResult.NOT_CURRENT;
+            }
+            if (oldValue == null && record != null) {
+                // The previous generation was already removed by Caffeine; if 
that removal was
+                // an eviction whose asynchronous cleanup has not run yet, 
account it here.
+                if (pendingEvictionGenerations.remove(key, record.generation)) 
{
+                    automaticEvictionWeight.accumulateAndGet(
+                            record.weight, MetaCacheWeightUtils::saturatedAdd);
+                }
+                reservations.remove(key, record);
+                record.reservation.release();
+                record = null;
+            }
+            if (oldValue != null && (record == null || !record.published)) {
+                rejectWeight("missing_reservation");
+                return AdmissionResult.REJECTED;
+            }
+
+            if (record == null) {
+                Optional<AdmissionReservation> reservation = 
reserveWithLocalEviction(key, newWeight);
+                if (!reservation.isPresent()) {
+                    rejectWeight("budget_exceeded");
+                    return AdmissionResult.REJECTED;
+                }
+                ReservationRecord newRecord = new ReservationRecord(
+                        newWeight, reservation.get(), 
nextReservationGeneration());
+                if (advanceMutationOnAdmission) {
+                    advanceKeyMutation(key);
+                }
+                reservations.put(key, newRecord);
+                try {
+                    beforeWeightedCachePutForTest(key, value);
+                    data.put(key, value);
+                    if (reservations.get(key) == newRecord && 
data.asMap().get(key) == value) {
+                        newRecord.published = true;
+                        notifyReplacement(key, null, value);
+                    }
+                    return AdmissionResult.ADMITTED;
+                } catch (RuntimeException | Error e) {
+                    reservations.remove(key, newRecord);
+                    newRecord.reservation.release();
+                    throw e;
+                }
+            }
+
+            ReservationRecord previousRecord = record;
+            long reservedWeight = Math.max(previousRecord.weight, newWeight);
+            if (!resizeWithLocalEviction(key, previousRecord.reservation, 
reservedWeight)) {
+                rejectWeight("budget_exceeded");
+                return AdmissionResult.REJECTED;
+            }
+            if (advanceMutationOnAdmission) {
+                advanceKeyMutation(key);
+            }
+            ReservationRecord newRecord = new ReservationRecord(
+                    newWeight, previousRecord.reservation, 
nextReservationGeneration());
+            reservations.put(key, newRecord);
+            try {
+                beforeWeightedCachePutForTest(key, value);
+                data.put(key, value);
+                boolean retained = reservations.get(key) == newRecord && 
data.asMap().get(key) == value;
+                if (retained) {
+                    newRecord.published = true;
+                }
+                if (retained && reservedWeight != newWeight && 
!newRecord.reservation.tryResize(newWeight)) {
+                    throw new IllegalStateException("failed to release cache 
replacement reservation delta");
+                }
+                if (retained) {
+                    notifyReplacement(key, oldValue, value);
+                }
+                return AdmissionResult.ADMITTED;
+            } catch (RuntimeException | Error e) {
+                if (reservations.replace(key, newRecord, previousRecord)) {
+                    if (data.asMap().get(key) == null) {
+                        reservations.remove(key, previousRecord);
+                        previousRecord.reservation.release();
+                    } else if 
(!previousRecord.reservation.tryResize(previousRecord.weight)) {
+                        throw new IllegalStateException("failed to roll back 
cache replacement reservation", e);
+                    }
+                }
+                throw e;
+            }
+        }
+    }
+
+    private Optional<AdmissionReservation> reserveWithLocalEviction(K 
incomingKey, long bytes) {
+        if (bytes > entryBudget.getEffectiveMaxWeight()) {
+            return Optional.empty();
+        }
+        Optional<AdmissionReservation> reservation = 
entryBudget.tryReserve(bytes);
+        while (!reservation.isPresent()) {
+            int evicted = evictLocalColdest(incomingKey, 
LOCAL_EVICTION_BATCH_SIZE);

Review Comment:
   [P2] Retry before evicting the rest of the cold batch
   
   `evictLocalColdest(..., 16)` removes every entry in the selected cold 
snapshot before this loop retries the reservation. If the cache is short by 
only a few bytes and the first candidate is large, its removal creates all 
required headroom, but the other fifteen values are still discarded; 
`resizeWithLocalEviction()` and peer reclamation have the same batch-level 
check. Under skewed metadata weights this can flush most of a catalog cache and 
force avoidable remote reloads for a tiny admission. Please retry/stop after 
each candidate (or once the requested deficit is reclaimed), and cover the 
skewed one-needed-versus-sixteen-selected case.



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