diff --git a/fe/fe-connector/README.md b/fe/fe-connector/README.md
index b3019c35955951..5a1dcef5e2d883 100644
--- a/fe/fe-connector/README.md
+++ b/fe/fe-connector/README.md
@@ -56,8 +56,7 @@ endpoint properties)
| Module | Role |
|---|---|
-| `fe-connector-metacache-spi` | JDK-only cache policy, entry-definition, invalidation, statistics, and lifecycle contracts. It has no fe-core, cache-library, or data-source SDK dependency. |
-| `fe-connector-metacache` | Shared Caffeine-backed cache runtime used by fe-core and connector plugins. It owns entry generations, refresh, scoped invalidation, catalog entry groups, and the engine registry; it never depends on fe-core or a data-source SDK. |
+| `fe-connector-cache` | Self-contained caching framework used by several connectors. No fe-core dependency; it is bundled into each consuming plugin, so shared third-party libraries stay at the consumers' lowest common version (see the version notes in consumer poms). |
| `fe-connector-hms-hive-shade` | Slim, relocated HMS metastore-client closure for connectors that speak HMS thrift. The pom comments say exactly what relocates where and why. |
| `fe-connector-paimon-hive-shade` | Paimon-private relocated HMS-thrift closure; same idea, different owner. |
@@ -184,10 +183,10 @@ metastore/shade/cache). For a write path, the richest example is
6. **Property ownership.** Metadata-connection properties are parsed in your
connector (or the metastore layer). Storage properties belong to
`fe-filesystem`. Do not add parsing to fe-core — rule 2 above.
-7. **Caching.** Describe reusable entries with `fe-connector-metacache-spi`
- and run them through `fe-connector-metacache` (example:
- `PaimonLatestSnapshotCache`). Keep shared third-party versions aligned with
- the other consumers (see the version notes in `fe-connector-paimon/pom.xml`). Respect the
+7. **Caching.** Reuse `fe-connector-cache` (example:
+ `PaimonLatestSnapshotCache`). Bundle the caching library into your plugin
+ zip and keep shared third-party versions aligned with the other consumers
+ (see the version notes in `fe-connector-paimon/pom.xml`). Respect the
authorization invariant in `AGENTS.md`: a cross-query cache must never
serve metadata that would bypass per-user, load-time authorization.
8. **Shading.** If your client stack drags a conflicting closure (hive/thrift
diff --git a/fe/fe-connector/fe-connector-metacache/pom.xml b/fe/fe-connector/fe-connector-cache/pom.xml
similarity index 59%
rename from fe/fe-connector/fe-connector-metacache/pom.xml
rename to fe/fe-connector/fe-connector-cache/pom.xml
index 33ced432a3a733..f9ba332609f91b 100644
--- a/fe/fe-connector/fe-connector-metacache/pom.xml
+++ b/fe/fe-connector/fe-connector-cache/pom.xml
@@ -29,21 +29,24 @@ under the License.
../pom.xml
- fe-connector-metacache
+ fe-connector-cache
jar
- Doris FE Connector MetaCache Runtime
+ Doris FE Connector Cache Framework
- Shared external metadata cache runtime used by fe-core and connector plugins.
- Contains the Caffeine-backed entry implementation, catalog entry grouping and reusable
- connector cache helpers. It depends on fe-connector-metacache-spi and never depends on fe-core.
+ Connector-side meta-cache framework (CacheSpec + MetaCacheEntry + CacheFactory + MetaCacheEntryStats),
+ an INDEPENDENT copy of fe-core's `org.apache.doris.datasource.metacache` framework re-homed under the
+ `org.apache.doris.connector.*` prefix so the connector plugins can reuse it (they cannot import fe-core).
+ fe-core keeps its own copy untouched; the two live side-by-side until every connector has migrated, then
+ the fe-core copy is retired. fe-core does NOT depend on this module.
+
+ This module is bundled into each connector plugin zip (child-first), so it uses the plugin's own bundled
+ Caffeine at runtime; Caffeine is therefore `provided` here (compiled against, never packaged by this
+ module). The framework's public API (MetaCacheEntry) is Caffeine-free, and fe-core and the connectors
+ never share a cache object across the classloader boundary, so no Caffeine type crosses and there is no
+ split-brain. Two knobs fe-core reads from static Config are constructor-injected here.
-
- ${project.groupId}
- fe-connector-metacache-spi
- ${project.version}
-
com.github.ben-manes.caffeine
caffeine
@@ -55,19 +58,9 @@ under the License.
junit-jupiter
test
-
- junit
- junit
- test
-
-
- com.google.guava
- guava
- test
-
- doris-fe-connector-metacache
+ doris-fe-connector-cache
diff --git a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/CacheFactory.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/CacheFactory.java
similarity index 89%
rename from fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/CacheFactory.java
rename to fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/CacheFactory.java
index 5c809753bc821b..03e4126e9911be 100644
--- a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/CacheFactory.java
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/CacheFactory.java
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache;
+package org.apache.doris.connector.cache;
import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
@@ -32,8 +32,11 @@
/**
* Factory to create Caffeine cache.
*
- *
This type is internal to the shared MetaCache runtime. Its public methods return Caffeine
- * types; callers outside this module use {@link MetaCacheEntry} instead.
+ *
Connector-side copy of fe-core {@code org.apache.doris.common.CacheFactory} (independent-copy meta-cache
+ * migration): connector plugins cannot import fe-core, so the framework is duplicated under
+ * {@code org.apache.doris.connector.cache}. This type is framework-internal — its public methods RETURN
+ * Caffeine types, which must never cross to connector (child-first) code; connectors only touch the
+ * Caffeine-free {@link MetaCacheEntry} API. Keep behaviourally in sync with the fe-core original.
*
*
This class is used to create Caffeine cache with specified parameters.
* It is used to create both sync and async cache.
diff --git a/fe/fe-connector/fe-connector-metacache-spi/src/main/java/org/apache/doris/connector/metacache/spi/CacheSpec.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/CacheSpec.java
similarity index 90%
rename from fe/fe-connector/fe-connector-metacache-spi/src/main/java/org/apache/doris/connector/metacache/spi/CacheSpec.java
rename to fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/CacheSpec.java
index 0f572ccf7155ca..0524b31d402f48 100644
--- a/fe/fe-connector/fe-connector-metacache-spi/src/main/java/org/apache/doris/connector/metacache/spi/CacheSpec.java
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/CacheSpec.java
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache.spi;
+package org.apache.doris.connector.cache;
import java.util.HashMap;
import java.util.Map;
@@ -25,9 +25,18 @@
/**
* Common cache specification for external metadata caches.
*
- *
The type is part of the connector-facing MetaCache SPI and therefore depends only on JDK types.
- * Property validation reports {@link IllegalArgumentException}; fe-core adapters may translate that
- * exception at their own API boundary.
+ *
Connector-side copy of the meta-cache property model (independent-copy meta-cache migration). fe-core is
+ * NOT changed: it keeps its own {@code org.apache.doris.datasource.metacache.CacheSpec}; this is a separate
+ * class under {@code org.apache.doris.connector.*} used only by the connector plugins. Although that prefix is
+ * parent-first, fe-core does not depend on this module, so the class resolves parent → miss → CHILD and is
+ * child-loaded per plugin — fe-core and the plugins do NOT share one {@code Class} identity. It carries no
+ * third-party dependency (JDK only) and never crosses the fe-core↔connector boundary as an object (only its
+ * {@code IllegalArgumentException}, a JDK type, crosses), so it is safe on both classpaths.
+ *
+ *
The {@code check*Property} validators throw {@link IllegalArgumentException} (fe-core's
+ * {@code PluginDrivenExternalCatalog.checkProperties} re-wraps it into a {@code DdlException} verbatim; the
+ * legacy fe-core catalogs that still call these validators declare {@code throws DdlException} but no longer
+ * need it). The user-facing message text is identical to the legacy one ({@code "... is wrong, value is ..."}).
*
*
diff --git a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/ConnectorMetadataCache.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ConnectorMetadataCache.java
similarity index 98%
rename from fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/ConnectorMetadataCache.java
rename to fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ConnectorMetadataCache.java
index db66be43a85243..fde68573e73c50 100644
--- a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/ConnectorMetadataCache.java
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ConnectorMetadataCache.java
@@ -15,9 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache;
-
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+package org.apache.doris.connector.cache;
import java.util.Collections;
import java.util.Map;
diff --git a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/ConnectorTableKey.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ConnectorTableKey.java
similarity index 98%
rename from fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/ConnectorTableKey.java
rename to fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ConnectorTableKey.java
index 0cb9aa4d545767..41ac71c9930240 100644
--- a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/ConnectorTableKey.java
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/ConnectorTableKey.java
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache;
+package org.apache.doris.connector.cache;
import java.util.Objects;
diff --git a/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheEntry.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheEntry.java
new file mode 100644
index 00000000000000..425697ef9b1098
--- /dev/null
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheEntry.java
@@ -0,0 +1,345 @@
+// 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.cache;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import com.github.benmanes.caffeine.cache.stats.CacheStats;
+
+import java.util.Objects;
+import java.util.OptionalLong;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Unified cache entry abstraction.
+ * It stores one logical cache dataset and provides optional lazy loading,
+ * key/predicate/full invalidation, and lightweight runtime stats.
+ *
+ * Connector-side copy of fe-core {@code org.apache.doris.datasource.metacache.MetaCacheEntry}
+ * (independent-copy meta-cache migration): connector plugins cannot import fe-core, so the framework is
+ * duplicated under {@code org.apache.doris.connector.cache}. The public API is Caffeine-free (Caffeine is
+ * encapsulated), so instances are safe to hold in connector (child-first) code. Two knobs that fe-core reads
+ * from static {@code Config} are here supplied by the connector via the constructor
+ * ({@code refreshAfterWriteSeconds}, {@code manualMissLoadEnabled}); otherwise keep in sync with fe-core.
+ */
+public class MetaCacheEntry {
+ // Use striped locks to deduplicate slow external loads without managing per-key lock lifecycle.
+ private static final int LOAD_LOCK_STRIPES = 128;
+
+ private final String name;
+ private final Function loader;
+ private final CacheSpec cacheSpec;
+ private final boolean effectiveEnabled;
+ private final boolean autoRefresh;
+ // fe-core reads these two from Config; the connector copy has no fe-core Config, so they are injected.
+ // refreshAfterWriteSeconds is already in seconds (fe-core computes Config.*_minutes * 60 at its call site).
+ private final long refreshAfterWriteSeconds;
+ private final boolean manualMissLoadEnabled;
+ // Keep the loading cache for refreshAfterWrite and the legacy sync-load path when the feature is disabled.
+ private final LoadingCache loadingData;
+ // Use the plain cache view for manual miss load so slow I/O does not happen in Caffeine's sync load path.
+ private final Cache data;
+ // Protect one key stripe at a time to deduplicate concurrent miss loads with bounded lock count.
+ private final Object[] loadLocks = new Object[LOAD_LOCK_STRIPES];
+ private final AtomicLong invalidateCount = new AtomicLong(0);
+ // Bump generation before invalidation so in-flight manual loads do not repopulate stale values.
+ private final AtomicLong invalidateGeneration = new AtomicLong(0);
+ // Track load statistics outside Caffeine because manual miss loads bypass the built-in load counters.
+ private final AtomicLong loadSuccessCount = new AtomicLong(0);
+ private final AtomicLong loadFailureCount = new AtomicLong(0);
+ private final AtomicLong totalLoadTimeNanos = new AtomicLong(0);
+ private final AtomicLong lastLoadSuccessTimeMs = new AtomicLong(-1L);
+ private final AtomicLong lastLoadFailureTimeMs = new AtomicLong(-1L);
+ private final AtomicReference lastError = new AtomicReference<>("");
+
+ /**
+ * Convenience constructor for the common connector case: a loader-backed entry with no auto-refresh and no
+ * manual miss load (Caffeine's sync load path). Use the full constructor for contextual-only entries,
+ * auto-refresh, or manual miss load.
+ */
+ public MetaCacheEntry(String name, Function loader, CacheSpec cacheSpec, ExecutorService refreshExecutor) {
+ this(name, loader, cacheSpec, refreshExecutor, false, false, 0L, false);
+ }
+
+ public MetaCacheEntry(String name, Function loader, CacheSpec cacheSpec,
+ ExecutorService refreshExecutor, boolean autoRefresh, boolean contextualOnly,
+ long refreshAfterWriteSeconds, boolean manualMissLoadEnabled) {
+ this.name = name;
+ if (contextualOnly) {
+ if (loader != null) {
+ throw new IllegalArgumentException("contextual-only entry loader must be null");
+ }
+ if (autoRefresh) {
+ throw new IllegalArgumentException("contextual-only entry can not enable auto refresh");
+ }
+ } else {
+ Objects.requireNonNull(loader, "loader can not be null");
+ }
+ this.loader = loader;
+ this.cacheSpec = Objects.requireNonNull(cacheSpec, "cacheSpec can not be null");
+ this.autoRefresh = autoRefresh;
+ this.refreshAfterWriteSeconds = refreshAfterWriteSeconds;
+ this.manualMissLoadEnabled = manualMissLoadEnabled;
+ Objects.requireNonNull(refreshExecutor, "refreshExecutor can not be null");
+ this.effectiveEnabled = CacheSpec.isCacheEnabled(
+ this.cacheSpec.isEnable(), this.cacheSpec.getTtlSecond(), this.cacheSpec.getCapacity());
+ OptionalLong expireAfterAccessSec =
+ effectiveEnabled ? CacheSpec.toExpireAfterAccess(this.cacheSpec.getTtlSecond()) : OptionalLong.empty();
+ OptionalLong refreshAfterWriteSec =
+ effectiveEnabled && autoRefresh
+ ? OptionalLong.of(refreshAfterWriteSeconds)
+ : OptionalLong.empty();
+ long maxSize = effectiveEnabled ? this.cacheSpec.getCapacity() : 0L;
+ CacheFactory cacheFactory = new CacheFactory(
+ expireAfterAccessSec,
+ refreshAfterWriteSec,
+ maxSize,
+ true,
+ null);
+ this.loadingData = cacheFactory.buildCache(this::loadFromDefaultLoader, refreshExecutor);
+ this.data = loadingData;
+ // Initialize striped locks eagerly to keep the hot path allocation-free.
+ for (int i = 0; i < loadLocks.length; i++) {
+ loadLocks[i] = new Object();
+ }
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public V get(K key) {
+ if (!isManualMissLoadEnabled()) {
+ return loadingData.get(key);
+ }
+ return getWithManualLoad(key, this::applyDefaultLoader);
+ }
+
+ public V get(K key, Function missLoader) {
+ Function loadFunction = Objects.requireNonNull(missLoader, "missLoader can not be null");
+ if (!isManualMissLoadEnabled()) {
+ return loadingData.get(key, typedKey -> loadAndTrack(typedKey, loadFunction));
+ }
+ return getWithManualLoad(key, loadFunction);
+ }
+
+ public V getIfPresent(K key) {
+ if (!effectiveEnabled) {
+ return null;
+ }
+ return data.getIfPresent(key);
+ }
+
+ public void put(K key, V value) {
+ if (!effectiveEnabled) {
+ return;
+ }
+ data.put(key, value);
+ }
+
+ /**
+ * The current invalidation generation. Capture this BEFORE a slow external load, then hand it to
+ * {@link #putIfNotInvalidatedSince} so a {@code flush}/{@code invalidate*} that raced the load does not
+ * get its clear silently undone by a stale write-back. Mirrors the guard the manual-miss-load path
+ * ({@link #getWithManualLoad}) applies around its own put; exposed so a caller that does its OWN bulk
+ * external read (e.g. a decorator batching a multi-key RPC and putting each result under its own key)
+ * can reuse the same generation guard instead of an unguarded {@link #put}.
+ */
+ public long invalidationGeneration() {
+ return invalidateGeneration.get();
+ }
+
+ /**
+ * Generation-guarded put: caches {@code (key, value)} only if no invalidation has happened since
+ * {@code generation} was captured (before the caller's external load). If a {@code flush}/{@code
+ * invalidate*} raced the load — bumping the generation either before the put (skip) or between the put
+ * and the recheck (drop only the value we wrote, via {@link #removeLoadedValue}) — the stale value is
+ * NOT left cached, exactly as {@link #getWithManualLoad} does for its single-key load. Additive: the
+ * existing {@link #put} is unchanged; a disabled entry is a no-op.
+ */
+ public void putIfNotInvalidatedSince(long generation, K key, V value) {
+ if (!effectiveEnabled) {
+ return;
+ }
+ synchronized (loadLock(key)) {
+ // A racing flush already bumped the generation before we could put: skip so a stale pre-flush
+ // value is not re-cached (mirrors getWithManualLoad's pre-put guard).
+ if (generation != invalidateGeneration.get()) {
+ return;
+ }
+ data.put(key, value);
+ // A flush landing between the check and the put: drop only the value we just wrote, keeping any
+ // newer replacement intact (mirrors getWithManualLoad's post-put guard).
+ if (generation != invalidateGeneration.get()) {
+ removeLoadedValue(key, value);
+ }
+ }
+ }
+
+ public void invalidateKey(K key) {
+ invalidateGeneration.incrementAndGet();
+ if (data.asMap().remove(key) != null) {
+ invalidateCount.incrementAndGet();
+ }
+ }
+
+ public void invalidateIf(Predicate predicate) {
+ invalidateGeneration.incrementAndGet();
+ data.asMap().keySet().removeIf(key -> {
+ if (predicate.test(key)) {
+ invalidateCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ });
+ }
+
+ public void invalidateAll() {
+ invalidateGeneration.incrementAndGet();
+ long size = data.estimatedSize();
+ data.invalidateAll();
+ invalidateCount.addAndGet(size);
+ }
+
+ public void forEach(BiConsumer consumer) {
+ data.asMap().forEach(consumer);
+ }
+
+ public MetaCacheEntryStats stats() {
+ CacheStats cacheStats = loadingData.stats();
+ long successCount = loadSuccessCount.get();
+ long failureCount = loadFailureCount.get();
+ long totalLoadTime = totalLoadTimeNanos.get();
+ long totalLoadCount = successCount + failureCount;
+ return new MetaCacheEntryStats(
+ cacheSpec.isEnable(),
+ effectiveEnabled,
+ autoRefresh,
+ cacheSpec.getTtlSecond(),
+ cacheSpec.getCapacity(),
+ data.estimatedSize(),
+ cacheStats.requestCount(),
+ cacheStats.hitCount(),
+ cacheStats.missCount(),
+ cacheStats.hitRate(),
+ successCount,
+ failureCount,
+ totalLoadTime,
+ totalLoadCount == 0 ? 0D : (double) totalLoadTime / totalLoadCount,
+ cacheStats.evictionCount(),
+ invalidateCount.get(),
+ lastLoadSuccessTimeMs.get(),
+ lastLoadFailureTimeMs.get(),
+ lastError.get());
+ }
+
+ // Injected at construction (fe-core reads Config.enable_external_meta_cache_manual_miss_load dynamically).
+ private boolean isManualMissLoadEnabled() {
+ return manualMissLoadEnabled;
+ }
+
+ // Execute slow miss loads outside Caffeine's sync load path and suppress stale write-back after invalidation.
+ private V getWithManualLoad(K key, Function loadFunction) {
+ if (!effectiveEnabled) {
+ // Bypass cache entirely when the entry is disabled so manual miss load does not relax disable semantics.
+ return loadAndTrack(key, loadFunction);
+ }
+
+ V value = data.getIfPresent(key);
+ if (value != null) {
+ return value;
+ }
+
+ synchronized (loadLock(key)) {
+ value = data.asMap().get(key);
+ if (value != null) {
+ return value;
+ }
+
+ long generation = invalidateGeneration.get();
+ V loaded = loadAndTrack(key, loadFunction);
+ if (generation != invalidateGeneration.get()) {
+ return loaded;
+ }
+
+ // Keep null results uncached so manual miss load matches LoadingCache null-return behavior.
+ if (loaded == null) {
+ return null;
+ }
+
+ // Leave a narrow hook for tests to pause exactly before the cache put race window.
+ beforeManualCachePutForTest(key, loaded);
+ data.put(key, loaded);
+ if (generation != invalidateGeneration.get()) {
+ removeLoadedValue(key, loaded);
+ }
+ return loaded;
+ }
+ }
+
+ // Remove only the value loaded by the current request and keep newer replacements intact.
+ private void removeLoadedValue(K key, V loaded) {
+ data.asMap().computeIfPresent(key, (ignored, currentValue) -> currentValue == loaded ? null : currentValue);
+ }
+
+ // Map keys to a fixed lock stripe set to bound memory usage while keeping same-key deduplication.
+ private Object loadLock(K key) {
+ int hash = key == null ? 0 : key.hashCode();
+ return loadLocks[(hash & Integer.MAX_VALUE) % loadLocks.length];
+ }
+
+ // Let tests pause between the first generation check and data.put without affecting production behavior.
+ void beforeManualCachePutForTest(K key, V loaded) {
+ }
+
+ private V loadFromDefaultLoader(K key) {
+ return loadAndTrack(key, this::applyDefaultLoader);
+ }
+
+ // Resolve the default loader separately so the manual path can share tracking without double counting.
+ private V applyDefaultLoader(K key) {
+ if (loader == null) {
+ throw new UnsupportedOperationException(
+ String.format("Entry '%s' requires a contextual miss loader.", name));
+ }
+ return loader.apply(key);
+ }
+
+ // Track load outcomes locally because manual miss loads do not contribute to Caffeine load statistics.
+ private V loadAndTrack(K key, Function loadFunction) {
+ long startNanos = System.nanoTime();
+ try {
+ V value = loadFunction.apply(key);
+ loadSuccessCount.incrementAndGet();
+ totalLoadTimeNanos.addAndGet(System.nanoTime() - startNanos);
+ lastLoadSuccessTimeMs.set(System.currentTimeMillis());
+ return value;
+ } catch (RuntimeException | Error e) {
+ loadFailureCount.incrementAndGet();
+ totalLoadTimeNanos.addAndGet(System.nanoTime() - startNanos);
+ lastLoadFailureTimeMs.set(System.currentTimeMillis());
+ lastError.set(e.toString());
+ throw e;
+ }
+ }
+}
diff --git a/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheEntryStats.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheEntryStats.java
new file mode 100644
index 00000000000000..41c8b89192cd1b
--- /dev/null
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/MetaCacheEntryStats.java
@@ -0,0 +1,201 @@
+// 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.cache;
+
+import java.util.Objects;
+
+/**
+ * Immutable stats snapshot of one {@link MetaCacheEntry}.
+ *
+ * Connector-side copy of fe-core {@code org.apache.doris.datasource.metacache.MetaCacheEntryStats}
+ * (independent-copy migration): the connector plugins cannot import fe-core, so the meta-cache framework is
+ * duplicated under {@code org.apache.doris.connector.cache}. Keep behaviourally in sync with the fe-core
+ * original until the fe-core copy is retired.
+ *
+ *
Time fields use the following units:
+ *
+ * - {@code totalLoadTimeNanos}/{@code averageLoadPenaltyNanos}: nanoseconds
+ * - {@code lastLoadSuccessTimeMs}/{@code lastLoadFailureTimeMs}: epoch milliseconds
+ *
+ *
+ * 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.
+ */
+public final class MetaCacheEntryStats {
+ private final boolean configEnabled;
+ private final boolean effectiveEnabled;
+ private final boolean autoRefresh;
+ private final long ttlSecond;
+ private final long capacity;
+ private final long estimatedSize;
+ private final long requestCount;
+ private final long hitCount;
+ private final long missCount;
+ private final double hitRate;
+ private final long loadSuccessCount;
+ private final long loadFailureCount;
+ private final long totalLoadTimeNanos;
+ private final double averageLoadPenaltyNanos;
+ private final long evictionCount;
+ private final long invalidateCount;
+ private final long lastLoadSuccessTimeMs;
+ private final long lastLoadFailureTimeMs;
+ private final String lastError;
+
+ /**
+ * Build an immutable stats snapshot.
+ */
+ public MetaCacheEntryStats(
+ boolean configEnabled,
+ boolean effectiveEnabled,
+ boolean autoRefresh,
+ long ttlSecond,
+ long capacity,
+ long estimatedSize,
+ long requestCount,
+ long hitCount,
+ long missCount,
+ double hitRate,
+ long loadSuccessCount,
+ long loadFailureCount,
+ long totalLoadTimeNanos,
+ double averageLoadPenaltyNanos,
+ long evictionCount,
+ long invalidateCount,
+ long lastLoadSuccessTimeMs,
+ long lastLoadFailureTimeMs,
+ String lastError) {
+ this.configEnabled = configEnabled;
+ this.effectiveEnabled = effectiveEnabled;
+ this.autoRefresh = autoRefresh;
+ this.ttlSecond = ttlSecond;
+ this.capacity = capacity;
+ this.estimatedSize = estimatedSize;
+ this.requestCount = requestCount;
+ this.hitCount = hitCount;
+ this.missCount = missCount;
+ this.hitRate = hitRate;
+ this.loadSuccessCount = loadSuccessCount;
+ this.loadFailureCount = loadFailureCount;
+ this.totalLoadTimeNanos = totalLoadTimeNanos;
+ this.averageLoadPenaltyNanos = averageLoadPenaltyNanos;
+ this.evictionCount = evictionCount;
+ this.invalidateCount = invalidateCount;
+ this.lastLoadSuccessTimeMs = lastLoadSuccessTimeMs;
+ this.lastLoadFailureTimeMs = lastLoadFailureTimeMs;
+ this.lastError = Objects.requireNonNull(lastError, "lastError");
+ }
+
+ public boolean isConfigEnabled() {
+ return configEnabled;
+ }
+
+ /**
+ * Effective cache enable state evaluated by {@link CacheSpec#isCacheEnabled(boolean, long, long)}.
+ */
+ public boolean isEffectiveEnabled() {
+ return effectiveEnabled;
+ }
+
+ public boolean isAutoRefresh() {
+ return autoRefresh;
+ }
+
+ public long getTtlSecond() {
+ return ttlSecond;
+ }
+
+ public long getCapacity() {
+ return capacity;
+ }
+
+ public long getEstimatedSize() {
+ return estimatedSize;
+ }
+
+ public long getRequestCount() {
+ return requestCount;
+ }
+
+ public long getHitCount() {
+ return hitCount;
+ }
+
+ public long getMissCount() {
+ return missCount;
+ }
+
+ public double getHitRate() {
+ return hitRate;
+ }
+
+ public long getLoadSuccessCount() {
+ return loadSuccessCount;
+ }
+
+ public long getLoadFailureCount() {
+ return loadFailureCount;
+ }
+
+ public long getTotalLoadTimeNanos() {
+ return totalLoadTimeNanos;
+ }
+
+ /**
+ * Average load penalty in nanoseconds.
+ */
+ public double getAverageLoadPenaltyNanos() {
+ return averageLoadPenaltyNanos;
+ }
+
+ public long getEvictionCount() {
+ return evictionCount;
+ }
+
+ public double getEvictionRate() {
+ if (requestCount == 0) {
+ return 0D;
+ }
+ return (double) evictionCount / requestCount;
+ }
+
+ public long getInvalidateCount() {
+ return invalidateCount;
+ }
+
+ /**
+ * Last successful load timestamp in epoch milliseconds, or {@code -1} if absent.
+ */
+ public long getLastLoadSuccessTimeMs() {
+ return lastLoadSuccessTimeMs;
+ }
+
+ /**
+ * Last failed load timestamp in epoch milliseconds, or {@code -1} if absent.
+ */
+ public long getLastLoadFailureTimeMs() {
+ return lastLoadFailureTimeMs;
+ }
+
+ /**
+ * Latest load failure message, or empty string if no failure is recorded.
+ */
+ public String getLastError() {
+ return lastError;
+ }
+}
diff --git a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/package-info.java b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/package-info.java
similarity index 64%
rename from fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/package-info.java
rename to fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/package-info.java
index 545b052360f901..492da2e2ff24f1 100644
--- a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/package-info.java
+++ b/fe/fe-connector/fe-connector-cache/src/main/java/org/apache/doris/connector/cache/package-info.java
@@ -16,10 +16,11 @@
// under the License.
/**
- * Shared external metadata cache runtime, reused by fe-core and connector plugins.
+ * Shared external meta-cache framework, reused by fe-core and the connector plugins.
*
- *
It is under the parent-first {@code org.apache.doris.connector.*} prefix, so all instances load on the app
- * classloader with a single {@code Class} identity across the fe-core ↔ plugin boundary. The module
- * contains no FE lifecycle orchestration, catalog routing, or data-source SDK code.
+ *
Under the parent-first {@code org.apache.doris.connector.*} prefix, so all instances load on the app
+ * classloader with a single {@code Class} identity across the fe-core ↔ plugin boundary. Classes are
+ * moved here from fe-core {@code org.apache.doris.datasource.metacache} + {@code org.apache.doris.common}
+ * (see plan-doc/tasks/designs/metacache-framework-unification-design.md, Option A / P1).
*/
-package org.apache.doris.connector.metacache;
+package org.apache.doris.connector.cache;
diff --git a/fe/fe-connector/fe-connector-metacache-spi/src/test/java/org/apache/doris/connector/metacache/spi/CacheSpecTest.java b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/CacheSpecTest.java
similarity index 96%
rename from fe/fe-connector/fe-connector-metacache-spi/src/test/java/org/apache/doris/connector/metacache/spi/CacheSpecTest.java
rename to fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/CacheSpecTest.java
index 4c59b27576b078..276735b40c2f9b 100644
--- a/fe/fe-connector/fe-connector-metacache-spi/src/test/java/org/apache/doris/connector/metacache/spi/CacheSpecTest.java
+++ b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/CacheSpecTest.java
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache.spi;
+package org.apache.doris.connector.cache;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -25,11 +25,13 @@
import java.util.OptionalLong;
/**
- * Pins the shared {@link CacheSpec} validators and parsing as the single source of truth.
+ * Pins the shared {@link CacheSpec} validators and parsing (the single source of truth after the
+ * three prior copies — fe-core {@code datasource.metacache.CacheSpec}, the {@code connector.api.cache}
+ * mirror, and the connectors' hand-rolled checks — were collapsed here).
*
*
WHY this matters: this class restores the legacy CREATE/ALTER CATALOG meta-cache property
* validation that was dropped at the SPI cutover. The validators MUST throw {@link IllegalArgumentException}
- * (not a FE-specific exception, which is unavailable here and would not be caught by
+ * (NOT a fe-core {@code DdlException}, which is unavailable here and would not be caught by
* {@code PluginDrivenExternalCatalog.checkProperties}) and MUST emit the exact legacy message substring
* {@code "is wrong"} so the user-facing error and the regression assertions (e.g.
* {@code test_iceberg_table_meta_cache} / {@code test_paimon_table_meta_cache}) still match.
diff --git a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/ConnectorMetadataCacheTest.java b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/ConnectorMetadataCacheTest.java
similarity index 99%
rename from fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/ConnectorMetadataCacheTest.java
rename to fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/ConnectorMetadataCacheTest.java
index dba80f5580e123..8a6c1596fccad4 100644
--- a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/ConnectorMetadataCacheTest.java
+++ b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/ConnectorMetadataCacheTest.java
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache;
+package org.apache.doris.connector.cache;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/ConnectorMetaCacheEntryCompatibilityTest.java b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheEntryTest.java
similarity index 88%
rename from fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/ConnectorMetaCacheEntryCompatibilityTest.java
rename to fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheEntryTest.java
index ea1c478b6f9496..8284f4ae1ed672 100644
--- a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/ConnectorMetaCacheEntryCompatibilityTest.java
+++ b/fe/fe-connector/fe-connector-cache/src/test/java/org/apache/doris/connector/cache/MetaCacheEntryTest.java
@@ -15,10 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package org.apache.doris.connector.metacache;
-
-import org.apache.doris.connector.metacache.spi.CacheSpec;
-import org.apache.doris.connector.metacache.spi.MetaCacheEntryStats;
+package org.apache.doris.connector.cache;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.junit.jupiter.api.Assertions;
@@ -33,35 +30,11 @@
import java.util.concurrent.atomic.AtomicInteger;
/**
- * Verifies the connector-facing construction modes of the shared {@link MetaCacheEntry}.
- * FE-owned configuration is passed through the constructor so this runtime remains independent of FE core.
+ * Behaviour parity tests for the connector-side {@link MetaCacheEntry} copy. Where fe-core's
+ * {@code MetaCacheEntryTest} toggles {@code Config.enable_external_meta_cache_manual_miss_load}, this copy
+ * passes the flag through the constructor instead (the connector copy has no fe-core Config).
*/
-public class ConnectorMetaCacheEntryCompatibilityTest {
-
- @Test
- public void fourArgumentConstructorPreservesConnectorDefaults() {
- ExecutorService refreshExecutor = Executors.newSingleThreadExecutor();
- try {
- AtomicInteger manualPublicationCount = new AtomicInteger();
- MetaCacheEntry entry = new MetaCacheEntry(
- "test",
- String::length,
- CacheSpec.of(true, CacheSpec.CACHE_NO_TTL, 10L),
- refreshExecutor) {
- @Override
- void beforeManualCachePutForTest(String key, Integer loaded) {
- manualPublicationCount.incrementAndGet();
- }
- };
-
- Assertions.assertEquals(Integer.valueOf(1), entry.get("k"));
- Assertions.assertAll(
- () -> Assertions.assertFalse(entry.stats().isAutoRefresh()),
- () -> Assertions.assertEquals(0, manualPublicationCount.get()));
- } finally {
- refreshExecutor.shutdownNow();
- }
- }
+public class MetaCacheEntryTest {
@Test
public void loaderGetTracksHitMissAndLastError() {
@@ -251,9 +224,7 @@ public void putIfNotInvalidatedSinceHonorsGenerationGuard() {
// No invalidation since the captured generation -> the guarded put caches normally.
long g1 = entry.invalidationGeneration();
entry.putIfNotInvalidatedSince(g1, "a", 1);
- entry.putIfNotInvalidatedSince(g1, "a2", 11);
Assertions.assertEquals(Integer.valueOf(1), entry.getIfPresent("a"));
- Assertions.assertEquals(Integer.valueOf(11), entry.getIfPresent("a2"));
// An invalidation between the capture and the put (the flush-races-an-in-flight-load case) must make
// the put a no-op, so a stale pre-invalidation value is NOT re-cached to the TTL.
diff --git a/fe/fe-connector/fe-connector-hive/pom.xml b/fe/fe-connector/fe-connector-hive/pom.xml
index 931bb0f9eb5845..29721c3a038425 100644
--- a/fe/fe-connector/fe-connector-hive/pom.xml
+++ b/fe/fe-connector/fe-connector-hive/pom.xml
@@ -47,22 +47,24 @@ under the License.
${project.version}
-
+
${project.groupId}
- fe-connector-metacache
+ fe-connector-cache
${project.version}
-
+ version fe-connector-cache is compiled against (the lowest common version across consuming plugins). -->
com.github.ben-manes.caffeine
caffeine
diff --git a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnector.java b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnector.java
index 9b1aefad95668b..e4a5dcee981246 100644
--- a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnector.java
+++ b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnector.java
@@ -29,12 +29,12 @@
import org.apache.doris.connector.api.procedure.ConnectorProcedureOps;
import org.apache.doris.connector.api.scan.ConnectorScanPlanProvider;
import org.apache.doris.connector.api.write.ConnectorWritePlanProvider;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
import org.apache.doris.connector.hms.CachingHmsClient;
import org.apache.doris.connector.hms.HmsClient;
import org.apache.doris.connector.hms.HmsClientConfig;
import org.apache.doris.connector.hms.ThriftHmsClient;
import org.apache.doris.connector.hms.event.HmsEventSource;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
import org.apache.doris.connector.metastore.HmsMetaStoreProperties;
import org.apache.doris.connector.metastore.spi.MetaStoreProviders;
import org.apache.doris.connector.spi.ConnectorContext;
@@ -100,7 +100,7 @@ public class HiveConnector implements Connector {
private final HiveFileListingCache fileListingCache;
// PERF-06 (S6): cross-query DERIVED partition-view cache ("cache A", the generic ConnectorMetadataCache
- // from fe-connector-metacache), layered ABOVE the raw per-name HMS listing served by CachingHmsClient: it
+ // from fe-connector-cache), layered ABOVE the raw per-name HMS listing served by CachingHmsClient: it
// memoizes the BUILT List (HiveConnectorMetadata#listPartitionsUncached's per-name
// HiveWriteUtils.toPartitionValues parse + ConnectorPartitionInfo construction), keyed by
// (db, table, -1, -1) — hive is snapshot-less (beginQuerySnapshot always pins -1) and its handle carries no
diff --git a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorMetadata.java b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorMetadata.java
index feb4cc047ebea9..603c2071e23355 100644
--- a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorMetadata.java
+++ b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorMetadata.java
@@ -56,6 +56,8 @@
import org.apache.doris.connector.api.pushdown.ConnectorLiteral;
import org.apache.doris.connector.api.pushdown.FilterApplicationResult;
import org.apache.doris.connector.api.scan.ConnectorPartitionValues;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorTableKey;
import org.apache.doris.connector.hms.HiveShowCreateTableRenderer;
import org.apache.doris.connector.hms.HmsClient;
import org.apache.doris.connector.hms.HmsClientException;
@@ -65,8 +67,6 @@
import org.apache.doris.connector.hms.HmsPartitionInfo;
import org.apache.doris.connector.hms.HmsTableInfo;
import org.apache.doris.connector.hms.HmsTypeMapping;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
-import org.apache.doris.connector.metacache.ConnectorTableKey;
import org.apache.doris.connector.spi.ConnectorConf;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.connector.spi.ConnectorStorageContext;
diff --git a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorProvider.java b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorProvider.java
index da15823f081f5b..dfe2174d4c2c54 100644
--- a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorProvider.java
+++ b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorProvider.java
@@ -18,8 +18,8 @@
package org.apache.doris.connector.hive;
import org.apache.doris.connector.api.Connector;
+import org.apache.doris.connector.cache.CacheSpec;
import org.apache.doris.connector.hms.HmsClientConfig;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.connector.spi.ConnectorProvider;
diff --git a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveFileListingCache.java b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveFileListingCache.java
index 1842a0a640438c..a62967c33760f2 100644
--- a/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveFileListingCache.java
+++ b/fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveFileListingCache.java
@@ -18,8 +18,8 @@
package org.apache.doris.connector.hive;
import org.apache.doris.connector.api.DorisConnectorException;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.doris.filesystem.FileEntry;
import org.apache.doris.filesystem.FileIterator;
import org.apache.doris.filesystem.FileSystem;
diff --git a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorMetadataPartitionViewCacheTest.java b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorMetadataPartitionViewCacheTest.java
index 094640a0e489b4..e18487005642f4 100644
--- a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorMetadataPartitionViewCacheTest.java
+++ b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorMetadataPartitionViewCacheTest.java
@@ -23,11 +23,11 @@
import org.apache.doris.connector.api.pushdown.ConnectorComparison;
import org.apache.doris.connector.api.pushdown.ConnectorExpression;
import org.apache.doris.connector.api.pushdown.ConnectorLiteral;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
import org.apache.doris.connector.hms.HmsClient;
import org.apache.doris.connector.hms.HmsDatabaseInfo;
import org.apache.doris.connector.hms.HmsPartitionInfo;
import org.apache.doris.connector.hms.HmsTableInfo;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorPartitionViewCacheTest.java b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorPartitionViewCacheTest.java
index 90022de73991e9..a13c072f0c26e2 100644
--- a/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorPartitionViewCacheTest.java
+++ b/fe/fe-connector/fe-connector-hive/src/test/java/org/apache/doris/connector/hive/HiveConnectorPartitionViewCacheTest.java
@@ -18,8 +18,8 @@
package org.apache.doris.connector.hive;
import org.apache.doris.connector.api.ConnectorPartitionInfo;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
-import org.apache.doris.connector.metacache.ConnectorTableKey;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorTableKey;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/fe/fe-connector/fe-connector-hms/pom.xml b/fe/fe-connector/fe-connector-hms/pom.xml
index 76ce661a10e949..b0fde2ef732c68 100644
--- a/fe/fe-connector/fe-connector-hms/pom.xml
+++ b/fe/fe-connector/fe-connector-hms/pom.xml
@@ -55,7 +55,7 @@ under the License.
dep is what puts CacheSpec/MetaCacheEntry on -hms's own compile classpath. -->
${project.groupId}
- fe-connector-metacache
+ fe-connector-cache
${project.version}
@@ -141,10 +141,10 @@ under the License.
com.github.ben-manes.caffeine
diff --git a/fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java b/fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java
index f8752a8ca603b0..19d46822dc65ec 100644
--- a/fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java
+++ b/fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.hms;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.hadoop.hive.common.FileUtils;
diff --git a/fe/fe-connector/fe-connector-iceberg/pom.xml b/fe/fe-connector/fe-connector-iceberg/pom.xml
index 040bf88dfb283b..d05edc1e8e7c10 100644
--- a/fe/fe-connector/fe-connector-iceberg/pom.xml
+++ b/fe/fe-connector/fe-connector-iceberg/pom.xml
@@ -47,15 +47,16 @@ under the License.
${project.version}
-
${project.groupId}
- fe-connector-metacache
+ fe-connector-cache
${project.version}
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCatalogFactory.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCatalogFactory.java
index 06a460e14990a9..d270b9b6897fab 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCatalogFactory.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCatalogFactory.java
@@ -18,7 +18,7 @@
package org.apache.doris.connector.iceberg;
import org.apache.doris.connector.api.DorisConnectorException;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
import org.apache.doris.filesystem.properties.S3CompatibleFileSystemProperties;
import org.apache.doris.filesystem.properties.StorageProperties;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCommentCache.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCommentCache.java
index 4d222007733255..6caf556f5780f6 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCommentCache.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergCommentCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.iceberg;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnector.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnector.java
index ff0d0965ca3143..738cf7bd1bf7bb 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnector.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnector.java
@@ -30,7 +30,7 @@
import org.apache.doris.connector.api.procedure.ConnectorProcedureOps;
import org.apache.doris.connector.api.scan.ConnectorScanPlanProvider;
import org.apache.doris.connector.api.write.ConnectorWritePlanProvider;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
import org.apache.doris.connector.metastore.HmsMetaStoreProperties;
import org.apache.doris.connector.metastore.spi.JdbcDriverSupport;
import org.apache.doris.connector.metastore.spi.MetaStoreProviders;
@@ -191,7 +191,7 @@ public class IcebergConnector implements Connector {
// authorization a shared cache would bypass. null for every other flavor.
private final IcebergCommentCache commentCache; // null under session=user
// PERF-06: cross-query DERIVED partition-view cache ("cache A", the generic ConnectorMetadataCache from
- // fe-connector-metacache), layered ABOVE the raw partitionCache (PERF-02): it memoizes the BUILT derived view
+ // fe-connector-cache), layered ABOVE the raw partitionCache (PERF-02): it memoizes the BUILT derived view
// (transform-to-range math + overlap merge for the MTMV view; the value-map construction for listPartitions)
// keyed by (db, table, snapshotId, schemaId), so a repeated query on a partitioned table skips the derived
// rebuild, not just the remote scan. Two typed fields because the two SPI hooks return structurally different
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadata.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadata.java
index 87f845540b6dcc..62e98dc2a0a048 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadata.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadata.java
@@ -43,8 +43,8 @@
import org.apache.doris.connector.api.mvcc.ConnectorMvccSnapshot;
import org.apache.doris.connector.api.mvcc.ConnectorTimeTravelSpec;
import org.apache.doris.connector.api.pushdown.ConnectorExpression;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
-import org.apache.doris.connector.metacache.ConnectorTableKey;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorTableKey;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.connector.spi.ConnectorStorageContext;
import org.apache.doris.thrift.THiveTable;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorProvider.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorProvider.java
index d39a39aeb41bbe..2f9e90b5a4d121 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorProvider.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorProvider.java
@@ -18,7 +18,7 @@
package org.apache.doris.connector.iceberg;
import org.apache.doris.connector.api.Connector;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
import org.apache.doris.connector.metastore.spi.MetaStoreProviders;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.connector.spi.ConnectorProvider;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergFormatCache.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergFormatCache.java
index 0584320484a934..cb07b3e57da124 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergFormatCache.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergFormatCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.iceberg;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCache.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCache.java
index 719d98b1944bf5..35b63c6aa8c8fc 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCache.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.iceberg;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergManifestCache.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergManifestCache.java
index 64312f459dc3b2..f3c46732fb2fc1 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergManifestCache.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergManifestCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.iceberg;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.iceberg.DataFile;
import org.apache.iceberg.DeleteFile;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergPartitionCache.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergPartitionCache.java
index ac5352629cb8f4..a85e2187043e7c 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergPartitionCache.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergPartitionCache.java
@@ -17,9 +17,9 @@
package org.apache.doris.connector.iceberg;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.doris.connector.iceberg.IcebergPartitionUtils.IcebergRawPartition;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
index 0b2851904bd85b..42db23c93ac1c4 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
@@ -29,7 +29,7 @@
import org.apache.doris.connector.api.scan.ConnectorScanRequest;
import org.apache.doris.connector.api.scan.ConnectorSplitSource;
import org.apache.doris.connector.api.scan.ScanNodePropertyKeys;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.connector.spi.ConnectorStorageContext;
import org.apache.doris.filesystem.properties.StorageProperties;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergTableCache.java b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergTableCache.java
index f1ce39bbcf71ae..426b706cf11f90 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergTableCache.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergTableCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.iceberg;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.iceberg.Table;
import org.apache.iceberg.catalog.Namespace;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorCacheTest.java b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorCacheTest.java
index 8b3d23134c7fb0..6abe09a0f52dfd 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorCacheTest.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorCacheTest.java
@@ -18,8 +18,8 @@
package org.apache.doris.connector.iceberg;
import org.apache.doris.connector.api.ConnectorPartitionInfo;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
-import org.apache.doris.connector.metacache.ConnectorTableKey;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorTableKey;
import org.apache.iceberg.DataFiles;
import org.apache.iceberg.ManifestFile;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadataPartitionViewCacheTest.java b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadataPartitionViewCacheTest.java
index 0df52cb7661c97..e5cb30b0ed97d8 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadataPartitionViewCacheTest.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergConnectorMetadataPartitionViewCacheTest.java
@@ -21,7 +21,7 @@
import org.apache.doris.connector.api.mvcc.ConnectorMvccPartition;
import org.apache.doris.connector.api.mvcc.ConnectorMvccPartitionView;
import org.apache.doris.connector.api.pushdown.ConnectorExpression;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
import org.apache.iceberg.DataFiles;
import org.apache.iceberg.FileFormat;
diff --git a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCacheTest.java b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCacheTest.java
index 8a8a5080abff25..c592472ffffb08 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCacheTest.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergLatestSnapshotCacheTest.java
@@ -25,7 +25,7 @@
/**
* Unit tests for {@link IcebergLatestSnapshotCache} (mirrors PaimonLatestSnapshotCacheTest). The cache is now
- * backed by the shared {@link org.apache.doris.connector.metacache.MetaCacheEntry} framework; these tests cover the
+ * backed by the shared {@link org.apache.doris.connector.cache.MetaCacheEntry} framework; these tests cover the
* adapter's contract — within-TTL stability, the {@code ttl <= 0} disable, and invalidation. Timed-expiry
* mechanics are the framework's responsibility (the ttl→duration mapping is unit-tested in the framework
* module's {@code CacheSpecTest}; Caffeine {@code expireAfterAccess} itself is the library's behavior), so they
diff --git a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergTableCacheTest.java b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergTableCacheTest.java
index 470127cdd623e5..9dd551c35fb483 100644
--- a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergTableCacheTest.java
+++ b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergTableCacheTest.java
@@ -31,7 +31,7 @@
/**
* Unit tests for {@link IcebergTableCache} (PERF-01). The cross-query RAW-table cache mirrors
- * {@link IcebergLatestSnapshotCache} exactly (same {@link org.apache.doris.connector.metacache.MetaCacheEntry}
+ * {@link IcebergLatestSnapshotCache} exactly (same {@link org.apache.doris.connector.cache.MetaCacheEntry}
* backing) but stores the whole {@link Table} instead of the {@code (snapshotId, schemaId)} pin, restoring the
* table-caching half of the legacy {@code IcebergExternalMetaCache}. These tests cover the adapter's contract —
* within-TTL stability, the {@code ttl <= 0} disable, invalidation, and the exception-propagation guarantee the
diff --git a/fe/fe-connector/fe-connector-maxcompute/pom.xml b/fe/fe-connector/fe-connector-maxcompute/pom.xml
index 752cab8a9f6bd9..37565fbc8a1140 100644
--- a/fe/fe-connector/fe-connector-maxcompute/pom.xml
+++ b/fe/fe-connector/fe-connector-maxcompute/pom.xml
@@ -45,22 +45,24 @@ under the License.
${project.version}
-
+
${project.groupId}
- fe-connector-metacache
+ fe-connector-cache
${project.version}
-
com.github.ben-manes.caffeine
diff --git a/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCache.java b/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCache.java
index 953bef93f47898..2cc4afda30d5e7 100644
--- a/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCache.java
+++ b/fe/fe-connector/fe-connector-maxcompute/src/main/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.maxcompute;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import com.aliyun.odps.Partition;
@@ -31,7 +31,7 @@
/**
* The MaxCompute connector's own partition-listing cache — a structural copy of the hive connector's
* {@code HiveFileListingCache}, backed by the shared
- * {@code fe-connector-metacache} framework ({@link CacheSpec} + {@link MetaCacheEntry}). It memoizes the (expensive)
+ * {@code fe-connector-cache} framework ({@link CacheSpec} + {@link MetaCacheEntry}). It memoizes the (expensive)
* per-table ODPS partition listing ({@code structureHelper.getPartitions}), keyed by {@code (db, table)} — the
* ODPS project is constant per catalog, so it is NOT part of the key.
*
diff --git a/fe/fe-connector/fe-connector-maxcompute/src/test/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCacheTest.java b/fe/fe-connector/fe-connector-maxcompute/src/test/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCacheTest.java
index 4c79aa7cf7c0a9..b957bb7a28579a 100644
--- a/fe/fe-connector/fe-connector-maxcompute/src/test/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCacheTest.java
+++ b/fe/fe-connector/fe-connector-maxcompute/src/test/java/org/apache/doris/connector/maxcompute/MaxComputePartitionCacheTest.java
@@ -39,7 +39,7 @@
/**
* Tests {@link MaxComputePartitionCache}: the connector-owned partition-listing cache (a structural copy of the
- * hive connector's {@code HiveFileListingCache}), backed by the shared {@code fe-connector-metacache} framework.
+ * hive connector's {@code HiveFileListingCache}), backed by the shared {@code fe-connector-cache} framework.
*
* WHY (Rule 9): after the max_compute cutover the fe-core engine-side external meta cache stops routing to a
* MaxCompute catalog, so without this connector-owned cache every {@code SHOW PARTITIONS} / partition-pruning /
diff --git a/fe/fe-connector/fe-connector-metacache-spi/pom.xml b/fe/fe-connector/fe-connector-metacache-spi/pom.xml
deleted file mode 100644
index a62451df8a715a..00000000000000
--- a/fe/fe-connector/fe-connector-metacache-spi/pom.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
- 4.0.0
-
-
- org.apache.doris
- fe-connector
- ${revision}
- ../pom.xml
-
-
- fe-connector-metacache-spi
- jar
- Doris FE Connector MetaCache SPI
-
- Stable, implementation-free contracts for external metadata cache providers.
- Contains only JDK-based configuration, entry definitions, invalidation contracts
- and statistics snapshots. It never depends on fe-core or a data source SDK.
-
-
-
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
-
-
- doris-fe-connector-metacache-spi
-
-
diff --git a/fe/fe-connector/fe-connector-metacache-spi/src/main/java/org/apache/doris/connector/metacache/spi/MetaCacheLifecycle.java b/fe/fe-connector/fe-connector-metacache-spi/src/main/java/org/apache/doris/connector/metacache/spi/MetaCacheLifecycle.java
deleted file mode 100644
index 32f79172ff1921..00000000000000
--- a/fe/fe-connector/fe-connector-metacache-spi/src/main/java/org/apache/doris/connector/metacache/spi/MetaCacheLifecycle.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// 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.metacache.spi;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Data-source-neutral lifecycle contract for one engine's metadata cache.
- *
- *
The contract deliberately excludes cache implementation types and FE state.
- * Entry lookup belongs to the runtime or an engine-specific adapter.
- */
-public interface MetaCacheLifecycle extends AutoCloseable {
- String engine();
-
- default Collection aliases() {
- return Collections.singleton(engine());
- }
-
- void initCatalog(long catalogId, Map catalogProperties);
-
- void checkCatalogInitialized(long catalogId);
-
- boolean isCatalogInitialized(long catalogId);
-
- void invalidateCatalog(long catalogId);
-
- default void invalidateCatalogEntries(long catalogId) {
- invalidateCatalog(catalogId);
- }
-
- void invalidateDb(long catalogId, String dbName);
-
- void invalidateTable(long catalogId, String dbName, String tableName);
-
- void invalidatePartitions(long catalogId, String dbName, String tableName, List partitions);
-
- Map stats(long catalogId);
-
- @Override
- void close();
-}
diff --git a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/AbstractMetaCache.java b/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/AbstractMetaCache.java
deleted file mode 100644
index eb32aacc211356..00000000000000
--- a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/AbstractMetaCache.java
+++ /dev/null
@@ -1,280 +0,0 @@
-// 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.metacache;
-
-import org.apache.doris.connector.metacache.spi.CacheSpec;
-import org.apache.doris.connector.metacache.spi.MetaCacheEntryDef;
-import org.apache.doris.connector.metacache.spi.MetaCacheEntryStats;
-import org.apache.doris.connector.metacache.spi.MetaCacheLifecycle;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-/**
- * Data-source-neutral metadata cache lifecycle.
- *
- * The runtime owns the engine -> catalog -> entry layout, typed entry lookup,
- * invalidation and statistics. FE catalog lookup, schema validation, edit-log handling
- * and REFRESH orchestration belong in fe-core adapters.
- */
-public abstract class AbstractMetaCache implements MetaCacheLifecycle {
- private final String engine;
- private final ExecutorService refreshExecutor;
- private final long refreshAfterWriteSeconds;
- private final int objectStripeCount;
- private final Map catalogEntries = new ConcurrentHashMap<>();
- private final Map> entryDefs = new ConcurrentHashMap<>();
-
- protected AbstractMetaCache(String engine, ExecutorService refreshExecutor,
- long refreshAfterWriteSeconds, int objectStripeCount) {
- this.engine = Objects.requireNonNull(engine, "engine can not be null");
- this.refreshExecutor = Objects.requireNonNull(refreshExecutor, "refreshExecutor can not be null");
- if (refreshAfterWriteSeconds <= 0) {
- throw new IllegalArgumentException("refreshAfterWriteSeconds must be positive");
- }
- if (objectStripeCount <= 0) {
- throw new IllegalArgumentException("objectStripeCount must be positive");
- }
- this.refreshAfterWriteSeconds = refreshAfterWriteSeconds;
- this.objectStripeCount = objectStripeCount;
- }
-
- public final String engine() {
- return engine;
- }
-
- public Collection aliases() {
- return Collections.singleton(engine);
- }
-
- public final void initCatalog(long catalogId, Map catalogProperties) {
- Map safeCatalogProperties = CacheSpec.applyCompatibilityMap(
- catalogProperties, catalogPropertyCompatibilityMap());
- catalogEntries.computeIfAbsent(catalogId, id -> buildCatalogEntryGroup(safeCatalogProperties));
- }
-
- public final void checkCatalogInitialized(long catalogId) {
- requireCatalogEntryGroup(catalogId);
- }
-
- public final boolean isCatalogInitialized(long catalogId) {
- return catalogEntries.containsKey(catalogId);
- }
-
- /**
- * Optional compatibility mapping in the form {@code legacyKey -> newKey}.
- */
- protected Map catalogPropertyCompatibilityMap() {
- return Collections.emptyMap();
- }
-
- @SuppressWarnings("unchecked")
- public final MetaCacheEntry entry(
- long catalogId, String entryName, Class keyType, Class valueType) {
- CatalogEntryGroup group = requireCatalogEntryGroup(catalogId);
- MetaCacheEntryDef, ?> def = requireEntryDef(entryName);
- ensureTypeCompatible(def, keyType, valueType);
-
- MetaCacheEntry, ?> cacheEntry = group.get(entryName);
- if (cacheEntry == null) {
- throw new IllegalStateException(String.format(
- "Entry '%s' is not initialized for engine '%s', catalog %d.",
- entryName, engine, catalogId));
- }
- return (MetaCacheEntry) cacheEntry;
- }
-
- public final void invalidateCatalog(long catalogId) {
- CatalogEntryGroup removed = catalogEntries.remove(catalogId);
- if (removed != null) {
- removed.invalidateAll();
- }
- }
-
- public final void invalidateCatalogEntries(long catalogId) {
- CatalogEntryGroup group = catalogEntries.get(catalogId);
- if (group != null) {
- group.invalidateAll();
- }
- }
-
- public final void invalidateDb(long catalogId, String dbName) {
- invalidateEntries(catalogId, entryDef -> entryDef.getInvalidation().dbPredicate(dbName));
- }
-
- public final void invalidateTable(long catalogId, String dbName, String tableName) {
- invalidateEntries(catalogId, entryDef -> entryDef.getInvalidation().tablePredicate(dbName, tableName));
- }
-
- public final void invalidatePartitions(
- long catalogId, String dbName, String tableName, List partitions) {
- invalidateEntries(catalogId,
- entryDef -> entryDef.getInvalidation().partitionPredicate(dbName, tableName, partitions));
- }
-
- public final Map stats(long catalogId) {
- CatalogEntryGroup group = catalogEntries.get(catalogId);
- return group == null ? Collections.emptyMap() : group.stats();
- }
-
- public void close() {
- catalogEntries.values().forEach(CatalogEntryGroup::invalidateAll);
- catalogEntries.clear();
- }
-
- protected final void registerEntryDef(MetaCacheEntryDef entryDef) {
- Objects.requireNonNull(entryDef, "entryDef");
- if (!catalogEntries.isEmpty()) {
- throw new IllegalStateException(
- String.format("Can not register entry '%s' after catalog initialization for engine '%s'.",
- entryDef.getName(), engine));
- }
- MetaCacheEntryDef, ?> existing = entryDefs.putIfAbsent(entryDef.getName(), entryDef);
- if (existing != null) {
- throw new IllegalArgumentException(
- String.format("Duplicated entry definition '%s' for engine '%s'.",
- entryDef.getName(), engine));
- }
- }
-
- protected final EntryHandle registerEntry(MetaCacheEntryDef entryDef) {
- registerEntryDef(entryDef);
- return new EntryHandle<>(entryDef);
- }
-
- protected final MetaCacheEntry entry(long catalogId, MetaCacheEntryDef entryDef) {
- validateRegisteredEntryDef(entryDef);
- return entry(catalogId, entryDef.getName(), entryDef.getKeyType(), entryDef.getValueType());
- }
-
- protected final String metaCacheTtlKey(String entryName) {
- return CacheSpec.metaCacheTtlKey(engine, entryName);
- }
-
- protected final Map singleCompatibilityMap(String legacyKey, String entryName) {
- return Collections.singletonMap(legacyKey, metaCacheTtlKey(entryName));
- }
-
- /**
- * Adapter hook for value validation or other local decoration before a loader is installed.
- */
- protected Function decorateLoader(Function loader, Class valueType) {
- return loader;
- }
-
- private CatalogEntryGroup requireCatalogEntryGroup(long catalogId) {
- CatalogEntryGroup group = catalogEntries.get(catalogId);
- if (group == null) {
- throw new IllegalStateException(String.format(
- "Catalog %d is not initialized for engine '%s'.", catalogId, engine));
- }
- return group;
- }
-
- private MetaCacheEntryDef, ?> requireEntryDef(String entryName) {
- MetaCacheEntryDef, ?> entryDef = entryDefs.get(entryName);
- if (entryDef == null) {
- throw new IllegalArgumentException(String.format(
- "Entry '%s' is not registered for engine '%s'.", entryName, engine));
- }
- return entryDef;
- }
-
- private void ensureTypeCompatible(MetaCacheEntryDef, ?> entryDef, Class> keyType, Class> valueType) {
- if (!entryDef.getKeyType().equals(keyType) || !entryDef.getValueType().equals(valueType)) {
- throw new IllegalArgumentException(String.format(
- "Entry '%s' for engine '%s' expects key/value types (%s, %s), but got (%s, %s).",
- entryDef.getName(), engine, entryDef.getKeyType().getName(), entryDef.getValueType().getName(),
- keyType.getName(), valueType.getName()));
- }
- }
-
- private void validateRegisteredEntryDef(MetaCacheEntryDef entryDef) {
- MetaCacheEntryDef, ?> registered = requireEntryDef(entryDef.getName());
- ensureTypeCompatible(registered, entryDef.getKeyType(), entryDef.getValueType());
- }
-
- private void invalidateEntries(long catalogId, Function, Predicate>> predicateFactory) {
- CatalogEntryGroup group = catalogEntries.get(catalogId);
- if (group == null) {
- return;
- }
- entryDefs.values().forEach(entryDef -> invalidateEntryIfMatched(group, entryDef, predicateFactory));
- }
-
- @SuppressWarnings("unchecked")
- private void invalidateEntryIfMatched(CatalogEntryGroup group, MetaCacheEntryDef entryDef,
- Function, Predicate>> predicateFactory) {
- Predicate predicate = (Predicate) predicateFactory.apply(entryDef);
- if (predicate == null) {
- return;
- }
- MetaCacheEntry entry = (MetaCacheEntry) group.get(entryDef.getName());
- if (entry != null) {
- entry.invalidateIf(predicate);
- }
- }
-
- private CatalogEntryGroup buildCatalogEntryGroup(Map catalogProperties) {
- CatalogEntryGroup group = new CatalogEntryGroup();
- entryDefs.values()
- .forEach(entryDef -> group.put(entryDef.getName(), newMetaCacheEntry(entryDef, catalogProperties)));
- return group;
- }
-
- @SuppressWarnings("unchecked")
- private MetaCacheEntry newMetaCacheEntry(
- MetaCacheEntryDef, ?> rawEntryDef, Map catalogProperties) {
- MetaCacheEntryDef entryDef = (MetaCacheEntryDef) rawEntryDef;
- CacheSpec cacheSpec = CacheSpec.fromProperties(
- catalogProperties, engine, entryDef.getName(), entryDef.getDefaultCacheSpec());
- return new MetaCacheEntry<>(
- entryDef.getName(),
- decorateLoader(entryDef.getLoader(), entryDef.getValueType()),
- cacheSpec,
- refreshExecutor,
- entryDef.isAutoRefresh(),
- entryDef.isContextualOnly(),
- objectStripeCount,
- refreshAfterWriteSeconds,
- true);
- }
-
- protected final class EntryHandle {
- private final MetaCacheEntryDef entryDef;
-
- private EntryHandle(MetaCacheEntryDef entryDef) {
- this.entryDef = entryDef;
- }
-
- public MetaCacheEntry get(long catalogId) {
- return entry(catalogId, entryDef);
- }
-
- public MetaCacheEntry getIfInitialized(long catalogId) {
- return isCatalogInitialized(catalogId) ? get(catalogId) : null;
- }
- }
-}
diff --git a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/MetaCacheRegistry.java b/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/MetaCacheRegistry.java
deleted file mode 100644
index b19c4c008146b7..00000000000000
--- a/fe/fe-connector/fe-connector-metacache/src/main/java/org/apache/doris/connector/metacache/MetaCacheRegistry.java
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.metacache;
-
-import org.apache.doris.connector.metacache.spi.MetaCacheLifecycle;
-
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Data-source-neutral registry for metadata cache engines and their aliases.
- */
-public class MetaCacheRegistry {
- private static final String ENGINE_DEFAULT = "default";
-
- private final Map engineCaches = new ConcurrentHashMap<>();
- private final Map engineAliasIndex = new ConcurrentHashMap<>();
-
- public final T resolve(String engine) {
- Objects.requireNonNull(engine, "engine is null");
- String normalizedEngine = normalizeEngineName(engine);
- String primaryEngine = engineAliasIndex.getOrDefault(normalizedEngine, normalizedEngine);
- T found = engineCaches.get(primaryEngine);
- if (found != null) {
- return found;
- }
- throw new IllegalArgumentException(
- String.format("unsupported external meta cache engine '%s'", normalizedEngine));
- }
-
- public final Collection allCaches() {
- return engineCaches.values();
- }
-
- public final void register(T cache) {
- Objects.requireNonNull(cache, "cache is null");
- String engineName = normalizeEngineName(cache.engine());
- T existing = engineCaches.putIfAbsent(engineName, cache);
- if (existing != null) {
- onDuplicatedEngine(engineName, existing, cache);
- return;
- }
- registerAlias(engineName, engineName);
- for (String alias : cache.aliases()) {
- registerAlias(alias, engineName);
- }
- onRegistered(engineName, cache);
- }
-
- public final void resetForTest(Collection extends T> caches) {
- engineCaches.clear();
- engineAliasIndex.clear();
- caches.forEach(this::register);
- }
-
- protected void onRegistered(String engineName, T cache) {
- }
-
- protected void onDuplicatedEngine(String engineName, T existing, T duplicate) {
- }
-
- protected void onDuplicatedAlias(String alias, String existingEngine, String duplicateEngine) {
- }
-
- static String normalizeEngineName(String engine) {
- if (engine == null) {
- return ENGINE_DEFAULT;
- }
- String normalized = engine.trim().toLowerCase(Locale.ROOT);
- return normalized.isEmpty() ? ENGINE_DEFAULT : normalized;
- }
-
- private void registerAlias(String alias, String primaryEngineName) {
- String normalizedAlias = normalizeEngineName(alias);
- String existing = engineAliasIndex.putIfAbsent(normalizedAlias, primaryEngineName);
- if (existing != null && !existing.equals(primaryEngineName)) {
- onDuplicatedAlias(normalizedAlias, existing, primaryEngineName);
- }
- }
-}
diff --git a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/AbstractMetaCacheTest.java b/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/AbstractMetaCacheTest.java
deleted file mode 100644
index 0801900f6744d0..00000000000000
--- a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/AbstractMetaCacheTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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.metacache;
-
-import org.apache.doris.connector.metacache.spi.CacheSpec;
-import org.apache.doris.connector.metacache.spi.MetaCacheEntryDef;
-import org.apache.doris.connector.metacache.spi.MetaCacheEntryInvalidation;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import java.util.Collections;
-import java.util.Objects;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class AbstractMetaCacheTest {
- @Test
- public void catalogLifecycleAndScopedInvalidationStayInRuntime() {
- ExecutorService refreshExecutor = Executors.newSingleThreadExecutor();
- try {
- TestMetaCache cache = new TestMetaCache("test", refreshExecutor);
- Assertions.assertThrows(IllegalStateException.class, () -> cache.tableEntry(1L));
-
- cache.initCatalog(1L, Collections.emptyMap());
- MetaCacheEntry entry = cache.tableEntry(1L);
- TableKey matched = new TableKey("db1", "table1");
- TableKey unmatched = new TableKey("db2", "table2");
- entry.put(matched, "matched");
- entry.put(unmatched, "unmatched");
-
- cache.invalidateTable(1L, "db1", "table1");
-
- Assertions.assertNull(entry.getIfPresent(matched));
- Assertions.assertEquals("unmatched", entry.getIfPresent(unmatched));
- Assertions.assertTrue(cache.isCatalogInitialized(1L));
- Assertions.assertTrue(cache.stats(1L).containsKey("table"));
-
- cache.invalidateCatalogEntries(1L);
- Assertions.assertTrue(cache.isCatalogInitialized(1L));
- Assertions.assertNull(entry.getIfPresent(unmatched));
-
- cache.invalidateCatalog(1L);
- Assertions.assertFalse(cache.isCatalogInitialized(1L));
- Assertions.assertThrows(IllegalStateException.class, () -> cache.tableEntry(1L));
- } finally {
- refreshExecutor.shutdownNow();
- }
- }
-
- @Test
- public void entryDefinitionsAreFrozenAfterCatalogInitialization() {
- ExecutorService refreshExecutor = Executors.newSingleThreadExecutor();
- try {
- TestMetaCache cache = new TestMetaCache("test", refreshExecutor);
- cache.initCatalog(1L, Collections.emptyMap());
-
- Assertions.assertThrows(IllegalStateException.class,
- () -> cache.registerAdditionalEntry());
- } finally {
- refreshExecutor.shutdownNow();
- }
- }
-
- private static final class TestMetaCache extends AbstractMetaCache {
- private final EntryHandle tableEntry;
-
- private TestMetaCache(String engine, ExecutorService refreshExecutor) {
- super(engine, refreshExecutor, 60L, 16);
- tableEntry = registerEntry(MetaCacheEntryDef.of(
- "table",
- TableKey.class,
- String.class,
- key -> key.dbName + "." + key.tableName,
- CacheSpec.of(true, CacheSpec.CACHE_NO_TTL, 10L),
- MetaCacheEntryInvalidation.forTableIdentity(
- key -> key.dbName,
- key -> key.tableName)));
- }
-
- private MetaCacheEntry tableEntry(long catalogId) {
- return tableEntry.get(catalogId);
- }
-
- private void registerAdditionalEntry() {
- registerEntryDef(MetaCacheEntryDef.of(
- "additional",
- String.class,
- String.class,
- value -> value,
- CacheSpec.of(true, CacheSpec.CACHE_NO_TTL, 10L)));
- }
- }
-
- private static final class TableKey {
- private final String dbName;
- private final String tableName;
-
- private TableKey(String dbName, String tableName) {
- this.dbName = dbName;
- this.tableName = tableName;
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (!(other instanceof TableKey)) {
- return false;
- }
- TableKey that = (TableKey) other;
- return dbName.equals(that.dbName) && tableName.equals(that.tableName);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(dbName, tableName);
- }
- }
-}
diff --git a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/MetaCacheRegistryTest.java b/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/MetaCacheRegistryTest.java
deleted file mode 100644
index 50460a229434d5..00000000000000
--- a/fe/fe-connector/fe-connector-metacache/src/test/java/org/apache/doris/connector/metacache/MetaCacheRegistryTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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.metacache;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class MetaCacheRegistryTest {
- @Test
- public void resolvesNormalizedEngineAndAliases() {
- ExecutorService refreshExecutor = Executors.newSingleThreadExecutor();
- try {
- MetaCacheRegistry registry = new MetaCacheRegistry<>();
- TestMetaCache cache = new TestMetaCache("Iceberg", refreshExecutor, "iceberg-rest", "iceberg-hms");
- registry.register(cache);
-
- Assertions.assertSame(cache, registry.resolve(" ICEBERG "));
- Assertions.assertSame(cache, registry.resolve("iceberg-rest"));
- Assertions.assertSame(cache, registry.resolve("ICEBERG-HMS"));
- Assertions.assertEquals(1, registry.allCaches().size());
- } finally {
- refreshExecutor.shutdownNow();
- }
- }
-
- @Test
- public void firstEngineAndAliasRegistrationWins() {
- ExecutorService refreshExecutor = Executors.newSingleThreadExecutor();
- try {
- MetaCacheRegistry registry = new MetaCacheRegistry<>();
- TestMetaCache first = new TestMetaCache("first", refreshExecutor, "shared");
- TestMetaCache duplicatedEngine = new TestMetaCache("FIRST", refreshExecutor, "other");
- TestMetaCache conflictingAlias = new TestMetaCache("second", refreshExecutor, "shared");
-
- registry.register(first);
- registry.register(duplicatedEngine);
- registry.register(conflictingAlias);
-
- Assertions.assertSame(first, registry.resolve("first"));
- Assertions.assertSame(first, registry.resolve("shared"));
- Assertions.assertSame(conflictingAlias, registry.resolve("second"));
- Assertions.assertThrows(IllegalArgumentException.class, () -> registry.resolve("other"));
- } finally {
- refreshExecutor.shutdownNow();
- }
- }
-
- private static final class TestMetaCache extends AbstractMetaCache {
- private final Collection aliases;
-
- private TestMetaCache(String engine, ExecutorService refreshExecutor, String... aliases) {
- super(engine, refreshExecutor, 60L, 16);
- this.aliases = Arrays.asList(aliases);
- }
-
- @Override
- public Collection aliases() {
- return aliases;
- }
- }
-}
diff --git a/fe/fe-connector/fe-connector-paimon/pom.xml b/fe/fe-connector/fe-connector-paimon/pom.xml
index 79572139fe7c4c..7e3c6e11172966 100644
--- a/fe/fe-connector/fe-connector-paimon/pom.xml
+++ b/fe/fe-connector/fe-connector-paimon/pom.xml
@@ -47,21 +47,22 @@ under the License.
${project.version}
-
+
${project.groupId}
- fe-connector-metacache
+ fe-connector-cache
${project.version}
-
com.github.ben-manes.caffeine
diff --git a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnector.java b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnector.java
index fb80af3929a541..79f15eae474fd7 100644
--- a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnector.java
+++ b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnector.java
@@ -24,7 +24,7 @@
import org.apache.doris.connector.api.ConnectorSession;
import org.apache.doris.connector.api.ConnectorValidationContext;
import org.apache.doris.connector.api.scan.ConnectorScanPlanProvider;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
import org.apache.doris.connector.metastore.HmsMetaStoreProperties;
import org.apache.doris.connector.metastore.spi.JdbcDriverSupport;
import org.apache.doris.connector.metastore.spi.MetaStoreProviders;
@@ -131,8 +131,7 @@ public class PaimonConnector implements Connector {
private final PaimonSchemaAtMemo schemaAtMemo = new PaimonSchemaAtMemo(PaimonSchemaAtMemo.DEFAULT_MAX_SIZE);
// PERF-06: cross-query DERIVED partition-view cache ("cache A", the generic ConnectorMetadataCache from
- // fe-connector-metacache), layered ABOVE the raw remote catalog.listPartitions call
- // (PaimonCatalogOps#listPartitions):
+ // fe-connector-cache), layered ABOVE the raw remote catalog.listPartitions call (PaimonCatalogOps#listPartitions):
// it memoizes the BUILT List (display-name rendering + null-sentinel normalization,
// see PaimonConnectorMetadata#collectPartitions) keyed by (db, table, snapshotId, schemaId), so a repeated
// query on a partitioned table skips the derived rebuild AND the remote catalog round-trip. ONE typed field
diff --git a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorMetadata.java b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorMetadata.java
index f2d604ecc28856..a89ebd885e0fd7 100644
--- a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorMetadata.java
+++ b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorMetadata.java
@@ -32,8 +32,8 @@
import org.apache.doris.connector.api.mvcc.ConnectorTimeTravelSpec;
import org.apache.doris.connector.api.pushdown.ConnectorExpression;
import org.apache.doris.connector.api.scan.ConnectorPartitionValues;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
-import org.apache.doris.connector.metacache.ConnectorTableKey;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorTableKey;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.thrift.THiveTable;
import org.apache.doris.thrift.TTableDescriptor;
diff --git a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorProvider.java b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorProvider.java
index a966a8556a60f4..ce5d2cf1c271ec 100644
--- a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorProvider.java
+++ b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonConnectorProvider.java
@@ -18,7 +18,7 @@
package org.apache.doris.connector.paimon;
import org.apache.doris.connector.api.Connector;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
import org.apache.doris.connector.metastore.spi.MetaStoreProviders;
import org.apache.doris.connector.spi.ConnectorContext;
import org.apache.doris.connector.spi.ConnectorProvider;
diff --git a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCache.java b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCache.java
index 27cbe6bb17ecbe..eb538c7d29f0e2 100644
--- a/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCache.java
+++ b/fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCache.java
@@ -17,8 +17,8 @@
package org.apache.doris.connector.paimon;
-import org.apache.doris.connector.metacache.MetaCacheEntry;
-import org.apache.doris.connector.metacache.spi.CacheSpec;
+import org.apache.doris.connector.cache.CacheSpec;
+import org.apache.doris.connector.cache.MetaCacheEntry;
import org.apache.paimon.catalog.Identifier;
diff --git a/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorCacheTest.java b/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorCacheTest.java
index 4f6fdf4dd4d544..ce6773d67d29ea 100644
--- a/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorCacheTest.java
+++ b/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorCacheTest.java
@@ -18,8 +18,8 @@
package org.apache.doris.connector.paimon;
import org.apache.doris.connector.api.ConnectorPartitionInfo;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
-import org.apache.doris.connector.metacache.ConnectorTableKey;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorTableKey;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorMetadataPartitionViewCacheTest.java b/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorMetadataPartitionViewCacheTest.java
index 36ab4c27010354..5a702feafe084e 100644
--- a/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorMetadataPartitionViewCacheTest.java
+++ b/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonConnectorMetadataPartitionViewCacheTest.java
@@ -19,7 +19,7 @@
import org.apache.doris.connector.api.ConnectorPartitionInfo;
import org.apache.doris.connector.api.pushdown.ConnectorExpression;
-import org.apache.doris.connector.metacache.ConnectorMetadataCache;
+import org.apache.doris.connector.cache.ConnectorMetadataCache;
import org.apache.paimon.partition.Partition;
import org.apache.paimon.types.DataTypes;
diff --git a/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCacheTest.java b/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCacheTest.java
index 62439cf3517faa..de85e2153b627a 100644
--- a/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCacheTest.java
+++ b/fe/fe-connector/fe-connector-paimon/src/test/java/org/apache/doris/connector/paimon/PaimonLatestSnapshotCacheTest.java
@@ -25,7 +25,7 @@
/**
* Unit tests for {@link PaimonLatestSnapshotCache} (data-snapshot caching, CI 973411). The cache is now backed
- * by the shared {@link org.apache.doris.connector.metacache.MetaCacheEntry} framework; these tests cover the
+ * by the shared {@link org.apache.doris.connector.cache.MetaCacheEntry} framework; these tests cover the
* adapter's contract — within-TTL stability, the {@code ttl <= 0} disable, and invalidation. Timed-expiry
* mechanics are the framework's responsibility (the ttl→duration mapping is unit-tested in the framework
* module's {@code CacheSpecTest}; Caffeine {@code expireAfterAccess} itself is the library's behavior), so they
diff --git a/fe/fe-connector/pom.xml b/fe/fe-connector/pom.xml
index b9922eaddc605d..4b3a44c05a726c 100644
--- a/fe/fe-connector/pom.xml
+++ b/fe/fe-connector/pom.xml
@@ -61,8 +61,7 @@ under the License.
fe-connector-api
fe-connector-spi
- fe-connector-metacache-spi
- fe-connector-metacache
+ fe-connector-cache
fe-connector-metastore-api
fe-connector-metastore-spi