924060929 commented on code in PR #69133:
URL: https://github.com/apache/doris/pull/69133#discussion_r4282142009


##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveScanPlanProvider.java:
##########
@@ -248,6 +247,169 @@ public boolean supportsBatchScan(ConnectorSession 
session, ConnectorTableHandle
         return partKeyNames != null && !partKeyNames.isEmpty() && 
!hiveHandle.isTransactional();
     }
 
+    @Override
+    public long streamingSplitEstimate(ConnectorSession session, 
ConnectorTableHandle handle,
+            Optional<ConnectorExpression> filter, boolean countPushdown) {
+        HiveTableHandle hiveHandle = (HiveTableHandle) handle;
+        if (hiveHandle.isTransactional()
+                || 
Boolean.FALSE.equals(session.getProperty("enable_external_table_batch_mode", 
Boolean.class))) {
+            return -1;
+        }
+        int fileThreshold = sessionInt(session, "num_files_in_batch_mode", 
DEFAULT_BATCH_THRESHOLD);
+        List<HmsPartitionInfo> partitions = hiveHandle.getPrunedPartitions();
+        long fileCount = 0;
+        int partitionCount = partitions == null ? 1 : partitions.size();
+        if (hiveHandle.getPartitionKeyNames() == null || 
hiveHandle.getPartitionKeyNames().isEmpty()) {
+            fileCount = 
fileListingCache.cachedFileCount(hiveHandle.getDbName(), 
hiveHandle.getTableName(),
+                    hiveHandle.getLocation(), Collections.emptyList());
+        } else if (partitions == null) {
+            fileCount = -1;
+        } else {
+            for (HmsPartitionInfo partition : partitions) {
+                long count = 
fileListingCache.cachedFileCount(hiveHandle.getDbName(), 
hiveHandle.getTableName(),
+                        partition.getLocation(), partition.getValues());
+                if (count < 0) {
+                    fileCount = -1;
+                    break;
+                }
+                fileCount += count;
+            }
+        }
+        // Unknown file counts must not trigger a full listing on the planning 
thread. Use a
+        // nonzero concurrency hint and let the lazy source perform the 
listing asynchronously.
+        if (fileCount < 0) {
+            return Math.max(DEFAULT_BATCH_THRESHOLD, fileThreshold);
+        }
+        int partitionThreshold = sessionInt(session, 
"num_partitions_in_batch_mode", DEFAULT_BATCH_THRESHOLD);
+        boolean partitionBatch = partitionThreshold > 0 && partitionCount >= 
partitionThreshold;
+        return fileCount >= fileThreshold || partitionBatch ? fileCount : -1;

Review Comment:
   A related implementation simplification: keep partition-batch eligibility in 
the engine's existing `shouldUseBatchMode()` instead of also calculating it in 
Hive's `streamingSplitEstimate()`. The two currently use different 
representations of the partition selection (`selectedPartitions` versus the 
handle's pruned partitions). When composing partition batching with streaming, 
let the engine select disjoint request scopes and let Hive enumerate each 
requested scope lazily. Remove the duplicated Hive partition-threshold decision 
as part of that routing change, rather than deleting the condition in isolation 
and accidentally restoring eager batch materialization. This is part of the fix 
discussed above, not a separate regression finding.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java:
##########
@@ -2239,36 +2266,80 @@ private void startStreamingSplit(QueryFinishCleanup 
queryFinishCleanup) {
         final ConnectorTableHandle handle = currentHandle;
         final ConnectorScanPlanProvider scanProvider = resolveScanProvider();
         queryFinishCleanup.bind(scanProvider);
+        long[] partitionCounts = displayPartitionCounts(selectedPartitions);
+        if (partitionCounts != null) {
+            selectedPartitionNum = partitionCounts[0];
+            totalPartitionNum = partitionCounts[1];
+        }
+        List<String> requiredPartitions = 
resolveRequiredPartitions(selectedPartitions,
+                onPluginClassLoader(scanProvider, 
scanProvider::ignorePartitionPruneShortCircuit));
+        if (requiredPartitions != null && requiredPartitions.isEmpty()) {
+            splitAssignment.finishSchedule();
+            return;
+        }
+        final ConnectorScanRequest request = 
ConnectorScanRequest.builder(handle, columns)
+                .filter(remainingFilter)
+                .requiredPartitions(requiredPartitions)
+                .build();
+        SummaryProfile summaryProfile = 
SummaryProfile.getSummaryProfile(ConnectContext.get());
+        RuntimeProfile generationProfile = new RuntimeProfile("Split 
Generation " + getId());
+        generationProfile.addInfoString("BatchSize", 
String.valueOf(sessionVariable.getSplitBatchSize()));
+        generationProfile.addInfoString("QueueCapacity", 
String.valueOf(sessionVariable.getSplitQueueCapacity()));
+        Counter generatedSplits = 
generationProfile.addCounter("GeneratedSplits", TUnit.UNIT,
+                RuntimeProfile.ROOT_COUNTER);
+        generatedSplits.setLevel(1);
+        if (summaryProfile != null) {
+            summaryProfile.getExecutionSummary().addChild(generationProfile, 
true);
+        }
         Executor scheduleExecutor = 
Env.getCurrentEnv().getExtMetaCacheMgr().getScheduleExecutor();
-        CompletableFuture.runAsync(() -> {
-            ConnectorSplitSource source = null;
+        new SplitProducer(splitAssignment, scheduleExecutor, new 
SplitProducer.BatchSource() {

Review Comment:
   Refactoring suggestion: now that both async paths use `SplitProducer`, share 
their common preparation and completion flow instead of maintaining two 
complete pipelines in `startSplit()` and `startStreamingSplit()`. Both prepare 
projection/filter state, pin the snapshot and rewrite scope, build a request, 
start producers, collect profiles, and finish scheduling. The meaningful 
differences are the source factory and producer count. The existing 
`SubmittedTaskFinalizer` can cover one producer as well as multiple producers; 
no additional lifecycle interface is needed. Preserve the current 
initialization order and prune-to-zero/capability semantics. Some duplication 
predates this PR; this is an opportunity to consolidate the paths while 
changing their common execution mechanism, not a new correctness finding.



##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveScanPlanProvider.java:
##########
@@ -248,6 +247,187 @@ public boolean supportsBatchScan(ConnectorSession 
session, ConnectorTableHandle
         return partKeyNames != null && !partKeyNames.isEmpty() && 
!hiveHandle.isTransactional();
     }
 
+    @Override
+    public long streamingSplitEstimate(ConnectorSession session, 
ConnectorTableHandle handle,
+            Optional<ConnectorExpression> filter, boolean countPushdown) {
+        HiveTableHandle hiveHandle = (HiveTableHandle) handle;
+        if (hiveHandle.isTransactional()
+                || 
Boolean.FALSE.equals(session.getProperty("enable_external_table_batch_mode", 
Boolean.class))) {
+            return -1;
+        }
+        int fileThreshold = sessionInt(session, "num_files_in_batch_mode", 
DEFAULT_BATCH_THRESHOLD);
+        long fileCount = cachedFileCount(hiveHandle);
+        // Unknown file counts must not trigger a full listing on the planning 
thread. Use a
+        // nonzero concurrency hint and let the lazy source perform the 
listing asynchronously.
+        if (fileCount < 0) {
+            return Math.max(DEFAULT_BATCH_THRESHOLD, fileThreshold);
+        }
+        List<HmsPartitionInfo> partitions = hiveHandle.getPrunedPartitions();
+        int partitionCount = partitions == null ? 1 : partitions.size();
+        int partitionThreshold = sessionInt(session, 
"num_partitions_in_batch_mode", DEFAULT_BATCH_THRESHOLD);
+        boolean partitionBatch = partitionThreshold > 0 && partitionCount >= 
partitionThreshold;
+        return fileCount >= fileThreshold || partitionBatch ? fileCount : -1;
+    }
+
+    private long cachedFileCount(HiveTableHandle handle) {
+        List<String> partitionKeys = handle.getPartitionKeyNames();
+        if (partitionKeys == null || partitionKeys.isEmpty()) {
+            return fileListingCache.cachedFileCount(handle.getDbName(), 
handle.getTableName(),
+                    handle.getLocation(), Collections.emptyList());
+        }
+        List<HmsPartitionInfo> partitions = handle.getPrunedPartitions();
+        if (partitions == null) {
+            return -1;
+        }
+        long fileCount = 0;
+        for (HmsPartitionInfo partition : partitions) {
+            long count = fileListingCache.cachedFileCount(handle.getDbName(), 
handle.getTableName(),
+                    partition.getLocation(), partition.getValues());
+            if (count < 0) {
+                return -1;
+            }
+            fileCount += count;
+        }
+        return fileCount;
+    }
+
+    @Override
+    public ConnectorSplitSource streamSplits(ConnectorSession session, 
ConnectorTableHandle handle,
+            List<ConnectorColumnHandle> columns, Optional<ConnectorExpression> 
filter, long limit) {
+        return streamSplits(session, ConnectorScanRequest.builder(handle, 
columns).filter(filter).limit(limit).build());
+    }
+
+    @Override
+    public ConnectorSplitSource streamSplits(ConnectorSession session, 
ConnectorScanRequest request) {
+        HiveTableHandle hiveHandle = (HiveTableHandle) 
request.getTableHandle();
+        if (hiveHandle.isTransactional()) {
+            throw new DorisConnectorException("Transactional Hive scans 
require snapshot-aware planning");
+        }
+        return new HiveSplitSource(session, hiveHandle, 
request.getRequiredPartitions());
+    }
+
+    private static int sessionInt(ConnectorSession session, String name, int 
defaultValue) {
+        Integer value = session.getProperty(name, Integer.class);
+        return value == null ? defaultValue : value;
+    }
+
+    private final class HiveSplitSource implements ConnectorSplitSource {
+        private final HiveTableHandle handle;
+        private final FileSystem fs;
+        private final HiveFileFormat tableFormat;
+        private final boolean isLzo;
+        private final boolean splittable;
+        private final long targetSplitSize;
+        private final int partitionBatchSize;
+        private Iterator<PartitionScanInfo> partitions = 
Collections.emptyIterator();
+        private Iterator<HiveFileStatus> files = Collections.emptyIterator();
+        private Iterator<ConnectorScanRange> ranges = 
Collections.emptyIterator();
+        private List<String> partitionNames;
+        private PartitionScanInfo partition;
+        private int nextPartition;
+        private boolean unpartitionedInitialized;
+        private volatile boolean closed;
+
+        HiveSplitSource(ConnectorSession session, HiveTableHandle handle, 
List<String> requiredPartitions) {
+            this.handle = handle;
+            this.partitionNames = requiredPartitions.isEmpty() ? null : 
requiredPartitions;
+            this.fs = storage().getFileSystem(session);
+            this.tableFormat = HiveFileFormat.detect(handle.getInputFormat(), 
handle.getSerializationLib(),

Review Comment:
   Refactoring suggestion: reuse this partition-scoped lazy enumeration in 
Hive's synchronous `planScanForPartitionBatch()` as well, by consuming the 
source into a list. `doPlanScanForPartitionBatch()` currently maintains another 
copy of partition resolution, format/LZO setup, file listing, and split 
traversal. One enumeration implementation with streaming and collecting 
consumers would keep those rules consistent, without another SPI method. The 
existing `splitFile()` adapter over `splitFileRanges()` already follows this 
pattern. Keep the initial consolidation limited to non-ACID partition batches: 
whole-scan `planScan()` also tracks `PlanCompleteness` to avoid memoizing 
incomplete results, so that signal must be preserved before sharing its entire 
path. Verify eager/lazy equivalence for partition subsets, LZO, empty files and 
tolerated directory-listing failures.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/split/SplitAssignment.java:
##########
@@ -70,12 +76,29 @@ public SplitAssignment(
             Map<String, String> locationProperties,
             List<String> pathPartitionKeys,
             boolean fileCacheAdmission) {
+        this(backendPolicy, splitGenerator, splitToScanRange, 
locationProperties, pathPartitionKeys,
+                fileCacheAdmission, 128, MAX_QUEUED_BATCHES);

Review Comment:
   Cleanup suggestion: remove the six-argument constructor and its 
`MAX_QUEUED_BATCHES` fallback, and pass explicit limits in its remaining test 
caller. The only production construction site, `FileQueryScanNode`, now 
supplies the session values; this overload is used only by 
`SplitAssignmentTest`. Keeping it introduces two implicit defaults: 128/10000 
here versus the production session defaults of 128/32. There is no remaining 
production caller to preserve, and explicit test limits avoid retaining a 
misleading compatibility entry point. This is not a claim that the current 
production queue ignores its configured capacity.



-- 
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