924060929 commented on code in PR #67996: URL: https://github.com/apache/doris/pull/67996#discussion_r4059599061
########## fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonMetaCacheCatalog.java: ########## @@ -0,0 +1,355 @@ +// 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.connector.paimon; + +import org.apache.doris.connector.cache.CacheSpec; +import org.apache.doris.connector.cache.CatalogMetaCache; +import org.apache.doris.connector.cache.JvmSizeUtils; +import org.apache.doris.connector.cache.MetaCache; +import org.apache.doris.connector.cache.MetaCacheDefinition; +import org.apache.doris.connector.cache.MetaCacheSizeEstimators; +import org.apache.doris.connector.cache.ScopePath; + +import org.apache.paimon.catalog.Catalog; +import org.apache.paimon.catalog.CatalogLoader; +import org.apache.paimon.catalog.Database; +import org.apache.paimon.catalog.DelegateCatalog; +import org.apache.paimon.catalog.Identifier; +import org.apache.paimon.catalog.PropertyChange; +import org.apache.paimon.fs.Path; +import org.apache.paimon.options.CatalogOptions; +import org.apache.paimon.options.MemorySize; +import org.apache.paimon.options.Options; +import org.apache.paimon.privilege.PrivilegedCatalog; +import org.apache.paimon.schema.SchemaChange; +import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.paimon.table.FileStoreTable; +import org.apache.paimon.table.Table; +import org.apache.paimon.table.system.SystemTableLoader; +import org.apache.paimon.utils.SegmentsCache; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongSupplier; + +/** + * Doris-owned replacement for Paimon's {@code CachingCatalog}. Table and database entries live in + * {@link CatalogMetaCache}, so a Doris catalog/database/table invalidation fences every matching + * in-flight load and cached value. + * + * <p>The cache retains raw table metadata. Paimon's privilege catalog is applied outside this + * wrapper so every lookup receives a fresh checker instead of caching one authorization snapshot. + * + * <p>The user's {@code paimon.cache-enabled} and access/write expiry settings remain authoritative. + * The Paimon SDK wrapper itself is disabled because a second hidden table cache cannot participate + * in Doris invalidation. Under a Doris weight budget, mutable SDK snapshot/stats/manifest caches are + * not attached: their post-publication growth cannot be reweighed by the enclosing budget. + */ +final class PaimonMetaCacheCatalog extends DelegateCatalog { + + private static final int DATABASE_CACHE_CAPACITY = 100; + static final long TABLE_ENTRY_OVERHEAD_BYTES = JvmSizeUtils.saturatedAdd( + JvmSizeUtils.instanceSize(ExpiringValue.class), JvmSizeUtils.instanceSize(AtomicLong.class)); + + private final CatalogMetaCache metaCache; + private final MetaCache<Identifier, ExpiringValue<Table>> tableCache; + private final MetaCache<String, ExpiringValue<Database>> databaseCache; + private final SegmentsCache<Path> manifestCache; + private final long tableExpireAfterAccessNanos; + private final long databaseExpireAfterAccessNanos; + private final long expireAfterWriteNanos; + private final int snapshotMaxNumPerTable; + private final boolean attachSdkCaches; + private final LongSupplier nanoTime; + + static Catalog tryToCreate(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean cacheEnabled, + boolean hasEnclosingWeightLimit) { + Catalog cached = new PaimonMetaCacheCatalog(wrapped, metaCache, tableCacheMaxSize, Review Comment: Fixed in a39998dce530. Catalog construction now rolls back both registered MetaCaches and closes the raw catalog if the outer privilege decorator fails. The rollback is scoped to these two caches, so unrelated sibling caches remain intact. Added a fail-once decoration test that verifies the registrations are empty after failure and the same owner can create the catalog successfully on retry. ########## fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonCacheSizeEstimator.java: ########## @@ -0,0 +1,152 @@ +// 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.connector.paimon; + +import org.apache.doris.connector.cache.JvmSizeUtils; +import org.apache.doris.connector.cache.MetaCacheSizeEstimate; +import org.apache.doris.connector.cache.ReflectiveObjectSizeEstimator; + +import org.apache.paimon.catalog.Identifier; +import org.apache.paimon.fs.Path; +import org.apache.paimon.privilege.PrivilegedFileStoreTable; +import org.apache.paimon.table.CatalogEnvironment; +import org.apache.paimon.table.DelegatedFileStoreTable; +import org.apache.paimon.table.FallbackReadFileStoreTable; +import org.apache.paimon.table.FileStoreTable; +import org.apache.paimon.table.FormatTable; +import org.apache.paimon.table.Table; +import org.apache.paimon.table.iceberg.IcebergTable; +import org.apache.paimon.table.lance.LanceTable; +import org.apache.paimon.table.object.ObjectTable; + +import java.net.URI; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + +/** + * Retained-size formulas for Paimon table-cache entries. + * + * <p>The table's shallow size includes references to FileIO, catalog loaders, and lock factories, + * but their graphs are catalog-scoped executable services rather than entry-owned metadata. Walking + * those graphs both double-counts shared state and reaches strongly encapsulated JDK objects. The + * estimator therefore expands only immutable metadata owned by the entry. + */ +final class PaimonCacheSizeEstimator { + private PaimonCacheSizeEstimator() { + } + + static MetaCacheSizeEstimate estimateTable(Identifier key, Table table, long entryOverheadBytes) { + if (table instanceof PrivilegedFileStoreTable) { + return MetaCacheSizeEstimate.incomplete( + "authorization decorators must be applied outside the metadata cache"); + } + long bytes = add(entryOverheadBytes, ReflectiveObjectSizeEstimator.estimateComplete(key)); + if (table instanceof FileStoreTable) { + Set<Object> visited = Collections.newSetFromMap(new IdentityHashMap<>()); + bytes = add(bytes, estimateFileStoreTable((FileStoreTable) table, visited)); + } else { + bytes = add(bytes, JvmSizeUtils.instanceSize(table.getClass())); Review Comment: Fixed in a39998dce530. Weight-governed admission now treats unknown non-FileStore table implementations as incomplete instead of assuming their public metadata covers the retained graph. The supported Format/Object/Lance/Iceberg shapes remain explicitly admitted. Added an AllTableOptionsTable case with a large retained options map and verified it is not cached or charged as a shallow entry. ########## fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonMetaCacheCatalog.java: ########## @@ -0,0 +1,355 @@ +// 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.connector.paimon; + +import org.apache.doris.connector.cache.CacheSpec; +import org.apache.doris.connector.cache.CatalogMetaCache; +import org.apache.doris.connector.cache.JvmSizeUtils; +import org.apache.doris.connector.cache.MetaCache; +import org.apache.doris.connector.cache.MetaCacheDefinition; +import org.apache.doris.connector.cache.MetaCacheSizeEstimators; +import org.apache.doris.connector.cache.ScopePath; + +import org.apache.paimon.catalog.Catalog; +import org.apache.paimon.catalog.CatalogLoader; +import org.apache.paimon.catalog.Database; +import org.apache.paimon.catalog.DelegateCatalog; +import org.apache.paimon.catalog.Identifier; +import org.apache.paimon.catalog.PropertyChange; +import org.apache.paimon.fs.Path; +import org.apache.paimon.options.CatalogOptions; +import org.apache.paimon.options.MemorySize; +import org.apache.paimon.options.Options; +import org.apache.paimon.privilege.PrivilegedCatalog; +import org.apache.paimon.schema.SchemaChange; +import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.paimon.table.FileStoreTable; +import org.apache.paimon.table.Table; +import org.apache.paimon.table.system.SystemTableLoader; +import org.apache.paimon.utils.SegmentsCache; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongSupplier; + +/** + * Doris-owned replacement for Paimon's {@code CachingCatalog}. Table and database entries live in + * {@link CatalogMetaCache}, so a Doris catalog/database/table invalidation fences every matching + * in-flight load and cached value. + * + * <p>The cache retains raw table metadata. Paimon's privilege catalog is applied outside this + * wrapper so every lookup receives a fresh checker instead of caching one authorization snapshot. + * + * <p>The user's {@code paimon.cache-enabled} and access/write expiry settings remain authoritative. + * The Paimon SDK wrapper itself is disabled because a second hidden table cache cannot participate + * in Doris invalidation. Under a Doris weight budget, mutable SDK snapshot/stats/manifest caches are + * not attached: their post-publication growth cannot be reweighed by the enclosing budget. + */ +final class PaimonMetaCacheCatalog extends DelegateCatalog { + + private static final int DATABASE_CACHE_CAPACITY = 100; + static final long TABLE_ENTRY_OVERHEAD_BYTES = JvmSizeUtils.saturatedAdd( + JvmSizeUtils.instanceSize(ExpiringValue.class), JvmSizeUtils.instanceSize(AtomicLong.class)); + + private final CatalogMetaCache metaCache; + private final MetaCache<Identifier, ExpiringValue<Table>> tableCache; + private final MetaCache<String, ExpiringValue<Database>> databaseCache; + private final SegmentsCache<Path> manifestCache; + private final long tableExpireAfterAccessNanos; + private final long databaseExpireAfterAccessNanos; + private final long expireAfterWriteNanos; + private final int snapshotMaxNumPerTable; + private final boolean attachSdkCaches; + private final LongSupplier nanoTime; + + static Catalog tryToCreate(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean cacheEnabled, + boolean hasEnclosingWeightLimit) { + Catalog cached = new PaimonMetaCacheCatalog(wrapped, metaCache, tableCacheMaxSize, + tableCacheTtlSecond, catalogOptions, cacheEnabled, hasEnclosingWeightLimit, System::nanoTime); + return PrivilegedCatalog.tryToCreate(cached, catalogOptions); + } + + PaimonMetaCacheCatalog(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean hasEnclosingWeightLimit, + LongSupplier nanoTime) { + this(wrapped, metaCache, tableCacheMaxSize, tableCacheTtlSecond, catalogOptions, + true, hasEnclosingWeightLimit, nanoTime); + } + + private PaimonMetaCacheCatalog(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean cacheEnabled, + boolean hasEnclosingWeightLimit, LongSupplier nanoTime) { + super(wrapped); + this.metaCache = metaCache; + this.nanoTime = nanoTime; + + Duration expireAfterAccess = catalogOptions.get(CatalogOptions.CACHE_EXPIRE_AFTER_ACCESS); Review Comment: Fixed in a39998dce530. Access/write settings are parsed only when the Doris cache is enabled, and snapshot/manifest settings are parsed only when mutable SDK child caches are actually attached. Added disabled-cache and enclosing-weight-limit tests with malformed dormant settings. ########## fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonMetaCacheCatalog.java: ########## @@ -0,0 +1,355 @@ +// 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.connector.paimon; + +import org.apache.doris.connector.cache.CacheSpec; +import org.apache.doris.connector.cache.CatalogMetaCache; +import org.apache.doris.connector.cache.JvmSizeUtils; +import org.apache.doris.connector.cache.MetaCache; +import org.apache.doris.connector.cache.MetaCacheDefinition; +import org.apache.doris.connector.cache.MetaCacheSizeEstimators; +import org.apache.doris.connector.cache.ScopePath; + +import org.apache.paimon.catalog.Catalog; +import org.apache.paimon.catalog.CatalogLoader; +import org.apache.paimon.catalog.Database; +import org.apache.paimon.catalog.DelegateCatalog; +import org.apache.paimon.catalog.Identifier; +import org.apache.paimon.catalog.PropertyChange; +import org.apache.paimon.fs.Path; +import org.apache.paimon.options.CatalogOptions; +import org.apache.paimon.options.MemorySize; +import org.apache.paimon.options.Options; +import org.apache.paimon.privilege.PrivilegedCatalog; +import org.apache.paimon.schema.SchemaChange; +import org.apache.paimon.shade.caffeine2.com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.paimon.table.FileStoreTable; +import org.apache.paimon.table.Table; +import org.apache.paimon.table.system.SystemTableLoader; +import org.apache.paimon.utils.SegmentsCache; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongSupplier; + +/** + * Doris-owned replacement for Paimon's {@code CachingCatalog}. Table and database entries live in + * {@link CatalogMetaCache}, so a Doris catalog/database/table invalidation fences every matching + * in-flight load and cached value. + * + * <p>The cache retains raw table metadata. Paimon's privilege catalog is applied outside this + * wrapper so every lookup receives a fresh checker instead of caching one authorization snapshot. + * + * <p>The user's {@code paimon.cache-enabled} and access/write expiry settings remain authoritative. + * The Paimon SDK wrapper itself is disabled because a second hidden table cache cannot participate + * in Doris invalidation. Under a Doris weight budget, mutable SDK snapshot/stats/manifest caches are + * not attached: their post-publication growth cannot be reweighed by the enclosing budget. + */ +final class PaimonMetaCacheCatalog extends DelegateCatalog { + + private static final int DATABASE_CACHE_CAPACITY = 100; + static final long TABLE_ENTRY_OVERHEAD_BYTES = JvmSizeUtils.saturatedAdd( + JvmSizeUtils.instanceSize(ExpiringValue.class), JvmSizeUtils.instanceSize(AtomicLong.class)); + + private final CatalogMetaCache metaCache; + private final MetaCache<Identifier, ExpiringValue<Table>> tableCache; + private final MetaCache<String, ExpiringValue<Database>> databaseCache; + private final SegmentsCache<Path> manifestCache; + private final long tableExpireAfterAccessNanos; + private final long databaseExpireAfterAccessNanos; + private final long expireAfterWriteNanos; + private final int snapshotMaxNumPerTable; + private final boolean attachSdkCaches; + private final LongSupplier nanoTime; + + static Catalog tryToCreate(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean cacheEnabled, + boolean hasEnclosingWeightLimit) { + Catalog cached = new PaimonMetaCacheCatalog(wrapped, metaCache, tableCacheMaxSize, + tableCacheTtlSecond, catalogOptions, cacheEnabled, hasEnclosingWeightLimit, System::nanoTime); + return PrivilegedCatalog.tryToCreate(cached, catalogOptions); + } + + PaimonMetaCacheCatalog(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean hasEnclosingWeightLimit, + LongSupplier nanoTime) { + this(wrapped, metaCache, tableCacheMaxSize, tableCacheTtlSecond, catalogOptions, + true, hasEnclosingWeightLimit, nanoTime); + } + + private PaimonMetaCacheCatalog(Catalog wrapped, CatalogMetaCache metaCache, int tableCacheMaxSize, + long tableCacheTtlSecond, Options catalogOptions, boolean cacheEnabled, + boolean hasEnclosingWeightLimit, LongSupplier nanoTime) { + super(wrapped); + this.metaCache = metaCache; + this.nanoTime = nanoTime; + + Duration expireAfterAccess = catalogOptions.get(CatalogOptions.CACHE_EXPIRE_AFTER_ACCESS); + Duration expireAfterWrite = catalogOptions.get(CatalogOptions.CACHE_EXPIRE_AFTER_WRITE); + if (cacheEnabled) { + requirePositive(expireAfterAccess, CatalogOptions.CACHE_EXPIRE_AFTER_ACCESS.key()); + requirePositive(expireAfterWrite, CatalogOptions.CACHE_EXPIRE_AFTER_WRITE.key()); + } + long paimonAccessNanos = cacheEnabled ? saturatedNanos(expireAfterAccess) : Long.MAX_VALUE; + this.tableExpireAfterAccessNanos = cacheEnabled && tableCacheTtlSecond > 0 + ? Math.min(paimonAccessNanos, saturatedNanos(Duration.ofSeconds(tableCacheTtlSecond))) + : paimonAccessNanos; + this.databaseExpireAfterAccessNanos = paimonAccessNanos; + this.expireAfterWriteNanos = cacheEnabled ? saturatedNanos(expireAfterWrite) : Long.MAX_VALUE; + + CacheSpec tableSpec = CacheSpec.of(cacheEnabled, + cacheEnabled && tableCacheTtlSecond > 0 + ? CacheSpec.CACHE_NO_TTL : CacheSpec.CACHE_TTL_DISABLE_CACHE, + tableCacheMaxSize); + this.tableCache = metaCache.create(MetaCacheDefinition + .<Identifier, ExpiringValue<Table>>builder("paimon-table", tableSpec, + id -> ScopePath.table(id.getDatabaseName(), id.getTableName())) + .sizeEstimator((id, value) -> PaimonCacheSizeEstimator.estimateTable( + id, value.value, TABLE_ENTRY_OVERHEAD_BYTES)) + .build()); + CacheSpec dbSpec = CacheSpec.of(cacheEnabled, cacheEnabled + ? CacheSpec.CACHE_NO_TTL : CacheSpec.CACHE_TTL_DISABLE_CACHE, DATABASE_CACHE_CAPACITY); + this.databaseCache = metaCache.create(MetaCacheDefinition + .<String, ExpiringValue<Database>>builder("paimon-database", dbSpec, ScopePath::database) + .sizeEstimator(MetaCacheSizeEstimators.reflective()) + .build()); + + this.attachSdkCaches = cacheEnabled && !hasEnclosingWeightLimit; + this.manifestCache = attachSdkCaches ? buildManifestCache(catalogOptions) : null; + this.snapshotMaxNumPerTable = catalogOptions.get( + CatalogOptions.CACHE_SNAPSHOT_MAX_NUM_PER_TABLE); + } + + @Override + public Table getTable(Identifier identifier) throws TableNotExistException { + if (identifier.isSystemTable()) { + Identifier origin = new Identifier(identifier.getDatabaseName(), identifier.getTableName(), + identifier.getBranchName(), null); + Table originTable = getTable(origin); + if (!(originTable instanceof FileStoreTable)) { + return super.getTable(identifier); + } + Table systemTable = SystemTableLoader.load(identifier.getSystemTableName(), + (FileStoreTable) originTable); + if (systemTable == null) { + throw new TableNotExistException(identifier); + } + return systemTable; + } + + while (true) { + long now = nanoTime.getAsLong(); + ExpiringValue<Table> cached = tableCache.getIfPresent(identifier); + if (cached != null) { + if (cached.tryAccess(now, tableExpireAfterAccessNanos, expireAfterWriteNanos)) { + return cached.value; + } + tableCache.compareAndSet(identifier, cached, null); + continue; + } + try { + return tableCache.get(identifier, ignored -> { Review Comment: Fixed in a39998dce530. Both table and database lookups now pass the value returned by MetaCache.get through the same expiration check/CAS eviction loop as a direct hit. Deterministic barrier tests cover a concurrent publisher filling the miss gap and advancing past the TTL before the original reader resumes. -- 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]
