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


##########
fe/fe-core/src/main/java/org/apache/doris/connector/DefaultConnectorContext.java:
##########
@@ -164,6 +166,7 @@ public DefaultConnectorContext(String catalogName, long 
catalogId,
         this.rawStoragePropsSupplier =
                 Objects.requireNonNull(rawStoragePropsSupplier, 
"rawStoragePropsSupplier");
         this.environment = buildEnvironment();
+        this.metadataAccessMetrics = new 
ConnectorMetadataAccessMetrics(catalogName);

Review Comment:
   **[P2] Give the validation metrics reference an owner.** This constructor 
now acquires a shared per-catalog metrics reference even for the temporary 
context created by `forCatalogCreationValidation()`. `CatalogFactory` passes 
that context inline and retains only the connector, while 
`PluginDrivenExternalCatalog` explicitly leaves `connectorContext` null for 
this validation connector, so neither initialization nor catalog teardown can 
call `DefaultConnectorContext.close()` on it. Each create/replay attempt 
therefore leaves an entry in `SHARED_METRICS`; after the live catalog records 
metrics, DROP also cannot unregister those catalog-labelled series because the 
leaked reference keeps the count nonzero. Please make validation use a 
non-acquiring metrics sink or give the temporary context an explicit owner that 
closes it on every success/failure/fallback path. The same ownership rule is 
also needed for live initialization: construct into a local context, publish it 
only after connecto
 r creation succeeds, and close it on null/throw so repeated retries cannot 
overwrite and leak failed contexts.



##########
fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java:
##########
@@ -282,9 +862,16 @@ public List<HmsColumnStatistics> 
getTableColumnStatistics(String dbName, String
 
     /** Drop every cached entry for one table. Backs {@code REFRESH TABLE}. */
     public void flush(String dbName, String tableName) {
+        ReentrantLock stateLock = partitionStateLock(dbName, tableName);
+        stateLock.lock();
+        try {
+            invalidateInFlightPartitionLoads(key -> key.matches(dbName, 
tableName), true);
+        } finally {
+            stateLock.unlock();

Review Comment:
   **[P1] Keep registration fenced through the cache clear.** The state lock is 
released before `partitionsCache.invalidateIf()` bumps the generation. A cold 
request can therefore register after the in-flight scan, start its HMS RPC, 
then let this refresh clear the cache and return; because that new batch was 
never marked invalid and `publishOwnedPartitions()` uses a direct `put`, its 
pre-clear load is cached afterward for the full TTL. The same gap exists in 
partition/DB/catalog invalidation. Please perform the matching cache 
invalidation under the same stripe(s), or capture/check a refresh epoch at 
owner publication, and add the mark/register/clear/publish interleaving to the 
concurrency tests.



##########
fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java:
##########
@@ -226,49 +296,559 @@ public List<HmsPartitionInfo> getPartitions(String 
dbName, String tableName, Lis
             }
         }
         if (missNames != null) {
-            // Capture the invalidation generation BEFORE the delegate RPC so 
a REFRESH (flush) that races this
-            // in-flight cold-cache fetch does not get silently undone by 
re-caching the pre-refresh partitions.
-            // The pre-D2 code went through partitionsCache.get(key, loader) 
-> getWithManualLoad, which had this
-            // guard; the per-partition put must restore it 
(getTable/listPartitionNames/getTableColumnStatistics
-            // still use the guarded get path). The delegate results still 
populate the RESULT list directly,
-            // preserving the misparse->never-drop safety (only the CACHE put 
is generation-guarded).
-            long generation = partitionsCache.invalidationGeneration();
-            for (HmsPartitionInfo info : delegate.getPartitions(dbName, 
tableName, missNames)) {
-                partitionsCache.putIfNotInvalidatedSince(
-                        generation, new PartitionKey(dbName, tableName, 
info.getValues()), info);
-                result.add(info);
+            loadMissingPartitions(request, missNames, resultByIdentity);
+        }
+        List<HmsPartitionInfo> result = new ArrayList<>(partNames.size());
+        for (int i = 0; i < partNames.size(); i++) {
+            
checkOperationActivePeriodically(request.getEffectiveOperationControl(), i);
+            String name = partNames.get(i);
+            List<String> identity = HmsPartitionIdentity.fromName(name);
+            HmsPartitionInfo partition = resultByIdentity.get(identity);
+            if (partition == null) {
+                throw HmsPartitionResultException.builder(partNames.size(), 
resultByIdentity.size())
+                        .missing(name)
+                        .build();
             }
+            result.add(partition);
         }
+        checkOperationActive(request.getEffectiveOperationControl());
         return result;
     }
 
-    /**
-     * Splits a Hive partition name ("c1=a/c2=b") into its ordered values 
("a", "b"), unescaping each via
-     * Hive's {@code FileUtils} (already a hms-module dependency — {@code 
HmsEventParser} uses it). Semantics
-     * match the write path's {@code HiveWriteUtils.toPartitionValues}, so 
scan and write correlate partitions
-     * identically. Only used to build the per-partition LOOKUP key: a parse 
that diverges from the stored
-     * partition's own values just misses and re-fetches (never a 
wrong/dropped partition), so this is a
-     * hit-rate optimization, not a correctness dependency.
-     */
-    private static List<String> toPartitionValues(String partitionName) {
-        List<String> values = new ArrayList<>();
-        int start = 0;
+    private void loadMissingPartitions(HmsPartitionRequest request, 
List<String> initialMissNames,
+            Map<List<String>, HmsPartitionInfo> resultByIdentity) {
+        if (!partitionsCache.isEffectiveEnabled()) {

Review Comment:
   **[P1] Preserve load admission when partition caching is disabled.** This 
early return skips both single-flight retention *and* the new window/slot 
limiter. In the supported `hive.metastore.client.pool.size=0` configuration, 
the constructor deliberately converts zero to one cold-load slot, but every 
disabled-cache request now bypasses that slot and `ThriftHmsClient` creates a 
fresh client per call; N concurrent scans/freshness probes can therefore open N 
HMS connections. Please keep windowing and slot admission on this path while 
skipping only cache coordination/publication, and cover zero-pool plus disabled 
cache concurrently.



##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorMetadata.java:
##########
@@ -1422,13 +1456,34 @@ public OptionalLong 
getPartitionFreshnessMillis(ConnectorSession session, Connec
         if (!(handle instanceof HiveTableHandle)) {
             return siblingMetadata(session, 
handle).getPartitionFreshnessMillis(session, handle, partitionName);
         }
-        HiveTableHandle hiveHandle = (HiveTableHandle) handle;
-        List<HmsPartitionInfo> partitions = 
hmsClient.getPartitions(hiveHandle.getDbName(),
-                hiveHandle.getTableName(), 
Collections.singletonList(partitionName));
-        if (partitions.isEmpty()) {
+        Map<String, Long> freshness = getPartitionFreshnessMillis(
+                session, handle, Collections.singletonList(partitionName));
+        Long partitionFreshness = freshness.get(partitionName);
+        if (partitionFreshness == null) {
             return OptionalLong.empty();
         }
-        return 
OptionalLong.of(lastDdlMillis(partitions.get(0).getParameters()));
+        return OptionalLong.of(partitionFreshness);
+    }
+
+    @Override
+    public Map<String, Long> getPartitionFreshnessMillis(ConnectorSession 
session, ConnectorTableHandle handle,
+            List<String> partitionNames) {
+        if (!(handle instanceof HiveTableHandle)) {
+            return siblingMetadata(session, handle)
+                    .getPartitionFreshnessMillis(session, handle, 
partitionNames);
+        }
+        if (partitionNames.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        HiveTableHandle hiveHandle = (HiveTableHandle) handle;
+        List<HmsPartitionInfo> partitions = hmsClient.getPartitions(
+                session, HmsPartitionAccessSource.MTMV,

Review Comment:
   **[P2] Preserve the display source in freshness telemetry.** `SHOW 
PARTITIONS` now builds and preloads `MTMVRefreshContext`, reaches these 
freshness methods, and is always emitted as `MTMV` here; the sibling 
whole-table freshness call is hard-coded the same way. There is no production 
use of the newly added `HmsPartitionAccessSource.DISPLAY`, so display traffic 
is indistinguishable from refresh/rewrite work in both process metrics and 
Query Profile despite the per-source observability contract. Please thread the 
logical access purpose into this freshness request and emit `DISPLAY` for the 
proc/display path, with a production-chain test.



##########
fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java:
##########
@@ -226,49 +296,559 @@ public List<HmsPartitionInfo> getPartitions(String 
dbName, String tableName, Lis
             }
         }
         if (missNames != null) {
-            // Capture the invalidation generation BEFORE the delegate RPC so 
a REFRESH (flush) that races this
-            // in-flight cold-cache fetch does not get silently undone by 
re-caching the pre-refresh partitions.
-            // The pre-D2 code went through partitionsCache.get(key, loader) 
-> getWithManualLoad, which had this
-            // guard; the per-partition put must restore it 
(getTable/listPartitionNames/getTableColumnStatistics
-            // still use the guarded get path). The delegate results still 
populate the RESULT list directly,
-            // preserving the misparse->never-drop safety (only the CACHE put 
is generation-guarded).
-            long generation = partitionsCache.invalidationGeneration();
-            for (HmsPartitionInfo info : delegate.getPartitions(dbName, 
tableName, missNames)) {
-                partitionsCache.putIfNotInvalidatedSince(
-                        generation, new PartitionKey(dbName, tableName, 
info.getValues()), info);
-                result.add(info);
+            loadMissingPartitions(request, missNames, resultByIdentity);
+        }
+        List<HmsPartitionInfo> result = new ArrayList<>(partNames.size());
+        for (int i = 0; i < partNames.size(); i++) {
+            
checkOperationActivePeriodically(request.getEffectiveOperationControl(), i);
+            String name = partNames.get(i);
+            List<String> identity = HmsPartitionIdentity.fromName(name);
+            HmsPartitionInfo partition = resultByIdentity.get(identity);
+            if (partition == null) {
+                throw HmsPartitionResultException.builder(partNames.size(), 
resultByIdentity.size())
+                        .missing(name)
+                        .build();
             }
+            result.add(partition);
         }
+        checkOperationActive(request.getEffectiveOperationControl());
         return result;
     }
 
-    /**
-     * Splits a Hive partition name ("c1=a/c2=b") into its ordered values 
("a", "b"), unescaping each via
-     * Hive's {@code FileUtils} (already a hms-module dependency — {@code 
HmsEventParser} uses it). Semantics
-     * match the write path's {@code HiveWriteUtils.toPartitionValues}, so 
scan and write correlate partitions
-     * identically. Only used to build the per-partition LOOKUP key: a parse 
that diverges from the stored
-     * partition's own values just misses and re-fetches (never a 
wrong/dropped partition), so this is a
-     * hit-rate optimization, not a correctness dependency.
-     */
-    private static List<String> toPartitionValues(String partitionName) {
-        List<String> values = new ArrayList<>();
-        int start = 0;
+    private void loadMissingPartitions(HmsPartitionRequest request, 
List<String> initialMissNames,
+            Map<List<String>, HmsPartitionInfo> resultByIdentity) {
+        if (!partitionsCache.isEffectiveEnabled()) {
+            loadAndCacheMissingPartitions(
+                    request, initialMissNames, 
partitionsCache.invalidationGeneration(), resultByIdentity);
+            return;
+        }
+        for (int offset = 0; offset < initialMissNames.size(); offset += 
partitionLoadWindowSize) {
+            checkOperationActive(request.getEffectiveOperationControl());
+            int end = Math.min(offset + partitionLoadWindowSize, 
initialMissNames.size());
+            loadMissingPartitionWindow(request, 
initialMissNames.subList(offset, end), resultByIdentity);
+        }
+    }
+
+    private void loadMissingPartitionWindow(HmsPartitionRequest request, 
List<String> initialMissNames,
+            Map<List<String>, HmsPartitionInfo> resultByIdentity) {
+        ConnectorOperationControl operationControl = 
request.getEffectiveOperationControl();
+        List<String> pendingNames = initialMissNames;
+        while (!pendingNames.isEmpty()) {
+            checkOperationActive(operationControl);
+            PartitionLoadBatch ownedBatch = new PartitionLoadBatch();
+            List<PartitionLoadRegistration> owned = new ArrayList<>();
+            Map<PartitionLoadBatch, List<PartitionLoadRegistration>> waiting = 
new IdentityHashMap<>();
+            acquirePartitionLoadSlot(request, pendingNames.size());
+            try {
+                try {
+                    for (int i = 0; i < pendingNames.size(); i++) {
+                        checkOperationActivePeriodically(operationControl, i);
+                        registerPartitionLoad(
+                                request, pendingNames.get(i), ownedBatch, 
resultByIdentity, owned, waiting);
+                    }
+                    afterPartitionLoadRegistrationForTest();
+                    if (!owned.isEmpty()) {
+                        loadOwnedPartitions(request, ownedBatch, owned, 
resultByIdentity);
+                    }
+                    ownedBatch.future.complete(PartitionLoadOutcome.success());
+                } catch (RuntimeException | Error e) {
+                    
ownedBatch.future.complete(PartitionLoadOutcome.failure(e));
+                    throw e;
+                } finally {
+                    releaseOwnedPartitionLoads(ownedBatch);
+                }
+            } finally {
+                // A pure waiter does not consume HMS capacity. Release the 
owner-registration/load budget before
+                // waiting so one slow identity cannot block unrelated cold 
loads while pool clients are idle.
+                partitionLoadSlots.release();
+            }
+            List<String> retryNames = new ArrayList<>();
+            for (Map.Entry<PartitionLoadBatch, 
List<PartitionLoadRegistration>> entry : waiting.entrySet()) {
+                consumeWaitingBatch(request, entry.getKey(), entry.getValue(), 
resultByIdentity, retryNames);
+            }
+            pendingNames = retryNames;
+        }
+    }
+
+    /** Test seam for observing registrations without changing the production 
coordination contract. */
+    void afterPartitionLoadRegistrationForTest() {
+    }
+
+    int inFlightPartitionLoadCountForTest() {
+        return inFlightPartitionLoads.size();
+    }
+
+    private void registerPartitionLoad(HmsPartitionRequest request, String 
partitionName,
+            PartitionLoadBatch ownedBatch, Map<List<String>, HmsPartitionInfo> 
resultByIdentity,
+            List<PartitionLoadRegistration> owned,
+            Map<PartitionLoadBatch, List<PartitionLoadRegistration>> waiting) {
+        List<String> values = HmsPartitionIdentity.fromName(partitionName);
+        PartitionKey key = new PartitionKey(request.getDbName(), 
request.getTableName(), values);
+        ReentrantLock stateLock = partitionStateLock(request.getDbName(), 
request.getTableName());
+        acquirePartitionStateLock(stateLock, 
request.getEffectiveOperationControl());
+        try {
+            HmsPartitionInfo hit = partitionsCache.getIfPresent(key);
+            if (hit != null) {
+                resultByIdentity.put(values, hit);
+                return;
+            }
+            while (true) {
+                PartitionLoadBatch existing = 
inFlightPartitionLoads.putIfAbsent(key, ownedBatch);
+                if (existing == null) {
+                    break;
+                }
+                if (!existing.isInvalidated(key)) {
+                    waiting.computeIfAbsent(existing, ignored -> new 
ArrayList<>())
+                            .add(new PartitionLoadRegistration(partitionName, 
key));
+                    return;
+                }
+                if (inFlightPartitionLoads.replace(key, existing, ownedBatch)) 
{
+                    break;
+                }
+            }
+            ownedBatch.claimedKeys.add(key);
+            // Close the cache-check/register race: a previous owner may have 
filled the cache and removed its
+            // future after our first cache check but before this putIfAbsent.
+            hit = partitionsCache.getIfPresent(key);
+            if (hit != null) {
+                ownedBatch.resolvedPartitions.put(key, hit);
+                resultByIdentity.put(values, hit);
+                ownedBatch.claimedKeys.remove(key);
+                inFlightPartitionLoads.remove(key, ownedBatch);
+                return;
+            }
+            owned.add(new PartitionLoadRegistration(partitionName, key));
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    private void loadOwnedPartitions(HmsPartitionRequest request, 
PartitionLoadBatch ownedBatch,
+            List<PartitionLoadRegistration> owned, Map<List<String>, 
HmsPartitionInfo> resultByIdentity) {
+        List<String> ownedNames = new ArrayList<>(owned.size());
+        Map<List<String>, PartitionLoadRegistration> ownedByIdentity = new 
HashMap<>();
+        for (PartitionLoadRegistration registration : owned) {
+            ownedNames.add(registration.partitionName);
+            ownedByIdentity.put(registration.key.values, registration);
+        }
+        HmsPartitionRequest missRequest = copiedPartitionRequest(request, 
ownedNames)
+                .partitionChunkConsumer((chunkNames, chunkPartitions, 
effectiveControl) -> publishOwnedPartitions(
+                        request, chunkPartitions, resultByIdentity, 
ownedBatch, ownedByIdentity, effectiveControl))
+                .build();
+        List<HmsPartitionInfo> loaded = delegate.getPartitions(missRequest);
+        ConnectorOperationControl effectiveControl = 
request.getEffectiveOperationControl();
+        checkOperationActive(effectiveControl);
+        if (ownedByIdentity.isEmpty()) {
+            return;
+        }
+        if (ownedByIdentity.size() != ownedNames.size()) {
+            throw new HmsClientException("HMS delegate invoked the partition 
chunk consumer for only part of "
+                    + "the request: requested=" + ownedNames.size() + ", 
unpublished=" + ownedByIdentity.size());
+        }
+        // Compatibility fallback for legacy/test delegates that implement the 
request overload without invoking
+        // its chunk consumer. The production raw client always takes the 
zero-extra-validation branch above.
+        List<HmsPartitionInfo> validated = 
HmsPartitionBatchLoader.validateAndOrder(
+                ownedNames, loaded, effectiveControl);
+        publishOwnedPartitions(
+                request, validated, resultByIdentity, ownedBatch, 
ownedByIdentity, effectiveControl);
+    }
+
+    private void releaseOwnedPartitionLoads(PartitionLoadBatch ownedBatch) {
+        for (PartitionKey key : ownedBatch.claimedKeys) {
+            inFlightPartitionLoads.remove(key, ownedBatch);
+        }
+    }
+
+    private void loadAndCacheMissingPartitions(HmsPartitionRequest request, 
List<String> missNames,
+            long generation, Map<List<String>, HmsPartitionInfo> 
resultByIdentity) {
+        Set<List<String>> unpublishedIdentities = new LinkedHashSet<>();
+        for (String missName : missNames) {
+            unpublishedIdentities.add(HmsPartitionIdentity.fromName(missName));
+        }
+        HmsPartitionRequest missRequest = copiedPartitionRequest(request, 
missNames)
+                .partitionChunkConsumer((chunkNames, chunkPartitions, 
effectiveControl) ->
+                        publishUncachedPartitions(request, chunkPartitions, 
generation,
+                                resultByIdentity, unpublishedIdentities, 
effectiveControl))
+                .build();
+        List<HmsPartitionInfo> loaded = delegate.getPartitions(missRequest);
+        ConnectorOperationControl effectiveControl = 
request.getEffectiveOperationControl();
+        checkOperationActive(effectiveControl);
+        if (unpublishedIdentities.isEmpty()) {
+            return;
+        }
+        if (unpublishedIdentities.size() != missNames.size()) {
+            throw new HmsClientException("HMS delegate invoked the partition 
chunk consumer for only part of "
+                    + "the request: requested=" + missNames.size()
+                    + ", unpublished=" + unpublishedIdentities.size());
+        }
+        List<HmsPartitionInfo> validated = 
HmsPartitionBatchLoader.validateAndOrder(
+                missNames, loaded, effectiveControl);
+        publishUncachedPartitions(request, validated, generation,
+                resultByIdentity, unpublishedIdentities, effectiveControl);
+    }
+
+    private void publishUncachedPartitions(HmsPartitionRequest request, 
List<HmsPartitionInfo> loaded,
+            long generation, Map<List<String>, HmsPartitionInfo> 
resultByIdentity,
+            Set<List<String>> unpublishedIdentities, ConnectorOperationControl 
effectiveControl) {
+        for (int i = 0; i < loaded.size(); i++) {
+            checkOperationActivePeriodically(effectiveControl, i);
+            HmsPartitionInfo info = loaded.get(i);
+            if (!unpublishedIdentities.remove(info.getValues())) {
+                throw new HmsClientException(
+                        "HMS chunk consumer published an unowned partition: " 
+ info.getValues());
+            }
+            PartitionKey key = new PartitionKey(request.getDbName(), 
request.getTableName(), info.getValues());
+            partitionsCache.putIfNotInvalidatedSince(generation, key, info);
+            resultByIdentity.put(info.getValues(), info);
+        }
+        checkOperationActive(effectiveControl);
+    }
+
+    private static HmsPartitionRequest.Builder copiedPartitionRequest(
+            HmsPartitionRequest request, List<String> partitionNames) {
+        return HmsPartitionRequest.builder()
+                .database(request.getDbName())
+                .table(request.getTableName())
+                .partitionNames(partitionNames)
+                .source(request.getSource())
+                .operationControl(request.getOperationControl())
+                .metadataAccessObserver(request.getMetadataAccessObserver())
+                .shareBatchExecutionWith(request);
+    }
+
+    private void publishOwnedPartitions(HmsPartitionRequest request,
+            List<HmsPartitionInfo> loaded, Map<List<String>, HmsPartitionInfo> 
resultByIdentity,
+            PartitionLoadBatch ownedBatch, Map<List<String>, 
PartitionLoadRegistration> ownedByIdentity,
+            ConnectorOperationControl effectiveControl) {
+        for (int i = 0; i < loaded.size(); i++) {
+            checkOperationActivePeriodically(effectiveControl, i);
+            HmsPartitionInfo info = loaded.get(i);
+            PartitionLoadRegistration registration = 
ownedByIdentity.remove(info.getValues());
+            if (registration == null) {
+                throw new HmsClientException(
+                        "HMS chunk consumer published an unowned partition: " 
+ info.getValues());
+            }
+            // Every partition-cache invalidation takes the same table state 
lock (flushDb/flushAll take all
+            // stripes). Therefore a relevant refresh either invalidates this 
key before this critical section,
+            // making isInvalidated true, or runs after the put and removes 
it. Direct publication under that lock
+            // also avoids a refresh of an unrelated table suppressing this 
valid result.
+            ReentrantLock stateLock = partitionStateLock(request.getDbName(), 
request.getTableName());
+            acquirePartitionStateLock(stateLock, effectiveControl);
+            try {
+                if (!ownedBatch.isInvalidated(registration.key)) {
+                    partitionsCache.put(registration.key, info);
+                }
+                ownedBatch.resolvedPartitions.put(registration.key, info);
+            } finally {
+                stateLock.unlock();
+            }
+            resultByIdentity.put(info.getValues(), info);
+        }
+        checkOperationActive(effectiveControl);
+    }
+
+    private void consumeWaitingBatch(HmsPartitionRequest request, 
PartitionLoadBatch batch,
+            List<PartitionLoadRegistration> registrations, Map<List<String>, 
HmsPartitionInfo> resultByIdentity,
+            List<String> retryNames) {
+        long startNanos = System.nanoTime();
+        boolean success = false;
+        try {
+            boolean retrying = false;
+            for (PartitionLoadRegistration registration : registrations) {
+                awaitPartitionLoad(batch, registration.key, 
request.getEffectiveOperationControl());
+                if (batch.isInvalidated(registration.key)) {
+                    inFlightPartitionLoads.remove(registration.key, batch);
+                    retryNames.add(registration.partitionName);
+                    retrying = true;
+                    continue;
+                }
+                HmsPartitionInfo partition = 
batch.resolvedPartitions.get(registration.key);
+                if (partition != null) {
+                    resultByIdentity.put(partition.getValues(), partition);
+                    continue;
+                }
+                PartitionLoadOutcome outcome = batch.future.getNow(null);
+                checkOperationActive(request.getEffectiveOperationControl());
+                Throwable ownerFailure = Objects.requireNonNull(
+                        Objects.requireNonNull(outcome,
+                                "partition load is unresolved but its 
completion is not available").failure,
+                        "completed partition load has neither a result nor a 
failure");
+                if (!isRetryableSharedFailure(ownerFailure)) {
+                    rethrow(ownerFailure);
+                }
+                // Only an exception published by the OWNER reaches this 
branch. Cancellation, deadline and
+                // interruption of the waiting request itself escape directly 
from awaitPartitionLoad and must
+                // never remove or replace a normally-running owner's future.
+                checkOperationActive(request.getEffectiveOperationControl());
+                inFlightPartitionLoads.remove(registration.key, batch);
+                retryNames.add(registration.partitionName);
+                retrying = true;
+            }
+            success = !retrying;
+        } finally {
+            recordPartitionWait(request, registrations.size(), startNanos, 
success);
+        }
+    }
+
+    private static void awaitPartitionLoad(
+            PartitionLoadBatch batch, PartitionKey key, 
ConnectorOperationControl control) {
+        while (true) {
+            long remainingMillis = checkOperationActive(control);
+            if (batch.isInvalidated(key)
+                    || batch.resolvedPartitions.containsKey(key) || 
batch.future.isDone()) {
+                return;
+            }
+            long waitMillis = remainingMillis == Long.MAX_VALUE
+                    ? PARTITION_LOAD_WAIT_CHECK_MILLIS
+                    : Math.min(remainingMillis, 
PARTITION_LOAD_WAIT_CHECK_MILLIS);
+            try {
+                batch.future.get(waitMillis, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                // Re-check the waiting request's cancellation and deadline at 
a bounded interval.
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new ConnectorOperationAbortedException(
+                        ConnectorOperationAbortedException.Reason.CANCELLED,
+                        "HMS in-flight partition load wait was interrupted");
+            } catch (ExecutionException e) {
+                Throwable cause = e.getCause();
+                rethrow(cause);
+                throw new AssertionError("unreachable");
+            }
+        }
+    }
+
+    private static boolean isRetryableSharedFailure(Throwable failure) {
+        return failure instanceof ConnectorOperationAbortedException
+                || failure instanceof HmsPartitionResultException;
+    }
+
+    private static void rethrow(Throwable failure) {
+        if (failure instanceof RuntimeException) {
+            throw (RuntimeException) failure;
+        }
+        if (failure instanceof Error) {
+            throw (Error) failure;
+        }
+        throw new HmsClientException("HMS in-flight partition load failed", 
failure);
+    }
+
+    private static void recordPartitionWait(HmsPartitionRequest request, int 
requestedItems,
+            long startNanos, boolean success) {
+        recordPartitionCoordinationWait(
+                request, PARTITION_INFLIGHT_WAIT_OPERATION, requestedItems, 
startNanos, success);
+    }
+
+    private static void recordPartitionCoordinationWait(HmsPartitionRequest 
request, String operation,
+            int requestedItems, long startNanos, boolean success) {
+        ConnectorMetadataAccessEvent event = 
ConnectorMetadataAccessEvent.builder()
+                .operation(operation)
+                .source(request.getSource().name())
+                .requestedItems(requestedItems)
+                
.logicalElapsedMillis(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - 
startNanos))
+                .success(success)
+                .build();
+        try {
+            request.getMetadataAccessObserver().record(event);

Review Comment:
   **[P2] Publish coordination waits to process metrics too.** Both in-flight 
and cold-load-slot wait events are recorded only through the request observer, 
which is a Query Profile sink and becomes `NOOP` when profiling is disabled. 
The catalog observer used by the raw loader never reaches this cache layer, so 
FE metrics permanently omit both newly advertised wait operations even while 
reporting their downstream HMS call. Please give the cache the catalog observer 
and safely publish these events to both sinks, with process-metric coverage 
when query profiling is disabled.



##########
fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/HmsPartitionBatchLoader.java:
##########
@@ -0,0 +1,458 @@
+// 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.hms;
+
+import org.apache.doris.connector.spi.ConnectorMetadataAccessEvent;
+import org.apache.doris.connector.spi.ConnectorMetadataAccessObserver;
+import org.apache.doris.connector.spi.ConnectorOperationAbortedException;
+import org.apache.doris.connector.spi.ConnectorOperationControl;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.LongSupplier;
+
+/** The single chunking, adaptive fallback, integrity and observability 
implementation for HMS partitions. */
+final class HmsPartitionBatchLoader {
+
+    private static final Logger LOG = 
LogManager.getLogger(HmsPartitionBatchLoader.class);
+
+    @FunctionalInterface
+    interface Fetcher {
+        List<HmsPartitionInfo> fetch(String dbName, String tableName, 
List<String> partitionNames,
+                ConnectorOperationControl operationControl) throws Exception;
+    }
+
+    @FunctionalInterface
+    interface TrackedFetcher {
+        List<HmsPartitionInfo> fetch(String dbName, String tableName, 
List<String> partitionNames,
+                ConnectorOperationControl operationControl, RemoteCallTracker 
remoteCallTracker) throws Exception;
+    }
+
+    @FunctionalInterface
+    interface FailureClassifier {
+        boolean isDegradable(Throwable failure);
+    }
+
+    private final int maxBatchSize;
+    private final int minBatchSize;
+    private final long fallbackTimeoutMillis;
+    private final TrackedFetcher fetcher;
+    private final FailureClassifier failureClassifier;
+    private final ConnectorMetadataAccessObserver observer;
+    private final LongSupplier nanoTime;
+
+    private HmsPartitionBatchLoader(Builder builder) {
+        this.maxBatchSize = builder.maxBatchSize;
+        this.minBatchSize = builder.minBatchSize;
+        this.fallbackTimeoutMillis = builder.fallbackTimeoutMillis;
+        this.fetcher = builder.fetcher;
+        this.failureClassifier = builder.failureClassifier;
+        this.observer = builder.observer;
+        this.nanoTime = builder.nanoTime;
+    }
+
+    static Builder builder() {
+        return new Builder();
+    }
+
+    List<HmsPartitionInfo> load(HmsPartitionRequest request) {
+        List<String> names = request.getPartitionNames();
+        if (names.isEmpty()) {
+            return java.util.Collections.emptyList();
+        }
+        long startNanos = nanoTime.getAsLong();
+        long fallbackTimeoutNanos = 
TimeUnit.MILLISECONDS.toNanos(fallbackTimeoutMillis);
+        long fallbackStartNanos = request.fallbackStartNanos();
+        RemoteCallTracker remoteCalls = new RemoteCallTracker(nanoTime);
+        int fallbackCount = 0;
+        boolean success = false;
+        try {
+            List<HmsPartitionInfo> result = new ArrayList<>(names.size());
+            int offset = 0;
+            int effectiveBatchSize = request.effectiveBatchSize(maxBatchSize);
+            while (offset < names.size()) {
+                checkActive(request, fallbackStartNanos, fallbackTimeoutNanos);
+                int batchSize = Math.min(effectiveBatchSize, names.size() - 
offset);
+                List<String> batch = new ArrayList<>(names.subList(offset, 
offset + batchSize));
+                List<HmsPartitionInfo> returned;
+                ConnectorOperationControl effectiveControl = 
operationControlForAttempt(
+                        request, fallbackStartNanos, fallbackTimeoutNanos);
+                try {
+                    returned = fetcher.fetch(
+                            request.getDbName(), request.getTableName(), batch,
+                            effectiveControl, remoteCalls);
+                } catch (HmsRemoteCallException e) {
+                    checkActive(request, fallbackStartNanos, 
fallbackTimeoutNanos);
+                    if (batchSize <= minBatchSize || 
!failureClassifier.isDegradable(e)) {
+                        throw finalBatchFailure(
+                                request, offset, batchSize, effectiveBatchSize,
+                                remoteCalls.count, fallbackCount, e);
+                    }
+                    if (fallbackStartNanos == 
HmsPartitionRequest.NO_FALLBACK_START_NANOS) {
+                        fallbackStartNanos = 
request.startFallback(nanoTime.getAsLong());
+                    }
+                    checkActive(request, fallbackStartNanos, 
fallbackTimeoutNanos);
+                    effectiveBatchSize = Math.max(minBatchSize, batchSize / 2);
+                    request.reduceEffectiveBatchSize(effectiveBatchSize);
+                    fallbackCount++;
+                    continue;
+                } catch (RuntimeException e) {
+                    // Authorization, cancellation and local programming 
failures are not transport fallback
+                    // candidates. Preserve their original type and stack 
instead of disguising them as a failed
+                    // HMS batch.
+                    throw e;
+                } catch (Exception e) {
+                    throw new HmsClientException("Unexpected checked failure 
fetching HMS partitions", e);
+                }
+                // Integrity validation and cache publication are deliberately 
outside the remote-failure catch:
+                // a malformed response or a local write-back bug must never 
trigger transport fallback or be
+                // wrapped as an HMS RPC failure.
+                checkActive(effectiveControl);
+                List<HmsPartitionInfo> ordered = validateAndOrder(batch, 
returned, effectiveControl);
+                checkActive(effectiveControl);
+                request.getPartitionChunkConsumer().accept(batch, ordered, 
effectiveControl);
+                checkActive(effectiveControl);
+                result.addAll(ordered);
+                checkActive(effectiveControl);
+                offset += batchSize;
+            }
+            checkActive(request, fallbackStartNanos, fallbackTimeoutNanos);
+            success = true;
+            return result;
+        } finally {
+            ConnectorMetadataAccessEvent event = 
ConnectorMetadataAccessEvent.builder()
+                    .operation("hms.get_partitions_by_names")
+                    .source(request.getSource().name())
+                    .requestedItems(names.size())
+                    .rpcCount(remoteCalls.count)
+                    .rpcItems(remoteCalls.items)
+                    .largestBatchSize(remoteCalls.largestBatchSize)
+                    .smallestBatchSize(remoteCalls.smallestBatchSize == 
Integer.MAX_VALUE
+                            ? 0 : remoteCalls.smallestBatchSize)
+                    .fallbackCount(fallbackCount)
+                    
.logicalElapsedMillis(TimeUnit.NANOSECONDS.toMillis(nanoTime.getAsLong() - 
startNanos))
+                    
.rpcElapsedMillis(TimeUnit.NANOSECONDS.toMillis(remoteCalls.elapsedNanos))
+                    
.maxRpcElapsedMillis(TimeUnit.NANOSECONDS.toMillis(remoteCalls.maxElapsedNanos))
+                    .success(success)
+                    .build();
+            recordSafely("catalog metrics", observer, event);
+            recordSafely("query profile", request.getMetadataAccessObserver(), 
event);
+        }
+    }
+
+    private void recordSafely(String sinkName, ConnectorMetadataAccessObserver 
sink,
+            ConnectorMetadataAccessEvent event) {
+        try {
+            sink.record(event);
+        } catch (RuntimeException e) {
+            LOG.warn("Failed to record HMS partition metadata access in {}", 
sinkName, e);
+        }
+    }
+
+    private void checkActive(HmsPartitionRequest request,
+            long fallbackStartNanos, long fallbackTimeoutNanos) {
+        checkActive(request.getOperationControl());
+        if (fallbackStartNanos != HmsPartitionRequest.NO_FALLBACK_START_NANOS
+                && nanoTime.getAsLong() - fallbackStartNanos >= 
fallbackTimeoutNanos) {
+            throw new ConnectorOperationAbortedException(
+                    
ConnectorOperationAbortedException.Reason.DEADLINE_EXCEEDED,
+                    "HMS partition batch fallback deadline exceeded");
+        }
+    }
+
+    private static void checkActive(ConnectorOperationControl 
operationControl) {
+        operationControl.checkActive();
+        if (operationControl.remainingTimeMillis() <= 0) {
+            throw new ConnectorOperationAbortedException(
+                    
ConnectorOperationAbortedException.Reason.DEADLINE_EXCEEDED,
+                    "HMS partition batch request deadline exceeded");
+        }
+    }
+
+    private ConnectorOperationControl 
operationControlForAttempt(HmsPartitionRequest request,
+            long fallbackStartNanos, long fallbackTimeoutNanos) {
+        if (fallbackStartNanos == HmsPartitionRequest.NO_FALLBACK_START_NANOS) 
{
+            
request.updateEffectiveOperationControl(request.getOperationControl());
+            return request.getOperationControl();
+        }
+        ConnectorOperationControl effectiveControl = new 
FallbackOperationControl(
+                request.getOperationControl(), fallbackStartNanos, 
fallbackTimeoutNanos, nanoTime);
+        request.updateEffectiveOperationControl(effectiveControl);
+        return effectiveControl;
+    }
+
+    /** Applies the fallback budget inside pool waits and 
RetryingMetaStoreClient retries, not only between RPCs. */
+    private static final class FallbackOperationControl implements 
ConnectorOperationControl {
+        private final ConnectorOperationControl callerControl;
+        private final long fallbackStartNanos;
+        private final long fallbackTimeoutNanos;
+        private final LongSupplier nanoTime;
+
+        private FallbackOperationControl(ConnectorOperationControl 
callerControl,
+                long fallbackStartNanos, long fallbackTimeoutNanos, 
LongSupplier nanoTime) {
+            this.callerControl = callerControl;
+            this.fallbackStartNanos = fallbackStartNanos;
+            this.fallbackTimeoutNanos = fallbackTimeoutNanos;
+            this.nanoTime = nanoTime;
+        }
+
+        @Override
+        public void checkActive() {
+            HmsPartitionBatchLoader.checkActive(callerControl);
+            if (fallbackRemainingNanos() <= 0) {
+                throw new ConnectorOperationAbortedException(
+                        
ConnectorOperationAbortedException.Reason.DEADLINE_EXCEEDED,
+                        "HMS partition batch fallback deadline exceeded");
+            }
+        }
+
+        @Override
+        public long remainingTimeMillis() {
+            long callerRemainingMillis = callerControl.remainingTimeMillis();
+            long fallbackRemainingMillis = TimeUnit.NANOSECONDS.toMillis(
+                    Math.max(0L, fallbackRemainingNanos()));
+            return Math.min(callerRemainingMillis, fallbackRemainingMillis);
+        }
+
+        private long fallbackRemainingNanos() {
+            return fallbackTimeoutNanos - (nanoTime.getAsLong() - 
fallbackStartNanos);
+        }
+    }
+
+    static List<HmsPartitionInfo> validateAndOrder(
+            List<String> requestedNames, List<HmsPartitionInfo> returned) {
+        return validateAndOrder(requestedNames, returned, 
ConnectorOperationControl.NONE);
+    }
+
+    static List<HmsPartitionInfo> validateAndOrder(List<String> requestedNames,
+            List<HmsPartitionInfo> returned, ConnectorOperationControl 
operationControl) {
+        int expectedValueCount = 
HmsPartitionIdentity.fromName(requestedNames.get(0)).size();
+        Map<List<String>, Integer> expected = new HashMap<>();
+        List<List<String>> requestedIdentities = new 
ArrayList<>(requestedNames.size());
+        for (int i = 0; i < requestedNames.size(); i++) {
+            checkActivePeriodically(operationControl, i);
+            List<String> identity = 
HmsPartitionIdentity.fromName(requestedNames.get(i));
+            requestedIdentities.add(identity);
+            Integer previous = expected.put(identity, i);
+            if (previous != null) {
+                throw new IllegalArgumentException(
+                        "duplicate partition identity in request: " + 
requestedNames.get(i));
+            }
+        }
+        HmsPartitionResultException.Builder failure = 
HmsPartitionResultException.builder(
+                requestedNames.size(), returned == null ? 0 : returned.size());
+        List<HmsPartitionInfo> ordered = new 
ArrayList<>(java.util.Collections.nCopies(requestedNames.size(), null));
+        Map<List<String>, Integer> returnedCounts = new LinkedHashMap<>();
+        if (returned == null) {
+            failure.invalid("<null response>");
+        } else {
+            for (int i = 0; i < returned.size(); i++) {
+                checkActivePeriodically(operationControl, i);
+                HmsPartitionInfo partition = returned.get(i);
+                if (partition == null) {
+                    failure.invalid("<null partition>");
+                    continue;
+                }
+                List<String> identity = partition.getValues();
+                if (identity.size() != expectedValueCount) {
+                    failure.invalid(identity.toString());
+                    continue;
+                }
+                returnedCounts.merge(identity, 1, Integer::sum);
+                Integer index = expected.get(identity);
+                if (index != null && ordered.get(index) == null) {
+                    ordered.set(index, partition);
+                }
+            }
+        }
+        for (int i = 0; i < requestedIdentities.size(); i++) {
+            checkActivePeriodically(operationControl, i);
+            if (!returnedCounts.containsKey(requestedIdentities.get(i))) {
+                failure.missing(requestedNames.get(i));
+            }
+        }
+        int returnedIndex = 0;
+        for (Map.Entry<List<String>, Integer> entry : 
returnedCounts.entrySet()) {
+            checkActivePeriodically(operationControl, returnedIndex++);
+            if (!expected.containsKey(entry.getKey())) {
+                failure.unexpected(entry.getKey().toString());
+            }
+            if (entry.getValue() > 1) {
+                failure.duplicate(entry.getKey().toString());
+            }
+        }
+        if (failure.hasMismatches()) {
+            throw failure.build();
+        }
+        return ordered;
+    }
+
+    private static void checkActivePeriodically(ConnectorOperationControl 
operationControl, int index) {
+        if ((index & 1023) == 0) {
+            checkActive(operationControl);
+        }
+    }
+
+    private RuntimeException finalBatchFailure(HmsPartitionRequest request, 
int offset,
+            int failedBatchSize, int effectiveBatchSize, int rpcCount, int 
fallbackCount, Exception failure) {
+        if (failure instanceof ConnectorOperationAbortedException
+                || failure instanceof HmsPartitionResultException) {
+            return (RuntimeException) failure;
+        }
+        String message = String.format(
+                "HMS partition batch request failed: db=%s, table=%s, 
requested=%d, offset=%d, "
+                        + "failedBatchSize=%d, effectiveBatchSize=%d, 
minBatchSize=%d, attempts=%d, "
+                        + "fallbacks=%d: %s",
+                request.getDbName(), request.getTableName(), 
request.getPartitionNames().size(), offset,
+                failedBatchSize, effectiveBatchSize, minBatchSize, rpcCount, 
fallbackCount,
+                failure.getMessage());
+        return new HmsClientException(message, failure);
+    }
+
+    static final class RemoteCallTracker {
+        private final LongSupplier nanoTime;
+        private int count;
+        private long items;
+        private long elapsedNanos;
+        private long maxElapsedNanos;
+        private int largestBatchSize;
+        private int smallestBatchSize = Integer.MAX_VALUE;
+
+        RemoteCallTracker(LongSupplier nanoTime) {
+            this.nanoTime = nanoTime;
+        }
+
+        <T> T call(int itemCount, Callable<T> remoteCall) throws Exception {
+            count++;
+            items += itemCount;
+            largestBatchSize = Math.max(largestBatchSize, itemCount);
+            smallestBatchSize = Math.min(smallestBatchSize, itemCount);
+            long startNanos = nanoTime.getAsLong();
+            try {
+                return remoteCall.call();
+            } finally {
+                long currentElapsedNanos = nanoTime.getAsLong() - startNanos;
+                elapsedNanos += currentElapsedNanos;
+                maxElapsedNanos = Math.max(maxElapsedNanos, 
currentElapsedNanos);
+            }
+        }
+    }
+
+    static boolean isDegradableRemoteFailure(Throwable failure) {
+        if (!(failure instanceof HmsRemoteCallException)) {
+            return false;
+        }
+        for (Throwable current = failure.getCause(); current != null; current 
= current.getCause()) {
+            String className = current.getClass().getName();
+            if (className.endsWith(".TTransportException")) {

Review Comment:
   **[P1] Do not halve batches for every transport outage.** This class-name 
check makes a closed/refused/reset/EOF/timeout `TTransportException` degradable 
even though reducing the payload cannot repair the connection. With the 
defaults, one 5,000-name offset can be replayed 13 times down to size 1 within 
the 30-second budget, and each logical call sits above Hive's own 
retry/reconnect proxy and may create/taint another client. That amplifies an 
HMS outage precisely while it is unhealthy. Please restrict fallback to 
explicit frame/message/request/partition-limit signals (or a proven oversize 
transport code), and make ordinary transport failures terminate after the 
original logical attempt.



##########
fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java:
##########
@@ -240,9 +248,31 @@ public void run() throws JobException {
                     MTMVPartitionUtil.addPartition(mtmv, partitionKeyDesc);
                 }
             }
+            boolean buildContextUnderLock = Config.isNotCloudMode()

Review Comment:
   **[P1] Keep local PCT mappings atomic with their versions in mixed MVs.** 
This condition moves the entire context build outside the sorted table locks 
whenever *any* base table is MVCC. If the actual PCT table is a local 
`OlapTable`, its mapping is copied here at T1, external preload can then block, 
and the later locked `refreshLocalBaseVersions()` refreshes only versions—not 
`partitionMappings`. A local partition dropped in that window remains in the 
mapping and makes the locked version lookup fail; an added partition is omitted 
from comparison/refresh. The base code built both together under the locks. 
Please split the capture so external pins/I/O stay outside, while local PCT 
mappings and versions are rebuilt together under the sorted FE locks. Cloud 
local-only plans also always take this branch and the refresh helper is a no-op 
there, so preserve an atomic cloud capture as well. Apply the same fix to the 
analogous `PartitionsProcDir` branch and add mixed local-PCT/external-M
 VCC plus cloud local-only race tests.



##########
fe/fe-connector/fe-connector-spi/src/test/java/org/apache/doris/connector/spi/ConnectorPluginSurfaceTest.java:
##########
@@ -73,8 +73,9 @@ public void connectorApiMajorTracksTheRecordedSurfaceChange() 
throws IOException
             Assertions.assertNotNull(in, "missing connector plugin API version 
resource");
             version.load(in);
         }
-        // Write binding gained execution-capability methods in this surface 
revision. A plugin built against
-        // major 5 must be refused rather than run against a contract it did 
not compile against.
+        // Write binding gained execution-capability methods, while metadata 
access gained operation control,

Review Comment:
   **[P1] Bump the connector SPI major for this surface change.** This PR adds 
methods and types to the public connector SPI, but the API is still stamped as 
`6.0`. The policy beside `connector.plugin.api.version` requires a same-commit 
major bump for *any* SPI surface addition, and `ApiVersionGate` checks only 
major equality. As written, a plugin compiled against these new APIs is 
labelled 6.0 and can be accepted by an older 6.0 FE, then fail at first use 
with `NoSuchMethodError`/`NoClassDefFoundError`. Please bump the connector API 
major (and this assertion) to 7.0 in this commit.



##########
fe/fe-connector/fe-connector-hms/src/main/java/org/apache/doris/connector/hms/CachingHmsClient.java:
##########
@@ -226,49 +296,559 @@ public List<HmsPartitionInfo> getPartitions(String 
dbName, String tableName, Lis
             }
         }
         if (missNames != null) {
-            // Capture the invalidation generation BEFORE the delegate RPC so 
a REFRESH (flush) that races this
-            // in-flight cold-cache fetch does not get silently undone by 
re-caching the pre-refresh partitions.
-            // The pre-D2 code went through partitionsCache.get(key, loader) 
-> getWithManualLoad, which had this
-            // guard; the per-partition put must restore it 
(getTable/listPartitionNames/getTableColumnStatistics
-            // still use the guarded get path). The delegate results still 
populate the RESULT list directly,
-            // preserving the misparse->never-drop safety (only the CACHE put 
is generation-guarded).
-            long generation = partitionsCache.invalidationGeneration();
-            for (HmsPartitionInfo info : delegate.getPartitions(dbName, 
tableName, missNames)) {
-                partitionsCache.putIfNotInvalidatedSince(
-                        generation, new PartitionKey(dbName, tableName, 
info.getValues()), info);
-                result.add(info);
+            loadMissingPartitions(request, missNames, resultByIdentity);
+        }
+        List<HmsPartitionInfo> result = new ArrayList<>(partNames.size());
+        for (int i = 0; i < partNames.size(); i++) {
+            
checkOperationActivePeriodically(request.getEffectiveOperationControl(), i);
+            String name = partNames.get(i);
+            List<String> identity = HmsPartitionIdentity.fromName(name);
+            HmsPartitionInfo partition = resultByIdentity.get(identity);
+            if (partition == null) {
+                throw HmsPartitionResultException.builder(partNames.size(), 
resultByIdentity.size())
+                        .missing(name)
+                        .build();
             }
+            result.add(partition);
         }
+        checkOperationActive(request.getEffectiveOperationControl());
         return result;
     }
 
-    /**
-     * Splits a Hive partition name ("c1=a/c2=b") into its ordered values 
("a", "b"), unescaping each via
-     * Hive's {@code FileUtils} (already a hms-module dependency — {@code 
HmsEventParser} uses it). Semantics
-     * match the write path's {@code HiveWriteUtils.toPartitionValues}, so 
scan and write correlate partitions
-     * identically. Only used to build the per-partition LOOKUP key: a parse 
that diverges from the stored
-     * partition's own values just misses and re-fetches (never a 
wrong/dropped partition), so this is a
-     * hit-rate optimization, not a correctness dependency.
-     */
-    private static List<String> toPartitionValues(String partitionName) {
-        List<String> values = new ArrayList<>();
-        int start = 0;
+    private void loadMissingPartitions(HmsPartitionRequest request, 
List<String> initialMissNames,
+            Map<List<String>, HmsPartitionInfo> resultByIdentity) {
+        if (!partitionsCache.isEffectiveEnabled()) {
+            loadAndCacheMissingPartitions(
+                    request, initialMissNames, 
partitionsCache.invalidationGeneration(), resultByIdentity);
+            return;
+        }
+        for (int offset = 0; offset < initialMissNames.size(); offset += 
partitionLoadWindowSize) {

Review Comment:
   **[P2] Emit one logical event for the caller's request.** The cache splits 
one business request into `partitionLoadWindowSize` windows and each copied 
request invokes the raw loader, whose `finally` records a completed logical 
event. A cold 12,000-name call therefore increments `LogicalRequests` three 
times; if the third window fails, the single caller-visible request is reported 
as two successes plus one failure. The cache-disabled path reports the same 
call once, so success rates and requested-item metrics depend on cache 
configuration rather than business semantics. All-hit and pure-waiter calls 
emit no completed business event, and mixed hit/miss calls report only owner 
misses. Please move event ownership to the outer cache call, aggregate the 
shared business-request state, and emit exactly once while retaining 
physical-attempt counters; test all-hit, mixed, pure-waiter, and multi-window 
later-failure cases.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to