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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergSnapshotCacheValue.java:
##########
@@ -221,4 +316,30 @@ private boolean isWriterCompatible(TableMetadata 
refreshedMetadata) {
                     && Objects.equals(retainedMetadata.properties(), 
refreshedMetadata.properties());
         }
     }
+
+    private static class ServiceBackedTableOperations extends 
RetainedTableOperations {
+        private TableMetadata currentMetadata;
+
+        private ServiceBackedTableOperations(TableOperations delegate, 
TableMetadata retainedMetadata) {
+            super(delegate, retainedMetadata);
+            this.currentMetadata = retainedMetadata;
+        }
+
+        @Override
+        public TableMetadata current() {
+            return currentMetadata;
+        }
+
+        @Override
+        public TableMetadata refresh() {
+            currentMetadata = delegate.refresh();
+            return currentMetadata;
+        }
+
+        @Override
+        public void commit(TableMetadata base, TableMetadata newMetadata) {
+            delegate.commit(base, newMetadata);

Review Comment:
   [P1] Rebind detached metadata updates before delegating the commit
   
   When this table entry is weight-managed, publication JSON-detaches its 
`TableMetadata`, and `getIcebergTable()` seeds `ServiceBackedTableOperations` 
with another detached object. Iceberg 1.10.1 `SchemaUpdate` and 
`BaseUpdatePartitionSpec` capture `ops.current()` and call `ops.commit(base, 
update)` without refreshing, while `HadoopTableOperations` and 
`BaseMetastoreTableOperations` require `base` to be their current object by 
identity. Forwarding this clone therefore makes ALTER TABLE schema/reorder and 
partition evolution fail as stale for Hadoop, Hive, JDBC, Glue, and DLF 
catalogs whenever a table, catalog, or global weight limit enables this path. 
Please rebind a verified retained generation to the delegate's actual current 
object and cover both update kinds under weighted caching.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergTableCacheValue.java:
##########
@@ -17,25 +17,41 @@
 
 package org.apache.doris.datasource.iceberg;
 
-import com.google.common.base.Suppliers;
-import org.apache.iceberg.Table;
+import org.apache.doris.datasource.NameMapping;
+import org.apache.doris.datasource.metacache.MetaCacheSizeEstimate;
+import org.apache.doris.datasource.metacache.MetaCacheSizeEstimator;
 
-import java.util.function.Supplier;
+import org.apache.iceberg.Table;
 
 public class IcebergTableCacheValue {
-    private final Table icebergTable;
-    private final Supplier<IcebergSnapshotCacheValue> latestSnapshotCacheValue;
+    private Table icebergTable;
+    private MetaCacheSizeEstimate sizeEstimate;
 
-    public IcebergTableCacheValue(Table icebergTable, 
Supplier<IcebergSnapshotCacheValue> latestSnapshotCacheValue) {
+    public IcebergTableCacheValue(Table icebergTable) {
         this.icebergTable = icebergTable;
-        this.latestSnapshotCacheValue = 
Suppliers.memoize(latestSnapshotCacheValue::get);
     }
 
     public Table getIcebergTable() {
-        return icebergTable;
+        return 
IcebergSnapshotCacheValue.createServiceBackedTable(icebergTable);
+    }
+
+    MetaCacheSizeEstimate prepareForCachePublication(NameMapping key) {
+        if (sizeEstimate == null) {
+            sizeEstimate = 
MetaCacheSizeEstimator.estimateSafely("iceberg_table_preparation_failed", () -> 
{
+                icebergTable = 
IcebergSnapshotCacheValue.detachTableGeneration(icebergTable);
+                
IcebergSnapshotCacheValue.materializeAllSnapshotManifests(icebergTable);

Review Comment:
   [P2] Avoid materializing all historical manifests for an unexposed graph
   
   Weighted table admission now calls both manifest accessors for every 
historical snapshot. For v2 snapshots this lazily reads one manifest-list file 
per snapshot. The lists are retained only in this hidden frozen table for 
weighing: every public `getIcebergTable()` JSON-clones the metadata into a new 
service-backed table, dropping Iceberg's transient memoized lists, so scans 
cannot reuse the work. Long-lived tables therefore pay history-linear work and 
v2 remote I/O on each admission/refresh solely for an unused graph. Please 
weigh only metadata that callers can reuse, and add a many-snapshot v2 
FileIO-read regression.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/AbstractExternalMetaCache.java:
##########
@@ -81,10 +89,29 @@ public Collection<String> aliases() {
     }
 
     @Override
-    public void initCatalog(long catalogId, Map<String, String> 
catalogProperties) {
+    public void validateCatalogProperties(Map<String, String> 
catalogProperties) {
         Map<String, String> safeCatalogProperties = 
CacheSpec.applyCompatibilityMap(
                 catalogProperties, catalogPropertyCompatibilityMap());
-        catalogEntries.computeIfAbsent(catalogId, id -> 
buildCatalogEntryGroup(safeCatalogProperties));
+        validateMappedCatalogProperties(safeCatalogProperties);
+    }
+
+    @Override
+    public synchronized void initCatalog(long catalogId, Map<String, String> 
catalogProperties) {

Review Comment:
   [P2] Keep initialized cache lookups off the engine-wide monitor
   
   Every `ExternalMetaCacheMgr` typed accessor unconditionally calls 
`prepareCatalogByEngine`, which copies and validates the properties, and then 
reaches this synchronized method. Even when the catalog group already exists, 
the lookup therefore serializes with every other catalog using this engine and 
repeats compatibility mapping plus hierarchy validation before 
`computeIfAbsent` discovers there is no work. This is on normal planning paths 
such as Iceberg table and Paimon snapshot/schema lookup, so parallel queries 
across unrelated catalogs acquire one global engine lock. Please add a 
lock-free initialized fast path and reserve synchronization/validation for the 
first build after create or invalidation.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonSnapshotEntryKey.java:
##########
@@ -0,0 +1,70 @@
+// 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.
+
+package org.apache.doris.datasource.paimon;
+
+import org.apache.doris.datasource.NameMapping;
+
+import java.util.Objects;
+
+/** Stable identity for a Paimon projection hydrated from one captured 
snapshot/schema fence. */
+public final class PaimonSnapshotEntryKey {
+    private final NameMapping nameMapping;
+    private final long snapshotId;
+    private final long schemaId;
+
+    public PaimonSnapshotEntryKey(NameMapping nameMapping, long snapshotId, 
long schemaId) {
+        this.nameMapping = Objects.requireNonNull(nameMapping, "nameMapping 
can not be null");
+        this.snapshotId = snapshotId;
+        this.schemaId = schemaId;
+    }
+
+    public static PaimonSnapshotEntryKey of(NameMapping nameMapping, 
PaimonSnapshot fence) {
+        return new PaimonSnapshotEntryKey(nameMapping, fence.getSnapshotId(), 
fence.getSchemaId());
+    }
+
+    public NameMapping getNameMapping() {
+        return nameMapping;
+    }
+
+    public long getSnapshotId() {
+        return snapshotId;
+    }
+
+    public long getSchemaId() {
+        return schemaId;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof PaimonSnapshotEntryKey)) {
+            return false;
+        }
+        PaimonSnapshotEntryKey that = (PaimonSnapshotEntryKey) object;
+        return snapshotId == that.snapshotId

Review Comment:
   [P1] Include the table generation in the snapshot-cache identity
   
   The contextual value retains the fenced Paimon `Table` and its partition 
projection, but equality uses only the table name plus snapshot/schema IDs. The 
table entry refreshes independently, while this contextual entry cannot 
auto-refresh and has its own TTL. After a drop/recreate (where IDs restart) or 
another same-ID physical table generation, `getSnapshotCache()` can read the 
new table fence and still hit the old value, returning the old table handle and 
partition map. Explicit invalidation clears both entries, but ordinary table 
refresh/replacement does not. Please add a stable table-generation/options 
identity to this key or couple every table-entry replacement to snapshot 
invalidation, with a same-ID replacement regression test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergSnapshotCacheValue.java:
##########
@@ -119,6 +202,18 @@ static Table createWritableTable(Table retainedTable, 
Table liveTable) {
                 new WritableTableOperations(liveOperations, retainedMetadata));
     }
 
+    static Table createServiceBackedTable(Table retainedTable) {
+        if (!isFrozenGeneration(retainedTable)) {
+            return retainedTable;
+        }
+        TableOperations retainedOperations = ((HasTableOperations) 
retainedTable).operations();
+        TableMetadata retainedMetadata = retainedOperations.current();
+        TableMetadata callerMetadata = TableMetadataParser.fromJson(

Review Comment:
   [P2] Avoid rebuilding full metadata on every weighted cache hit
   
   Once weighted publication freezes this table entry, every 
`getIcebergTable()` reaches these calls and serializes then reparses the 
complete `TableMetadata`: schemas, specs, sort orders, snapshots, refs, 
statistics, and history logs. This runs on ordinary table/scan planning and 
even before `getSnapshotCache()` can hit its contextual entry, so a successful 
cache hit still performs history-linear CPU and allocation and builds a 
transient graph outside the retained budget. Count-only entries take the early 
return. Please reuse a safe read-only frozen projection and construct an 
isolated service-backed/writable wrapper only for mutation paths, with a 
long-history cache-hit benchmark.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/OwnedObjectSizeEstimator.java:
##########
@@ -0,0 +1,712 @@
+// 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.
+
+package org.apache.doris.datasource.metacache;
+
+import org.apache.doris.analysis.LiteralExpr;
+import org.apache.doris.analysis.MaxLiteral;
+import org.apache.doris.catalog.ListPartitionItem;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.RangePartitionItem;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.catalog.Type;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Full, identity-deduplicated traversal for owned Doris cache value graphs.
+ *
+ * <p>JDK containers use public collection APIs and explicit layout formulas, 
so
+ * this walker never needs module opens. An inaccessible non-JDK owned field 
makes
+ * the result incomplete instead of silently contributing zero. Unknown 
mutable ArrayLists
+ * are rejected because their retained capacity is not exposed by a public 
API. The dedicated
+ * Hive partition adapters conservatively model lists created by the 
connector's known path;
+ * cache publishers freeze all other owned lists before estimation.
+ */
+public final class OwnedObjectSizeEstimator {
+    private static final long LEGACY_LITERAL_HEADROOM_BYTES = 1024L;
+    private static final long NEREIDS_LITERAL_HEADROOM_BYTES = 2048L;
+    // These are hard safety ceilings, not sampling limits. Production-sized 
Hive partition graphs
+    // are still traversed exactly; an object is rejected if exact traversal 
cannot finish safely.
+    static final int DEFAULT_MAX_OBJECTS = 5_000_000;
+    static final int DEFAULT_MAX_DEPTH = 128;
+    static final long DEFAULT_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(5);
+    private static final int DEADLINE_CHECK_INTERVAL = 256;
+    private static final int CIRCUIT_FAILURE_THRESHOLD = 3;
+    private static final long CIRCUIT_OPEN_NANOS = TimeUnit.MINUTES.toNanos(1);
+    private static final Map<Class<?>, FailureCircuit> FAILURE_CIRCUITS = new 
ConcurrentHashMap<>();
+    private static final Set<String> SUPPORTED_LEGACY_LITERAL_TYPES = new 
HashSet<>(Arrays.asList(
+            "org.apache.doris.analysis.BoolLiteral",
+            "org.apache.doris.analysis.DateLiteral",
+            "org.apache.doris.analysis.DecimalLiteral",
+            "org.apache.doris.analysis.FloatLiteral",
+            "org.apache.doris.analysis.IPv4Literal",
+            "org.apache.doris.analysis.IPv6Literal",
+            "org.apache.doris.analysis.IntLiteral",
+            "org.apache.doris.analysis.JsonLiteral",
+            "org.apache.doris.analysis.LargeIntLiteral",
+            "org.apache.doris.analysis.MaxLiteral",
+            "org.apache.doris.analysis.NullLiteral",
+            "org.apache.doris.analysis.StringLiteral",
+            "org.apache.doris.analysis.TimeV2Literal",
+            "org.apache.doris.analysis.VarBinaryLiteral"));
+    private static final Set<String> SUPPORTED_NEREIDS_LITERAL_TYPES = new 
HashSet<>(Arrays.asList(
+            "org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.CharLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.DateLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal",
+            "org.apache.doris.nereids.trees.expressions.literal.DateV2Literal",
+            
"org.apache.doris.nereids.trees.expressions.literal.DecimalLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.DecimalV3Literal",
+            "org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.FloatLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.IPv4Literal",
+            "org.apache.doris.nereids.trees.expressions.literal.IPv6Literal",
+            
"org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.JsonLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.LargeIntLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.MaxLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.NullLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.SmallIntLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.StringLiteral",
+            "org.apache.doris.nereids.trees.expressions.literal.TimeV2Literal",
+            
"org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.VarBinaryLiteral",
+            
"org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral"));
+    private static final Set<String> SHARED_BOUNDARY_TYPE_NAMES = new 
LinkedHashSet<>();
+
+    static {
+        SHARED_BOUNDARY_TYPE_NAMES.add("java.util.concurrent.Executor");
+        SHARED_BOUNDARY_TYPE_NAMES.add("java.util.concurrent.ExecutorService");
+        SHARED_BOUNDARY_TYPE_NAMES.add("javax.sql.DataSource");
+        
SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.doris.common.security.authentication.HadoopAuthenticator");
+        
SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.doris.datasource.CatalogIf");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.hadoop.conf.Configuration");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.hadoop.fs.FileSystem");
+        
SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.hadoop.hive.metastore.IMetaStoreClient");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.iceberg.catalog.Catalog");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.iceberg.io.FileIO");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.iceberg.Table");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.paimon.catalog.Catalog");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.paimon.fs.FileIO");
+        SHARED_BOUNDARY_TYPE_NAMES.add("org.apache.paimon.table.Table");
+    }
+
+    private static final ClassValue<Boolean> SHARED_BOUNDARY_TYPES = new 
ClassValue<Boolean>() {
+        @Override
+        protected Boolean computeValue(Class<?> type) {
+            return isNamedBoundaryType(type);
+        }
+    };
+
+    private static final ClassValue<ClassPlan> CLASS_PLANS = new 
ClassValue<ClassPlan>() {
+        @Override
+        protected ClassPlan computeValue(Class<?> type) {
+            List<Field> referenceFields = new ArrayList<>();
+            for (Class<?> current = type; current != null; current = 
current.getSuperclass()) {
+                for (Field field : current.getDeclaredFields()) {
+                    if (Modifier.isStatic(field.getModifiers()) || 
field.getType().isPrimitive()) {
+                        continue;
+                    }
+                    try {
+                        if (!field.isAccessible()) {
+                            field.setAccessible(true);
+                        }
+                        referenceFields.add(field);
+                    } catch (RuntimeException e) {
+                        return ClassPlan.inaccessible(current.getName() + "." 
+ field.getName());
+                    }
+                }
+            }
+            return ClassPlan.accessible(referenceFields.toArray(new Field[0]));
+        }
+    };
+
+    private OwnedObjectSizeEstimator() {
+    }
+
+    public static MetaCacheSizeEstimate estimate(Object... roots) {
+        Set<Class<?>> rootTypes = new LinkedHashSet<>();
+        for (Object root : roots) {
+            if (root != null) {
+                rootTypes.add(root.getClass());
+            }
+        }
+        long now = System.nanoTime();
+        for (Class<?> rootType : rootTypes) {
+            FailureCircuit circuit = FAILURE_CIRCUITS.get(rootType);

Review Comment:
   [P2] Do not circuit-break data-dependent failures by root class
   
   This circuit is static and keyed only by the root Java class. After three 
equal incomplete results, every graph with that class is rejected for a minute 
before inspection. Reasons such as `object_budget_exceeded` and 
`time_budget_exceeded` are graph-dependent, so three large 
`HivePartitionValues` entries can make small, fully supported tables in 
unrelated catalogs miss the weighted cache as `estimator_circuit_open`; a valid 
sibling cannot record success because it is short-circuited here. Please scope 
suppression to the actual cache owner/key or only class-break deterministic 
unsupported-type failures, and test an invalid root followed by a valid root of 
the same class.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergPartition.java:
##########
@@ -79,4 +81,10 @@ public List<String> getPartitionValues() {
     public List<String> getTransforms() {
         return transforms;
     }
+
+    IcebergPartition immutableCopy() {
+        return new IcebergPartition(partitionName, specId, recordCount, 
fileSizeInBytes, fileCount,
+                lastUpdateTime, lastSnapshotId, 
ImmutableList.copyOf(partitionValues),

Review Comment:
   [P2] Preserve supported NULL partition values in the immutable copy
   
   `generateIcebergPartition()` intentionally appends Java `null` for a NULL 
partition field, and `getPartitionRange()` has a dedicated null branch, but 
Guava `ImmutableList.copyOf` rejects null elements here. With direct or 
inherited snapshot weight governance, publication therefore produces an 
incomplete estimate and serves the projection only once without caching it; 
every later lookup re-enumerates the partitions metadata table. Please use an 
ownership-isolated, null-tolerant unmodifiable copy and add a weighted 
snapshot-cache test with a NULL partition value.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonCacheSizeEstimator.java:
##########
@@ -0,0 +1,271 @@
+// 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.
+
+package org.apache.doris.datasource.paimon;
+
+import org.apache.doris.datasource.metacache.JvmSizeUtils;
+import org.apache.doris.datasource.metacache.MetaCacheSizeEstimate;
+import org.apache.doris.datasource.metacache.OwnedObjectSizeEstimator;
+
+import org.apache.paimon.FileStore;
+import org.apache.paimon.schema.TableSchema;
+import org.apache.paimon.table.FallbackReadFileStoreTable;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.Table;
+import org.apache.paimon.types.ArrayType;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.MapType;
+import org.apache.paimon.types.MultisetType;
+import org.apache.paimon.types.RowType;
+import org.apache.paimon.types.VectorType;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Full construction-time estimator for Paimon snapshot projections. */
+final class PaimonCacheSizeEstimator {
+    private PaimonCacheSizeEstimator() {
+    }
+
+    static MetaCacheSizeEstimate estimateSnapshotEntry(
+            PaimonSnapshotEntryKey key, PaimonSnapshotCacheValue value) {
+        Table table = value.getSnapshot().getTable();
+        if (!isSupportedTable(table)) {
+            return MetaCacheSizeEstimate.incomplete("unsupported_paimon_table:"
+                    + (table == null ? "null" : table.getClass().getName()));
+        }
+        MetaCacheSizeEstimate owned = OwnedObjectSizeEstimator.estimate(key, 
value);
+        if (!owned.isComplete()) {
+            return owned;
+        }
+        // Materialize lazyStore before admission. It is then covered by the 
version-pinned type
+        // formula below; walking the whole SDK graph would cross 
implementation caches/services.
+        ((FileStoreTable) table).store();
+        long bytes = add(owned.getBytes(), estimateTable(
+                table, Collections.newSetFromMap(new IdentityHashMap<>())));
+        bytes = add(bytes, 
JvmSizeUtils.shallowSizeOf(MetaCacheSizeEstimate.class));
+        return MetaCacheSizeEstimate.complete(bytes);
+    }
+
+    /**
+     * Count the snapshot-scoped table wrapper and its immutable 
schema/options. FileIO, catalog
+     * environment and SDK-internal shared caches remain ownership boundaries.
+     */
+    private static long estimateTable(Table table, Set<RowType> 
visitedRowTypes) {
+        if (table == null) {
+            return 0L;
+        }
+        long bytes = JvmSizeUtils.shallowSizeOf(table.getClass());
+        bytes = add(bytes, JvmSizeUtils.sizeOfString(table.name()));
+        long rowTypeBytes = estimateRowType(table.rowType(), visitedRowTypes);
+        bytes = add(bytes, rowTypeBytes);
+        bytes = add(bytes, estimateStringList(table.partitionKeys()));
+        bytes = add(bytes, estimateStringList(table.primaryKeys()));
+        bytes = add(bytes, estimateStringMap(table.options()));
+        bytes = add(bytes, 
table.comment().map(JvmSizeUtils::sizeOfString).orElse(0L));
+        if (table instanceof FileStoreTable) {
+            FileStoreTable fileStoreTable = (FileStoreTable) table;
+            bytes = add(bytes, estimateTableSchema(fileStoreTable.schema()));
+            bytes = add(bytes, 
JvmSizeUtils.shallowSizeOf(fileStoreTable.location().getClass()));
+            bytes = add(bytes, 
JvmSizeUtils.sizeOfString(fileStoreTable.location().toString()));
+            // store() was materialized before this formula was evaluated.
+            FileStore<?> store = fileStoreTable.store();
+            bytes = add(bytes, JvmSizeUtils.shallowSizeOf(store.getClass()));
+            bytes = add(bytes, estimateRowType(store.partitionType(), 
visitedRowTypes));
+            // Paimon derives several separately retained RowTypes and small 
factories from the
+            // same schema. Count six full-schema equivalents plus wrapper 
headroom. Exact field
+            // contracts are pinned by ExternalSdkSizeFieldCoverageTest on 
every SDK upgrade.
+            bytes = add(bytes, multiply(6L, estimateRowType(table.rowType(),
+                    Collections.newSetFromMap(new IdentityHashMap<>()))));
+            bytes = add(bytes, 16L * 1024L);
+            if (table instanceof FallbackReadFileStoreTable) {
+                FallbackReadFileStoreTable fallback = 
(FallbackReadFileStoreTable) table;
+                bytes = add(bytes, estimateTable(fallback.wrapped(), 
visitedRowTypes));
+                bytes = add(bytes, estimateTable(fallback.other(), 
visitedRowTypes));
+            }
+        }
+        return bytes;
+    }
+
+    private static boolean isSupportedTable(Table table) {
+        if (table == null) {
+            return false;
+        }
+        String className = table.getClass().getName();
+        if 
("org.apache.paimon.table.AppendOnlyFileStoreTable".equals(className)

Review Comment:
   [P2] Support the privilege wrapper before rejecting the table
   
   A production Paimon table can be a `PrivilegedFileStoreTable`: Doris 
explicitly accepts that delegate in `PaimonReaderOptions`, and its 
schema/time-travel copies preserve the wrapper. Such a table reaches snapshot 
publication still wrapped, but this exact-class allowlist rejects it as 
`unsupported_paimon_table` without examining the supported underlying 
file-store table. With snapshot weight governance enabled the projection is 
then returned once but never cached, so every request reloads and re-enumerates 
all partitions. Please handle the approved privilege delegate chain (and 
account for its owned wrapper state) and cover it with a weighted-cache 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