This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new c4f15f35eb9 [improvement](fe) Add the branch-4.x weight budget columns 
to catalog_meta_cache_statistics (#69557)
c4f15f35eb9 is described below

commit c4f15f35eb9b3d5391b454bd395c67cd10b5ecee
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sun Oct 25 08:04:34 2026 +0000

    [improvement](fe) Add the branch-4.x weight budget columns to 
catalog_meta_cache_statistics (#69557)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #66717, #67726
    
    Problem Summary:
    
    **In short.** This brings the weight columns of
    `information_schema.catalog_meta_cache_statistics` from branch-4.x
    #66717 to master. Master shows four of the ten: this PR adds
    `WEIGHT_BOUNDED`, `EVICTION_WEIGHT`, `CATALOG_MAX_WEIGHT`,
    `CATALOG_ESTIMATED_WEIGHT`, `GLOBAL_MAX_WEIGHT` and
    `GLOBAL_ESTIMATED_WEIGHT` with the 4.x names, types and meanings, and
    makes a cache entry without a weight bound report `-1` in every weight
    column, as 4.x does, instead of `0` in `ESTIMATED_WEIGHT` and
    `WEIGHT_REJECT_COUNT`.
    
    **Background.** External catalogs can bound the memory of their metadata
    caches: an FE-global limit (`external_meta_cache_max_weight`), a catalog
    limit (`meta.cache.max-weight`) and per-entry limits.
    `catalog_meta_cache_statistics` is how users watch those caches. On
    branch-4.x (#66717) the view tells, per entry, whether it is weight
    bounded, what it holds and has evicted, and how full its catalog and the
    FE-global budget are. Master (#67726) re-implemented the governance on
    the connector cache framework and exposed only `MAX_WEIGHT`,
    `ESTIMATED_WEIGHT`, `WEIGHT_REJECT_COUNT` and
    `LAST_WEIGHT_REJECT_REASON`.
    
    **The problem, and what it cost.** Monitoring queries written for 4.x
    fail on master with an unknown column as soon as they name one of the
    other six, and so do the weight scenarios of 4.x's
    `test_paimon_table_meta_cache` and `test_iceberg_table_meta_cache`.
    There is also no way on master to see how much of a catalog or of the
    FE-global budget is used, or how many bytes were evicted to make room.
    And an entry without a weight bound reports `ESTIMATED_WEIGHT = 0` and
    `WEIGHT_REJECT_COUNT = 0`, which reads like an empty but bounded cache.
    
    **How this PR fixes it.**
    - `MetaCacheBudgetManager.EntryBudget` exposes the catalog limit the
    entry is admitted under (the smaller of the catalog and FE-global
    limits, as the admission check uses it), the bytes charged to its
    catalog, the FE-global limit and the bytes charged to it. A limit that
    does not exist is `-1`.
    - `ScopedMetaCache` adds the bytes each value was admitted with to an
    eviction-weight counter when it removes the value by eviction, by
    Caffeine (capacity or expiry) or to make room in the budget. The bytes
    are recorded when the value is admitted, because an exact invalidation
    can release the reservation of an evicted value before the eviction is
    accounted. Invalidation and replacement do not count.
    - `ScopedMetaCache.CacheMetrics`, `MetaCacheEntryStats`, `SchemaTable`
    and `MetadataGenerator` carry the six values. The columns are appended
    after `LAST_WEIGHT_REJECT_REASON`. For an entry without a weight bound,
    every weight column is `-1`.
    - The BE scanner knows the six columns. An FE that does not know them
    rejects the request, and the scanner then asks for the leading 28
    columns, then 24, as it already did for the four earlier weight columns.
    
    `SELECT *` positions still differ from 4.x, because master's base
    columns also have `EVICTION_RATE`; queries that name their columns now
    work on both.
    
    **Results.**
    
    | | master | this PR |
    |---|---|---|
    | `SELECT weight_bounded, catalog_max_weight ... FROM
    catalog_meta_cache_statistics` | fails: unknown column | works, as on
    4.x |
    | Entry of a catalog with `meta.cache.max-weight = 128MB` | `MAX_WEIGHT
    = 134217728` only | also `CATALOG_MAX_WEIGHT = 134217728`, the catalog's
    and the FE-global charges, the evicted bytes |
    | Entry without any weight bound | `ESTIMATED_WEIGHT = 0`,
    `WEIGHT_REJECT_COUNT = 0` | `-1` in every weight column |
    | New BE, FE without the six columns | n/a | the BE falls back to the
    leading 28 columns |
    
    Tests:
    - `MetaCacheWeightGovernanceTest`: an entry without a weight bound
    reports `-1` everywhere; the catalog and FE-global limits and charges of
    bounded entries, with and without an FE-global limit; a budget reclaim
    and a capacity eviction add the evicted reservation to the eviction
    weight, an invalidation does not. An eviction keeps the bytes it was
    admitted with when the key is invalidated before the deferred eviction
    is accounted. Restoring master's `0` for unbounded entries, or dropping
    the eviction accounting, turns these tests red.
    - `FeMetaCacheEntryTest` checks the fe-core view of an unbounded and of
    a bounded entry; `HudiConnectorHmsCacheTest` now expects `-1` for its
    unbounded entry.
    - Regression: `test_paimon_table_meta_cache` gets the weight scenario of
    4.x on master's entry names (a catalog with `meta.cache.max-weight =
    128MB` next to an unbounded one); `test_iceberg_table_meta_cache` puts
    its first catalog under a catalog limit and checks the budget hierarchy
    of its bounded entries, as 4.x does; the new
    `test_remote_doris_meta_cache_weight` covers the `doris` engine without
    any external service.
    - Manually: a BE from this PR against an FE without the six columns
    returns the rows (the FE rejects the 34 columns, the BE retries with
    28).
    
    Co-authored-by: Calvin Kirs <[email protected]>
---
 .../schema_catalog_meta_cache_stats_scanner.cpp    |  27 ++++-
 .../connector/cache/MetaCacheBudgetManager.java    |  20 ++++
 .../doris/connector/cache/ScopedMetaCache.java     |  57 ++++++++++-
 .../cache/MetaCacheWeightGovernanceTest.java       | 111 +++++++++++++++++++++
 .../connector/hudi/HudiConnectorHmsCacheTest.java  |   3 +-
 .../java/org/apache/doris/catalog/SchemaTable.java |   8 ++
 .../datasource/metacache/MetaCacheEntryStats.java  |  50 +++++++++-
 .../doris/tablefunction/MetadataGenerator.java     |  10 ++
 .../datasource/metacache/FeMetaCacheEntryTest.java |  16 +++
 .../paimon/test_paimon_table_meta_cache.out        |  15 +++
 .../test_remote_doris_meta_cache_weight.out        |   9 ++
 .../iceberg/test_iceberg_table_meta_cache.groovy   |  24 ++++-
 .../paimon/test_paimon_table_meta_cache.groovy     |  71 +++++++++++++
 .../test_remote_doris_meta_cache_weight.groovy     |  96 ++++++++++++++++++
 14 files changed, 506 insertions(+), 11 deletions(-)

diff --git 
a/be/src/information_schema/schema_catalog_meta_cache_stats_scanner.cpp 
b/be/src/information_schema/schema_catalog_meta_cache_stats_scanner.cpp
index 7dde0c92e0d..8abe2b4e4b3 100644
--- a/be/src/information_schema/schema_catalog_meta_cache_stats_scanner.cpp
+++ b/be/src/information_schema/schema_catalog_meta_cache_stats_scanner.cpp
@@ -17,6 +17,9 @@
 
 #include "information_schema/schema_catalog_meta_cache_stats_scanner.h"
 
+#include <algorithm>
+#include <array>
+
 #include "core/block/block.h"
 #include "core/data_type/data_type_factory.hpp"
 #include "core/string_ref.h"
@@ -57,9 +60,17 @@ std::vector<SchemaScanner::ColumnDesc> 
SchemaCatalogMetaCacheStatsScanner::_s_tb
         {"ESTIMATED_WEIGHT", TYPE_BIGINT, sizeof(int64_t), true},
         {"WEIGHT_REJECT_COUNT", TYPE_BIGINT, sizeof(int64_t), true},
         {"LAST_WEIGHT_REJECT_REASON", TYPE_STRING, sizeof(StringRef), true},
+        {"WEIGHT_BOUNDED", TYPE_BOOLEAN, sizeof(bool), true},
+        {"EVICTION_WEIGHT", TYPE_BIGINT, sizeof(int64_t), true},
+        {"CATALOG_MAX_WEIGHT", TYPE_BIGINT, sizeof(int64_t), true},
+        {"CATALOG_ESTIMATED_WEIGHT", TYPE_BIGINT, sizeof(int64_t), true},
+        {"GLOBAL_MAX_WEIGHT", TYPE_BIGINT, sizeof(int64_t), true},
+        {"GLOBAL_ESTIMATED_WEIGHT", TYPE_BIGINT, sizeof(int64_t), true},
 };
 
-static constexpr size_t kLegacyMetaCacheStatsColumnCount = 24;
+// The leading columns that older FE versions know, newest first: the first 
weight columns came before the
+// budget hierarchy columns, and the base columns before any weight column.
+static constexpr std::array<size_t, 2> kOlderFeMetaCacheStatsColumnCounts = 
{28, 24};
 
 SchemaCatalogMetaCacheStatsScanner::SchemaCatalogMetaCacheStatsScanner()
         : SchemaScanner(_s_tbls_columns, 
TSchemaTableType::SCH_CATALOG_META_CACHE_STATISTICS) {}
@@ -111,8 +122,13 @@ Status 
SchemaCatalogMetaCacheStatsScanner::_get_meta_cache_from_fe() {
             return status;
         }
         Status first_status = status;
-        result = TFetchSchemaTableDataResult();
-        status = _fetch_from_fe(kLegacyMetaCacheStatsColumnCount, &result, 
&fe_rejected);
+        for (size_t column_count : kOlderFeMetaCacheStatsColumnCounts) {
+            result = TFetchSchemaTableDataResult();
+            status = _fetch_from_fe(column_count, &result, &fe_rejected);
+            if (status.ok() || !fe_rejected) {
+                break;
+            }
+        }
         if (!status.ok()) {
             LOG(WARNING) << "fetch catalog meta cache stats from FE(" << 
_fe_addr.hostname
                          << ") failed, errmsg=" << first_status;
@@ -134,7 +150,10 @@ Status 
SchemaCatalogMetaCacheStatsScanner::_get_meta_cache_from_fe() {
     size_t col_size = _s_tbls_columns.size();
     if (!result_data.empty()) {
         col_size = result_data[0].column_value.size();
-        if (col_size != _s_tbls_columns.size() && col_size != 
kLegacyMetaCacheStatsColumnCount) {
+        if (col_size != _s_tbls_columns.size() &&
+            std::find(kOlderFeMetaCacheStatsColumnCounts.begin(),
+                      kOlderFeMetaCacheStatsColumnCounts.end(),
+                      col_size) == kOlderFeMetaCacheStatsColumnCounts.end()) {
             return Status::InternalError<false>(
                     "catalog meta cache stats schema is not match for FE and 
BE");
         }
diff --git 
a/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheBudgetManager.java
 
b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheBudgetManager.java
index 40b22ec76c7..ea285a75d8c 100644
--- 
a/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheBudgetManager.java
+++ 
b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheBudgetManager.java
@@ -530,6 +530,26 @@ public final class MetaCacheBudgetManager {
             }
         }
 
+        /** The catalog limit this entry is admitted under: the smaller of the 
catalog and FE-global limits, or -1. */
+        public long getCatalogMaxWeight() {
+            return catalogBucket.maxWeight == Long.MAX_VALUE ? -1L : 
catalogBucket.maxWeight;
+        }
+
+        public long getCatalogUsedWeight() {
+            synchronized (manager.lock) {
+                return catalogBucket.usedWeight;
+            }
+        }
+
+        /** The FE-global limit; -1 if none. */
+        public long getGlobalMaxWeight() {
+            return manager.globalMaxWeight.orElse(-1L);
+        }
+
+        public long getGlobalUsedWeight() {
+            return manager.getGlobalUsedWeight();
+        }
+
         @Override
         public void close() {
             manager.close(this);
diff --git 
a/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ScopedMetaCache.java
 
b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ScopedMetaCache.java
index b58ad13bd9d..7dfaf88eda9 100644
--- 
a/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ScopedMetaCache.java
+++ 
b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ScopedMetaCache.java
@@ -91,6 +91,7 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
     private final LongAdder loadFailureCount = new LongAdder();
     private final LongAdder totalLoadTimeNanos = new LongAdder();
     private final LongAdder evictionCount = new LongAdder();
+    private final AtomicLong evictionWeight = new AtomicLong();
     private final LongAdder invalidateCount = new LongAdder();
     private final AtomicReference<Long> lastLoadSuccessTimeMs = new 
AtomicReference<>(-1L);
     private final AtomicReference<Long> lastLoadFailureTimeMs = new 
AtomicReference<>(-1L);
@@ -576,8 +577,13 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
                     lastError.get(),
                     weightBounded,
                     entryBudget == null ? -1L : 
entryBudget.getEffectiveMaxWeight(),
-                    entryBudget == null ? 0L : entryBudget.getUsedWeight(),
-                    weightRejectCount.sum(),
+                    entryBudget == null ? -1L : entryBudget.getUsedWeight(),
+                    entryBudget == null ? -1L : evictionWeight.get(),
+                    entryBudget == null ? -1L : weightRejectCount.sum(),
+                    entryBudget == null ? -1L : 
entryBudget.getCatalogMaxWeight(),
+                    entryBudget == null ? -1L : 
entryBudget.getCatalogUsedWeight(),
+                    entryBudget == null ? -1L : 
entryBudget.getGlobalMaxWeight(),
+                    entryBudget == null ? -1L : 
entryBudget.getGlobalUsedWeight(),
                     lastWeightRejectReason.get()));
     }
 
@@ -826,7 +832,7 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
         for (Map.Entry<K, VersionedValue<K, V>> candidate : 
coldest.entrySet()) {
             VersionedValue<K, V> current = 
data.getIfPresent(candidate.getKey());
             if (current == candidate.getValue()) {
-                long weight = current.registration.reservation.getBytes();
+                long weight = current.registration.admittedBytes;
                 budgetEvictionRegistration.set(current.registration);
                 try {
                     if (data.asMap().remove(candidate.getKey(), current)) {
@@ -1144,6 +1150,11 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
             VersionedValue<K, V> versioned, RemovalCause cause, boolean 
budgetEviction) {
         if (cause.wasEvicted() || budgetEviction) {
             evictionCount.increment();
+            if (weightBounded) {
+                // Releasing a reservation zeroes it, and an exact 
invalidation can release the evicted generation
+                // before this deferred removal runs, so count the bytes the 
generation was admitted with.
+                
evictionWeight.accumulateAndGet(versioned.registration.admittedBytes, 
JvmSizeUtils::saturatedAdd);
+            }
         } else if (cause == RemovalCause.EXPLICIT) {
             invalidateCount.increment();
         }
@@ -1298,7 +1309,12 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
         private final boolean weightBounded;
         private final long maxWeight;
         private final long estimatedWeight;
+        private final long evictionWeight;
         private final long weightRejectCount;
+        private final long catalogMaxWeight;
+        private final long catalogEstimatedWeight;
+        private final long globalMaxWeight;
+        private final long globalEstimatedWeight;
         private final String lastWeightRejectReason;
 
         private CacheMetrics(
@@ -1322,7 +1338,12 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
                 boolean weightBounded,
                 long maxWeight,
                 long estimatedWeight,
+                long evictionWeight,
                 long weightRejectCount,
+                long catalogMaxWeight,
+                long catalogEstimatedWeight,
+                long globalMaxWeight,
+                long globalEstimatedWeight,
                 String lastWeightRejectReason) {
             this.physicalEntryCount = physicalEntryCount;
             this.keyNodeCount = keyNodeCount;
@@ -1344,7 +1365,12 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
             this.weightBounded = weightBounded;
             this.maxWeight = maxWeight;
             this.estimatedWeight = estimatedWeight;
+            this.evictionWeight = evictionWeight;
             this.weightRejectCount = weightRejectCount;
+            this.catalogMaxWeight = catalogMaxWeight;
+            this.catalogEstimatedWeight = catalogEstimatedWeight;
+            this.globalMaxWeight = globalMaxWeight;
+            this.globalEstimatedWeight = globalEstimatedWeight;
             this.lastWeightRejectReason = lastWeightRejectReason;
         }
 
@@ -1428,10 +1454,31 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
             return estimatedWeight;
         }
 
+        /** Bytes of the values removed by eviction, by Caffeine or to make 
room in the budget; -1 if unbounded. */
+        public long getEvictionWeight() {
+            return evictionWeight;
+        }
+
         public long getWeightRejectCount() {
             return weightRejectCount;
         }
 
+        public long getCatalogMaxWeight() {
+            return catalogMaxWeight;
+        }
+
+        public long getCatalogEstimatedWeight() {
+            return catalogEstimatedWeight;
+        }
+
+        public long getGlobalMaxWeight() {
+            return globalMaxWeight;
+        }
+
+        public long getGlobalEstimatedWeight() {
+            return globalEstimatedWeight;
+        }
+
         public String getLastWeightRejectReason() {
             return lastWeightRejectReason;
         }
@@ -1557,6 +1604,9 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
     /** Lightweight ownership token: deliberately does not retain the cached 
value. */
     private static final class Registration {
         private final MetaCacheBudgetManager.AdmissionReservation reservation;
+        // Read when the generation is created, right after its reservation is 
admitted: releasing the
+        // reservation zeroes its bytes, while eviction accounting still needs 
them.
+        private final long admittedBytes;
         private final CacheAddress address;
         private final ScopeSnapshot scopeSnapshot;
         private final AtomicBoolean released = new AtomicBoolean(false);
@@ -1564,6 +1614,7 @@ public final class ScopedMetaCache<K, V> implements 
AutoCloseable {
         private Registration(MetaCacheBudgetManager.AdmissionReservation 
reservation,
                 CacheAddress address, ScopeSnapshot scopeSnapshot) {
             this.reservation = reservation;
+            this.admittedBytes = reservation == null ? 0L : 
reservation.getBytes();
             this.address = address;
             this.scopeSnapshot = scopeSnapshot;
         }
diff --git 
a/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheWeightGovernanceTest.java
 
b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheWeightGovernanceTest.java
index 59acec37a20..5f8daa712cf 100644
--- 
a/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheWeightGovernanceTest.java
+++ 
b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheWeightGovernanceTest.java
@@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
@@ -243,6 +244,105 @@ class MetaCacheWeightGovernanceTest {
             Assertions.assertEquals(2048L, 
entry.metrics().getEstimatedWeight());
             Assertions.assertEquals(1L, 
entry.metrics().getWeightRejectCount());
             Assertions.assertEquals(1L, entry.metrics().getEvictionCount());
+            // The value reclaimed for the budget counts with the reservation 
it held.
+            Assertions.assertEquals(1024L, 
entry.metrics().getEvictionWeight());
+        }
+    }
+
+    @Test
+    void capacityEvictionCountsTheEvictedReservationButInvalidationDoesNot() {
+        MetaCacheBudgetManager manager = new 
MetaCacheBudgetManager(OptionalLong.of(4096L));
+        try (CatalogMetaCache owner = new CatalogMetaCache(
+                manager, 25L, "iceberg", Collections.emptyMap())) {
+            MetaCache<String, String> entry = owner.create(MetaCacheDefinition
+                    .<String, String>builder("table", CacheSpec.of(true, -1L, 
1L),
+                            ignored -> ScopePath.catalog())
+                    .sizeEstimator((key, value) -> 
MetaCacheSizeEstimate.complete(256L))
+                    .build());
+
+            entry.put("one", "value");
+            entry.put("two", "value");
+            awaitReclaimed(entry, 1L, ENTRY_OVERHEAD + 256L);
+            Assertions.assertEquals(1L, entry.metrics().getEvictionCount());
+            Assertions.assertEquals(ENTRY_OVERHEAD + 256L, 
entry.metrics().getEvictionWeight());
+
+            // Either key may be the one that was evicted; invalidating the 
survivor is not an eviction.
+            entry.invalidateKey("one");
+            entry.invalidateKey("two");
+            Assertions.assertEquals(0L, entry.metrics().getEstimatedWeight());
+            Assertions.assertEquals(ENTRY_OVERHEAD + 256L, 
entry.metrics().getEvictionWeight());
+        }
+    }
+
+    @Test
+    void evictionWeightKeepsTheAdmittedBytesWhenInvalidationReleasesFirst() {
+        MetaCacheBudgetManager manager = new 
MetaCacheBudgetManager(OptionalLong.of(4096L));
+        MetaCacheBudgetManager.EntryBudget budget = manager.createEntryBudget(
+                28L, "iceberg", "table", "table", OptionalLong.empty(), 
OptionalLong.empty());
+        try (ScopedMetaCacheRegistry registry = new ScopedMetaCacheRegistry()) 
{
+            ScopedMetaCache<String, String> cache = 
registry.createCacheWithMetaRemovalListener(
+                    "table", CacheSpec.of(true, -1L, 1L), null, null, null, 
null,
+                    (key, value) -> MetaCacheSizeEstimate.complete(256L), 
budget);
+            cache.put("one", ScopePath.catalog(), "value");
+            AtomicBoolean evictedInsideTheDeferral = new AtomicBoolean();
+            // forEach defers the removals its callback causes, so the 
capacity eviction of either key is drained
+            // only after both exact invalidations have released the 
reservations, which zeroes them.
+            cache.forEach((key, value) -> {
+                if (evictedInsideTheDeferral.compareAndSet(false, true)) {
+                    cache.put("two", ScopePath.catalog(), "value");
+                    cache.invalidateKey("one");
+                    cache.invalidateKey("two");
+                }
+            });
+
+            Assertions.assertTrue(evictedInsideTheDeferral.get());
+            Assertions.assertEquals(1L, cache.metrics().getEvictionCount());
+            Assertions.assertEquals(ENTRY_OVERHEAD + 256L, 
cache.metrics().getEvictionWeight(),
+                    "eviction must count the bytes the evicted generation was 
admitted with");
+            Assertions.assertEquals(0L, manager.getGlobalUsedWeight());
+        }
+    }
+
+    @Test
+    void weightMetricsReportTheCatalogAndGlobalBudgetsOfTheEntry() {
+        MetaCacheBudgetManager manager = new 
MetaCacheBudgetManager(OptionalLong.of(8192L));
+        try (CatalogMetaCache owner = new CatalogMetaCache(manager, 26L, 
"paimon",
+                
Collections.singletonMap(MetaCacheBudgetManager.CATALOG_MAX_WEIGHT_PROPERTY, 
"4096"));
+                CatalogMetaCache unlimitedCatalog = new CatalogMetaCache(
+                        manager, 27L, "paimon", Collections.emptyMap())) {
+            MetaCache<String, String> table = weightedStringEntry(owner, 
"table", 512L);
+            MetaCache<String, String> snapshot = weightedStringEntry(owner, 
"snapshot", 256L);
+            MetaCache<String, String> other = 
weightedStringEntry(unlimitedCatalog, "table", 512L);
+            table.put("t", "value");
+            snapshot.put("s", "value");
+            other.put("o", "value");
+
+            ScopedMetaCache.CacheMetrics metrics = table.metrics();
+            Assertions.assertTrue(metrics.isWeightBounded());
+            Assertions.assertEquals(4096L, metrics.getMaxWeight());
+            Assertions.assertEquals(1024L, metrics.getEstimatedWeight());
+            Assertions.assertEquals(0L, metrics.getEvictionWeight());
+            Assertions.assertEquals(4096L, metrics.getCatalogMaxWeight());
+            Assertions.assertEquals(1024L + 768L, 
metrics.getCatalogEstimatedWeight());
+            Assertions.assertEquals(8192L, metrics.getGlobalMaxWeight());
+            Assertions.assertEquals(1024L + 768L + 1024L, 
metrics.getGlobalEstimatedWeight());
+            // Without a catalog limit, the catalog level is bounded by the 
FE-global one.
+            Assertions.assertEquals(8192L, 
other.metrics().getCatalogMaxWeight());
+            Assertions.assertEquals(1024L, 
other.metrics().getCatalogEstimatedWeight());
+        }
+    }
+
+    @Test
+    void weightMetricsHaveNoGlobalMaximumWithoutAGlobalLimit() {
+        MetaCacheBudgetManager manager = new 
MetaCacheBudgetManager(OptionalLong.empty());
+        try (CatalogMetaCache owner = new CatalogMetaCache(manager, 28L, 
"iceberg",
+                
Collections.singletonMap(MetaCacheBudgetManager.CATALOG_MAX_WEIGHT_PROPERTY, 
"4096"))) {
+            MetaCache<String, String> entry = weightedStringEntry(owner, 
"table", 512L);
+            entry.put("key", "value");
+
+            Assertions.assertEquals(4096L, 
entry.metrics().getCatalogMaxWeight());
+            Assertions.assertEquals(-1L, entry.metrics().getGlobalMaxWeight());
+            Assertions.assertEquals(1024L, 
entry.metrics().getGlobalEstimatedWeight());
         }
     }
 
@@ -329,6 +429,17 @@ class MetaCacheWeightGovernanceTest {
             Assertions.assertEquals("value", entry.getIfPresent("key"));
             Assertions.assertFalse(entry.isWeightBounded());
             Assertions.assertEquals(0, estimates.get());
+            // An entry without a weight limit has no weight to report.
+            ScopedMetaCache.CacheMetrics metrics = entry.metrics();
+            Assertions.assertFalse(metrics.isWeightBounded());
+            Assertions.assertEquals(-1L, metrics.getMaxWeight());
+            Assertions.assertEquals(-1L, metrics.getEstimatedWeight());
+            Assertions.assertEquals(-1L, metrics.getEvictionWeight());
+            Assertions.assertEquals(-1L, metrics.getWeightRejectCount());
+            Assertions.assertEquals(-1L, metrics.getCatalogMaxWeight());
+            Assertions.assertEquals(-1L, metrics.getCatalogEstimatedWeight());
+            Assertions.assertEquals(-1L, metrics.getGlobalMaxWeight());
+            Assertions.assertEquals(-1L, metrics.getGlobalEstimatedWeight());
         }
     }
 
diff --git 
a/fe/fe-connector/fe-connector-hudi/src/test/java/org/apache/doris/connector/hudi/HudiConnectorHmsCacheTest.java
 
b/fe/fe-connector/fe-connector-hudi/src/test/java/org/apache/doris/connector/hudi/HudiConnectorHmsCacheTest.java
index cda50755001..26a814711bc 100644
--- 
a/fe/fe-connector/fe-connector-hudi/src/test/java/org/apache/doris/connector/hudi/HudiConnectorHmsCacheTest.java
+++ 
b/fe/fe-connector/fe-connector-hudi/src/test/java/org/apache/doris/connector/hudi/HudiConnectorHmsCacheTest.java
@@ -130,7 +130,8 @@ public class HudiConnectorHmsCacheTest {
         cache.listPartitionNames("db", "t", -1);
         cache.listPartitionNames("db", "t", -1);
         Assertions.assertEquals(1, delegate.cachedCalls);
-        Assertions.assertEquals(0L, entry.metrics().getEstimatedWeight());
+        // An entry without a weight limit has no weight to report.
+        Assertions.assertEquals(-1L, entry.metrics().getEstimatedWeight());
     }
 
     // ── wrap 
─────────────────────────────────────────────────────────────────────────────────────────────
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
index 5b250467ae8..074a877cb1a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/SchemaTable.java
@@ -660,6 +660,14 @@ public class SchemaTable extends Table {
                                     .column("ESTIMATED_WEIGHT", 
ScalarType.createType(PrimitiveType.BIGINT))
                                     .column("WEIGHT_REJECT_COUNT", 
ScalarType.createType(PrimitiveType.BIGINT))
                                     .column("LAST_WEIGHT_REJECT_REASON", 
ScalarType.createStringType())
+                                    // Appended last: the BE scanner falls 
back to asking an older FE for only the
+                                    // leading columns.
+                                    .column("WEIGHT_BOUNDED", 
ScalarType.createType(PrimitiveType.BOOLEAN))
+                                    .column("EVICTION_WEIGHT", 
ScalarType.createType(PrimitiveType.BIGINT))
+                                    .column("CATALOG_MAX_WEIGHT", 
ScalarType.createType(PrimitiveType.BIGINT))
+                                    .column("CATALOG_ESTIMATED_WEIGHT", 
ScalarType.createType(PrimitiveType.BIGINT))
+                                    .column("GLOBAL_MAX_WEIGHT", 
ScalarType.createType(PrimitiveType.BIGINT))
+                                    .column("GLOBAL_ESTIMATED_WEIGHT", 
ScalarType.createType(PrimitiveType.BIGINT))
                                     .build())
             )
             .put("backend_kerberos_ticket_cache",
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCacheEntryStats.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCacheEntryStats.java
index 05d71700600..c76c16f3192 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCacheEntryStats.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCacheEntryStats.java
@@ -34,6 +34,8 @@ import java.util.Objects;
  *
  * <p>For last-load timestamps, {@code -1} means no corresponding event 
happened yet.
  * {@code lastError} keeps the latest load failure message; empty string means 
no failure recorded.
+ * The weight fields are {@code -1} for an entry that is not weight bounded, 
and the catalog and global maximums
+ * are {@code -1} when that level has no limit.
  */
 public final class MetaCacheEntryStats {
     private final boolean configEnabled;
@@ -55,9 +57,15 @@ public final class MetaCacheEntryStats {
     private final long lastLoadSuccessTimeMs;
     private final long lastLoadFailureTimeMs;
     private final String lastError;
+    private final boolean weightBounded;
     private final long maxWeight;
     private final long estimatedWeight;
+    private final long evictionWeight;
     private final long weightAdmissionRejectedCount;
+    private final long catalogMaxWeight;
+    private final long catalogEstimatedWeight;
+    private final long globalMaxWeight;
+    private final long globalEstimatedWeight;
     private final String lastWeightRejectReason;
 
     /**
@@ -83,9 +91,15 @@ public final class MetaCacheEntryStats {
             long lastLoadSuccessTimeMs,
             long lastLoadFailureTimeMs,
             String lastError,
+            boolean weightBounded,
             long maxWeight,
             long estimatedWeight,
+            long evictionWeight,
             long weightAdmissionRejectedCount,
+            long catalogMaxWeight,
+            long catalogEstimatedWeight,
+            long globalMaxWeight,
+            long globalEstimatedWeight,
             String lastWeightRejectReason) {
         this.configEnabled = configEnabled;
         this.effectiveEnabled = effectiveEnabled;
@@ -106,9 +120,15 @@ public final class MetaCacheEntryStats {
         this.lastLoadSuccessTimeMs = lastLoadSuccessTimeMs;
         this.lastLoadFailureTimeMs = lastLoadFailureTimeMs;
         this.lastError = Objects.requireNonNull(lastError, "lastError");
+        this.weightBounded = weightBounded;
         this.maxWeight = maxWeight;
         this.estimatedWeight = estimatedWeight;
+        this.evictionWeight = evictionWeight;
         this.weightAdmissionRejectedCount = weightAdmissionRejectedCount;
+        this.catalogMaxWeight = catalogMaxWeight;
+        this.catalogEstimatedWeight = catalogEstimatedWeight;
+        this.globalMaxWeight = globalMaxWeight;
+        this.globalEstimatedWeight = globalEstimatedWeight;
         this.lastWeightRejectReason = 
Objects.requireNonNull(lastWeightRejectReason, "lastWeightRejectReason");
     }
 
@@ -125,8 +145,10 @@ public final class MetaCacheEntryStats {
                 metrics.getLoadSuccessCount(), metrics.getLoadFailureCount(), 
metrics.getTotalLoadTimeNanos(),
                 loads == 0L ? 0D : (double) metrics.getTotalLoadTimeNanos() / 
loads,
                 metrics.getEvictionCount(), metrics.getInvalidateCount(), 
metrics.getLastLoadSuccessTimeMs(),
-                metrics.getLastLoadFailureTimeMs(), metrics.getLastError(), 
metrics.getMaxWeight(),
-                metrics.getEstimatedWeight(), metrics.getWeightRejectCount(),
+                metrics.getLastLoadFailureTimeMs(), metrics.getLastError(), 
metrics.isWeightBounded(),
+                metrics.getMaxWeight(), metrics.getEstimatedWeight(), 
metrics.getEvictionWeight(),
+                metrics.getWeightRejectCount(), metrics.getCatalogMaxWeight(), 
metrics.getCatalogEstimatedWeight(),
+                metrics.getGlobalMaxWeight(), 
metrics.getGlobalEstimatedWeight(),
                 metrics.getLastWeightRejectReason());
     }
 
@@ -228,6 +250,10 @@ public final class MetaCacheEntryStats {
         return lastError;
     }
 
+    public boolean isWeightBounded() {
+        return weightBounded;
+    }
+
     public long getMaxWeight() {
         return maxWeight;
     }
@@ -236,10 +262,30 @@ public final class MetaCacheEntryStats {
         return estimatedWeight;
     }
 
+    public long getEvictionWeight() {
+        return evictionWeight;
+    }
+
     public long getWeightAdmissionRejectedCount() {
         return weightAdmissionRejectedCount;
     }
 
+    public long getCatalogMaxWeight() {
+        return catalogMaxWeight;
+    }
+
+    public long getCatalogEstimatedWeight() {
+        return catalogEstimatedWeight;
+    }
+
+    public long getGlobalMaxWeight() {
+        return globalMaxWeight;
+    }
+
+    public long getGlobalEstimatedWeight() {
+        return globalEstimatedWeight;
+    }
+
     public String getLastWeightRejectReason() {
         return lastWeightRejectReason;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
index dc90bc915d0..fb42bde298a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/tablefunction/MetadataGenerator.java
@@ -1984,6 +1984,16 @@ public class MetadataGenerator {
                     trow.addToColumnValue(new 
TCell().setLongVal(entryStats.getEstimatedWeight()));
                     trow.addToColumnValue(new 
TCell().setLongVal(entryStats.getWeightAdmissionRejectedCount()));
                     trow.addToColumnValue(new 
TCell().setStringVal(entryStats.getLastWeightRejectReason()));
+                    trow.addToColumnValue(new 
TCell().setBoolVal(entryStats.isWeightBounded())); // WEIGHT_BOUNDED
+                    trow.addToColumnValue(new 
TCell().setLongVal(entryStats.getEvictionWeight())); // EVICTION_WEIGHT
+                    trow.addToColumnValue(
+                            new 
TCell().setLongVal(entryStats.getCatalogMaxWeight())); // CATALOG_MAX_WEIGHT
+                    trow.addToColumnValue(new TCell().setLongVal(
+                            entryStats.getCatalogEstimatedWeight())); // 
CATALOG_ESTIMATED_WEIGHT
+                    trow.addToColumnValue(
+                            new 
TCell().setLongVal(entryStats.getGlobalMaxWeight())); // GLOBAL_MAX_WEIGHT
+                    trow.addToColumnValue(new TCell().setLongVal(
+                            entryStats.getGlobalEstimatedWeight())); // 
GLOBAL_ESTIMATED_WEIGHT
                     dataBatch.add(trow);
                 }
             }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/metacache/FeMetaCacheEntryTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/metacache/FeMetaCacheEntryTest.java
index 22bbc80ad78..ba4673d8065 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/metacache/FeMetaCacheEntryTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/metacache/FeMetaCacheEntryTest.java
@@ -59,6 +59,16 @@ public class FeMetaCacheEntryTest {
             Assertions.assertEquals(1L, stats.getLoadSuccessCount());
             Assertions.assertTrue(stats.getRequestCount() >= 4L);
             Assertions.assertTrue(stats.getInvalidateCount() >= 1L);
+            // Without a weight limit the entry reports -1 for every weight 
figure.
+            Assertions.assertFalse(stats.isWeightBounded());
+            Assertions.assertEquals(-1L, stats.getMaxWeight());
+            Assertions.assertEquals(-1L, stats.getEstimatedWeight());
+            Assertions.assertEquals(-1L, stats.getEvictionWeight());
+            Assertions.assertEquals(-1L, 
stats.getWeightAdmissionRejectedCount());
+            Assertions.assertEquals(-1L, stats.getCatalogMaxWeight());
+            Assertions.assertEquals(-1L, stats.getCatalogEstimatedWeight());
+            Assertions.assertEquals(-1L, stats.getGlobalMaxWeight());
+            Assertions.assertEquals(-1L, stats.getGlobalEstimatedWeight());
         } finally {
             executor.shutdownNow();
         }
@@ -81,6 +91,12 @@ public class FeMetaCacheEntryTest {
 
             Assertions.assertSame(oversized, mutation.get(3L, 
TimeUnit.SECONDS));
             Assertions.assertNull(entry.getIfPresent("key"));
+            MetaCacheEntryStats stats = entry.stats();
+            Assertions.assertTrue(stats.isWeightBounded());
+            Assertions.assertEquals(1_024L, stats.getMaxWeight());
+            Assertions.assertEquals(1L, 
stats.getWeightAdmissionRejectedCount());
+            // The rejected mutation invalidated the old value; an 
invalidation is not an eviction.
+            Assertions.assertEquals(0L, stats.getEvictionWeight());
         } finally {
             worker.shutdownNow();
             refreshExecutor.shutdownNow();
diff --git 
a/regression-test/data/external_table_p0/paimon/test_paimon_table_meta_cache.out
 
b/regression-test/data/external_table_p0/paimon/test_paimon_table_meta_cache.out
new file mode 100644
index 00000000000..96b6f02f9a5
--- /dev/null
+++ 
b/regression-test/data/external_table_p0/paimon/test_paimon_table_meta_cache.out
@@ -0,0 +1,15 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !paimon_weighted_entries --
+paimon-database        true    134217728       134217728       -1      0
+paimon-latest-snapshot true    134217728       134217728       -1      0
+paimon-schema-at       true    134217728       134217728       -1      0
+paimon-table   true    134217728       134217728       -1      0
+paimon.partition-view  true    134217728       134217728       -1      0
+
+-- !paimon_unbounded_entries --
+paimon-database        false   -1      -1      -1      -1      -1      -1      
-1      -1
+paimon-latest-snapshot false   -1      -1      -1      -1      -1      -1      
-1      -1
+paimon-schema-at       false   -1      -1      -1      -1      -1      -1      
-1      -1
+paimon-table   false   -1      -1      -1      -1      -1      -1      -1      
-1
+paimon.partition-view  false   -1      -1      -1      -1      -1      -1      
-1      -1
+
diff --git 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_meta_cache_weight.out
 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_meta_cache_weight.out
new file mode 100644
index 00000000000..637072013f4
--- /dev/null
+++ 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_meta_cache_weight.out
@@ -0,0 +1,9 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !weighted_entries --
+backends       true    134217728       134217728       -1      0
+schema true    134217728       134217728       -1      0
+
+-- !unbounded_entries --
+backends       false   -1      -1      -1      -1      -1      -1      -1      
-1
+schema false   -1      -1      -1      -1      -1      -1      -1      -1
+
diff --git 
a/regression-test/suites/external_table_p0/iceberg/test_iceberg_table_meta_cache.groovy
 
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_table_meta_cache.groovy
index ed88421f687..4ccda9d31b7 100644
--- 
a/regression-test/suites/external_table_p0/iceberg/test_iceberg_table_meta_cache.groovy
+++ 
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_table_meta_cache.groovy
@@ -28,7 +28,8 @@ suite("test_iceberg_table_meta_cache", "p0,external") {
             String default_fs = "hdfs://${externalEnvIp}:${hdfs_port}"
             String warehouse = "${default_fs}/warehouse"
 
-            // 1. test default catalog
+            // 1. test a catalog-level memory bound without a global bound. 
The existing
+            // create/insert/select/refresh flow below is the weighted-cache 
happy path.
             sql """drop catalog if exists ${catalog_name};"""
             sql """
             create catalog ${catalog_name} properties (
@@ -37,6 +38,7 @@ suite("test_iceberg_table_meta_cache", "p0,external") {
                 'hive.metastore.uris' = 'thrift://${externalEnvIp}:${hmsPort}',
                 'fs.defaultFS' = '${default_fs}',
                 'warehouse' = '${warehouse}',
+                'meta.cache.max-weight' = '128MB',
                 'meta.cache.iceberg.manifest.enable' = 'true'
             );
             """
@@ -63,6 +65,26 @@ suite("test_iceberg_table_meta_cache", "p0,external") {
             sql """refresh table test_iceberg_meta_cache_db.sales"""
             // select 3 rows
             sql """select * from test_iceberg_meta_cache_db.sales"""
+            // The weight-bounded entries expose their budget hierarchy in the 
statistics view. Only the
+            // catalog is bounded, so it bounds every weighted entry, and the 
catalog's charge covers the entry's.
+            def weightStats = sql """
+                select entry_name, max_weight, estimated_weight, 
eviction_weight, catalog_max_weight,
+                       catalog_estimated_weight, global_max_weight, 
global_estimated_weight
+                from internal.information_schema.catalog_meta_cache_statistics
+                where catalog_name = "${catalog_name}" and engine_name = 
"iceberg" and weight_bounded = true
+                order by entry_name;
+            """
+            assertTrue(weightStats.size() > 0)
+            for (row in weightStats) {
+                assertTrue((row[1] as long) > 0L)
+                assertTrue((row[2] as long) >= 0L)
+                assertTrue((row[3] as long) >= 0L)
+                assertEquals(row[1] as long, row[4] as long)
+                assertTrue((row[5] as long) >= (row[2] as long))
+                long globalMax = row[6] as long
+                assertTrue(globalMax == -1L || globalMax >= (row[4] as long))
+                assertTrue((row[7] as long) >= (row[5] as long))
+            }
             sql """drop table test_iceberg_meta_cache_db.sales"""
 
             // 2. test catalog with meta.cache.iceberg.table.ttl-second
diff --git 
a/regression-test/suites/external_table_p0/paimon/test_paimon_table_meta_cache.groovy
 
b/regression-test/suites/external_table_p0/paimon/test_paimon_table_meta_cache.groovy
index e4c806bf26d..21f149c7d2b 100644
--- 
a/regression-test/suites/external_table_p0/paimon/test_paimon_table_meta_cache.groovy
+++ 
b/regression-test/suites/external_table_p0/paimon/test_paimon_table_meta_cache.groovy
@@ -27,10 +27,12 @@ suite("test_paimon_table_meta_cache", "p0,external") {
 
     String catalogWithCache = "test_paimon_table_cache_with_cache"
     String catalogNoCache = "test_paimon_table_cache_no_cache"
+    String catalogWeighted = "test_paimon_table_cache_weighted"
     String testDb = "paimon_cache_test_db"
 
     sql """drop catalog if exists ${catalogWithCache}"""
     sql """drop catalog if exists ${catalogNoCache}"""
+    sql """drop catalog if exists ${catalogWeighted}"""
 
     sql """
         CREATE CATALOG ${catalogWithCache} PROPERTIES (
@@ -80,6 +82,19 @@ suite("test_paimon_table_meta_cache", "p0,external") {
         exception "is wrong"
     }
 
+    // A catalog-level memory bound puts the table handle and snapshot 
projections under weight.
+    sql """
+        CREATE CATALOG ${catalogWeighted} PROPERTIES (
+            'type' = 'paimon',
+            'warehouse' = 's3://warehouse/wh',
+            's3.endpoint' = 'http://${externalEnvIp}:${minioPort}',
+            's3.access_key' = 'admin',
+            's3.secret_key' = 'password',
+            's3.path.style.access' = 'true',
+            'meta.cache.max-weight' = '128MB'
+        );
+    """
+
     try {
         spark_paimon "CREATE DATABASE IF NOT EXISTS paimon.${testDb}"
 
@@ -113,6 +128,61 @@ suite("test_paimon_table_meta_cache", "p0,external") {
         def result3 = sql """select * from ${testDb}.test_insert order by id"""
         assertEquals(2, result3.size())
 
+        // ==================== Test 1b: weight-bounded cache 
====================
+        logger.info("========== Test 1b: weight-bounded cache ==========")
+        sql """switch ${catalogWeighted}"""
+        def resultWeighted = sql """select * from ${testDb}.test_insert order 
by id"""
+        assertEquals(2, resultWeighted.size())
+        // desc/table-only paths admit the base table handle; the scan above 
admits the snapshot.
+        sql """desc ${testDb}.test_insert"""
+        // With only a catalog limit, every weighted entry is bounded by it, 
and nothing was rejected.
+        order_qt_paimon_weighted_entries """
+            select entry_name, weight_bounded, max_weight, catalog_max_weight, 
global_max_weight,
+                   weight_reject_count
+            from internal.information_schema.catalog_meta_cache_statistics
+            where catalog_name = "${catalogWeighted}" and engine_name = 
"paimon" and weight_bounded = true
+        """
+        def weightStats = sql """
+            select entry_name, estimated_weight, eviction_weight, 
catalog_estimated_weight,
+                   global_estimated_weight
+            from internal.information_schema.catalog_meta_cache_statistics
+            where catalog_name = "${catalogWeighted}" and engine_name = 
"paimon"
+              and entry_name in ("paimon-table", "paimon-latest-snapshot")
+        """
+        assertEquals(2, weightStats.size())
+        for (row in weightStats) {
+            // An admitted value is charged to its entry, its catalog and the 
FE-global total.
+            assertTrue((row[1] as long) > 0L)
+            assertEquals(0L, row[2] as long)
+            assertTrue((row[3] as long) >= (row[1] as long))
+            assertTrue((row[4] as long) >= (row[3] as long))
+        }
+        // A catalog without any memory bound reports -1 for every weight 
column.
+        order_qt_paimon_unbounded_entries """
+            select entry_name, weight_bounded, max_weight, estimated_weight, 
eviction_weight,
+                   weight_reject_count, catalog_max_weight, 
catalog_estimated_weight, global_max_weight,
+                   global_estimated_weight
+            from internal.information_schema.catalog_meta_cache_statistics
+            where catalog_name = "${catalogWithCache}" and engine_name = 
"paimon"
+        """
+        // Refresh releases the table handle reservation with its projections; 
the next scan
+        // re-admits both without rejections.
+        sql """refresh table ${testDb}.test_insert"""
+        def resultWeightedRefreshed = sql """select * from 
${testDb}.test_insert order by id"""
+        assertEquals(2, resultWeightedRefreshed.size())
+        def weightStatsRefreshed = sql """
+            select entry_name, estimated_weight, weight_reject_count
+            from internal.information_schema.catalog_meta_cache_statistics
+            where catalog_name = "${catalogWeighted}" and engine_name = 
"paimon"
+              and entry_name in ("paimon-table", "paimon-latest-snapshot")
+        """
+        assertEquals(2, weightStatsRefreshed.size())
+        for (row in weightStatsRefreshed) {
+            assertTrue((row[1] as long) > 0L)
+            assertEquals(0L, row[2] as long)
+        }
+        sql """switch ${catalogWithCache}"""
+
         // ==================== Test 2: Schema Change (ADD COLUMN) 
====================
         logger.info("========== Test 2: Schema Change (ADD COLUMN) ==========")
         spark_paimon "DROP TABLE IF EXISTS paimon.${testDb}.test_add_column"
@@ -150,5 +220,6 @@ suite("test_paimon_table_meta_cache", "p0,external") {
         }
         sql """drop catalog if exists ${catalogWithCache}"""
         sql """drop catalog if exists ${catalogNoCache}"""
+        sql """drop catalog if exists ${catalogWeighted}"""
     }
 }
diff --git 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_meta_cache_weight.groovy
 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_meta_cache_weight.groovy
new file mode 100644
index 00000000000..5fdf3f4b360
--- /dev/null
+++ 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_meta_cache_weight.groovy
@@ -0,0 +1,96 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_remote_doris_meta_cache_weight", "p0,external") {
+    String remote_doris_host = 
context.config.otherConfigs.get("extArrowFlightSqlHost")
+    String remote_doris_user = 
context.config.otherConfigs.get("extArrowFlightSqlUser")
+    String remote_doris_psw = 
context.config.otherConfigs.get("extArrowFlightSqlPassword")
+
+    def showres = sql "show frontends"
+    String remote_doris_arrow_port = showres[0][6]
+    String remote_doris_http_port = showres[0][3]
+    String remote_doris_thrift_port = showres[0][5]
+
+    sql """DROP DATABASE IF EXISTS test_remote_doris_meta_cache_weight_db"""
+    sql """CREATE DATABASE IF NOT EXISTS 
test_remote_doris_meta_cache_weight_db"""
+    sql """
+        CREATE TABLE 
`test_remote_doris_meta_cache_weight_db`.`test_remote_doris_meta_cache_weight_t`
 (
+            `id` INT,
+            `name` STRING
+        ) DUPLICATE KEY(`id`)
+        DISTRIBUTED BY HASH(`id`) BUCKETS 1
+        PROPERTIES ("replication_num" = "1");
+    """
+
+    // One catalog bounds its metadata caches by memory weight, the other one 
has no bound.
+    sql """DROP CATALOG IF EXISTS `test_remote_doris_meta_cache_weighted`"""
+    sql """
+       CREATE CATALOG `test_remote_doris_meta_cache_weighted` PROPERTIES (
+                'type' = 'doris',
+                'fe_thrift_hosts' = 
'${remote_doris_host}:${remote_doris_thrift_port}',
+                'fe_http_hosts' = 
'http://${remote_doris_host}:${remote_doris_http_port}',
+                'fe_arrow_hosts' = 
'${remote_doris_host}:${remote_doris_arrow_port}',
+                'user' = '${remote_doris_user}',
+                'password' = '${remote_doris_psw}',
+                'use_arrow_flight' = 'true',
+                'meta.cache.max-weight' = '128MB'
+        );
+    """
+    sql """DROP CATALOG IF EXISTS `test_remote_doris_meta_cache_unbounded`"""
+    sql """
+       CREATE CATALOG `test_remote_doris_meta_cache_unbounded` PROPERTIES (
+                'type' = 'doris',
+                'fe_thrift_hosts' = 
'${remote_doris_host}:${remote_doris_thrift_port}',
+                'fe_http_hosts' = 
'http://${remote_doris_host}:${remote_doris_http_port}',
+                'fe_arrow_hosts' = 
'${remote_doris_host}:${remote_doris_arrow_port}',
+                'user' = '${remote_doris_user}',
+                'password' = '${remote_doris_psw}',
+                'use_arrow_flight' = 'true'
+        );
+    """
+
+    // DESC loads the remote table's schema into the schema cache of each 
catalog.
+    sql """DESC 
test_remote_doris_meta_cache_weighted.test_remote_doris_meta_cache_weight_db.test_remote_doris_meta_cache_weight_t"""
+    sql """DESC 
test_remote_doris_meta_cache_unbounded.test_remote_doris_meta_cache_weight_db.test_remote_doris_meta_cache_weight_t"""
+
+    // With only a catalog limit, each entry is bounded by it, and nothing was 
rejected.
+    order_qt_weighted_entries """
+        select entry_name, weight_bounded, max_weight, catalog_max_weight, 
global_max_weight, weight_reject_count
+        from internal.information_schema.catalog_meta_cache_statistics
+        where catalog_name = 'test_remote_doris_meta_cache_weighted' and 
engine_name = 'doris'
+    """
+    // A catalog without any memory bound reports -1 for every weight column.
+    order_qt_unbounded_entries """
+        select entry_name, weight_bounded, max_weight, estimated_weight, 
eviction_weight, weight_reject_count,
+               catalog_max_weight, catalog_estimated_weight, 
global_max_weight, global_estimated_weight
+        from internal.information_schema.catalog_meta_cache_statistics
+        where catalog_name = 'test_remote_doris_meta_cache_unbounded' and 
engine_name = 'doris'
+    """
+    // The admitted schema is charged to its entry, its catalog and the 
FE-global total.
+    def schemaWeights = sql """
+        select estimated_weight, eviction_weight, catalog_estimated_weight, 
global_estimated_weight
+        from internal.information_schema.catalog_meta_cache_statistics
+        where catalog_name = 'test_remote_doris_meta_cache_weighted' and 
engine_name = 'doris'
+          and entry_name = 'schema'
+    """
+    assertEquals(1, schemaWeights.size())
+    long schemaWeight = schemaWeights[0][0] as long
+    assertTrue(schemaWeight > 0L)
+    assertEquals(0L, schemaWeights[0][1] as long)
+    assertTrue((schemaWeights[0][2] as long) >= schemaWeight)
+    assertTrue((schemaWeights[0][3] as long) >= (schemaWeights[0][2] as long))
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to