github-actions[bot] commented on code in PR #66473:
URL: https://github.com/apache/doris/pull/66473#discussion_r3772121355
##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -870,15 +873,24 @@ private CloseableIterable<FileScanTask>
splitFiles(TableScan scan) {
return TableScanUtil.splitFiles(scan.planFiles(),
sessionVariable.getFileSplitSize());
}
- if (isBatchMode()) {
+ if (isBatchMode() || tableLevelPushDownCount) {
// Currently iceberg batch split mode will use max split size.
// TODO: dynamic split size in batch split mode need to customize
iceberg splitter.
+ // A metadata COUNT(*) also consumes only a bounded number of
representative splits.
+ // Keep planFiles lazy for that path instead of materializing and
retaining the whole
+ // table in the statement cache.
return TableScanUtil.splitFiles(scan.planFiles(),
sessionVariable.getMaxSplitSize());
}
// Non Batch Mode
// Materialize planFiles() into a list to avoid iterating the
CloseableIterable twice.
// RISK: It will cost memory if the table is large.
+ List<FileScanTask> fileScanTaskList = getOrPlanFileScanTasks(scan, ()
-> materializeFileScanTasks(scan));
+ targetSplitSize = determineTargetFileSplitSize(fileScanTaskList);
Review Comment:
[P1] Cap ordinary Iceberg task retention
Batch, explicit-size, count, and generic metadata-table paths now preserve
streaming, but the ordinary non-batch path still materializes every
`FileScanTask` and retains the unweighted list until statement cleanup. After
`TableScanUtil.splitFiles`/`createIcebergSplit` build the execution splits, the
cached SDK task wrappers plus residual/schema/spec state remain additionally
owned even though this node no longer needs them, and a large plan has no task
or byte cap; before this change the materialized list was method-local. Please
use a cumulative retention limit with an oversize direct-consumption fallback,
or cache a compact converted representation, and cover a large non-batch plan.
##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -1416,14 +1522,30 @@ private List<Split>
doGetPositionDeletesSystemTableSplits() throws UserException
long startTime = System.currentTimeMillis();
scan = scan.planWith(source.getCatalog().getThreadPoolWithPreAuth());
- try (CloseableIterable<ScanTask> scanTasks = scan.planFiles()) {
- for (ScanTask task : scanTasks) {
- if (!(task instanceof PositionDeletesScanTask)) {
- throw new UserException("Unexpected Iceberg
position_deletes scan task: " + task);
+ BatchScan plannedScan = scan;
+ Snapshot snapshot = plannedScan.snapshot();
+ IcebergScanTaskCacheKey<PositionDeletesScanTask> cacheKey = new
IcebergScanTaskCacheKey<>(
+ source.getCatalog().getId(),
+ source.getTargetTable().getId(),
+ snapshot == null ? null : snapshot.snapshotId(),
+ plannedScan.schema().schemaId(),
+ plannedScan.filter(),
+ plannedScan.isCaseSensitive(),
+ PositionDeletesScanTask.class.getName());
+ try {
+ positionDeleteTasks = getOrLoadExternalScanTasks(cacheKey, () -> {
+ List<PositionDeletesScanTask> tasks = new ArrayList<>();
Review Comment:
[P1] Keep position-delete planning bounded
`position_deletes` bypasses the generic system-table streaming helper: this
call first collects every `PositionDeletesScanTask` into the unweighted
statement cache, then splits and converts the complete list into native
`IcebergSplit` ranges. Those ranges carry the file, offset, partition, and
deletion-vector fields needed by BE, but the original SDK task graph remains
owned until statement cleanup, so a table with many delete files keeps both
representations. Please stream this special path through conversion where
possible, or apply a bounded compact cache/oversize fallback, and cover a
many-delete-file plan.
##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hudi/source/HudiScanNode.java:
##########
@@ -380,29 +383,65 @@ private List<HivePartition>
getPrunedPartitions(HoodieTableMetaClient metaClient
private List<Split> getIncrementalSplits() {
long startTime = System.currentTimeMillis();
- if (canUseNativeReader()) {
- List<Split> splits = incrementalRelation.collectSplits();
- noLogsSplitNum.addAndGet(splits.size());
+ try {
+ if (canUseNativeReader()) {
+ List<Split> splits = incrementalRelation.collectSplits();
+ noLogsSplitNum.addAndGet(splits.size());
+ return splits;
+ }
+ Option<String[]> partitionColumns =
hudiClient.getTableConfig().getPartitionFields();
+ List<String> partitionNames = partitionColumns.isPresent()
+ ? Arrays.asList(partitionColumns.get()) :
Collections.emptyList();
+ List<Split> splits =
incrementalRelation.collectFileSlices().stream()
+ .map(fileSlice -> generateHudiSplit(fileSlice,
+ HudiPartitionUtils.parsePartitionValues(
+ partitionNames,
fileSlice.getPartitionPath()),
+ incrementalRelation.getEndTs()))
+ .collect(Collectors.toList());
+ if (!sessionVariable.isForceJniScanner()) {
+ splits.stream()
+ .map(split -> (HudiSplit) split)
+ .filter(split -> split.getHudiDeltaLogs().isEmpty())
+ .forEach(split -> noLogsSplitNum.incrementAndGet());
+ }
+ return splits;
+ } finally {
if (getSummaryProfile() != null) {
getSummaryProfile().addExternalTableGetFileScanTasksTime(System.currentTimeMillis()
- startTime);
}
- return splits;
}
- Option<String[]> partitionColumns =
hudiClient.getTableConfig().getPartitionFields();
- List<String> partitionNames = partitionColumns.isPresent() ?
Arrays.asList(partitionColumns.get())
- : Collections.emptyList();
- List<Split> splits = incrementalRelation.collectFileSlices().stream()
- .map(fileSlice -> generateHudiSplit(fileSlice,
-
HudiPartitionUtils.parsePartitionValues(partitionNames,
fileSlice.getPartitionPath()),
- incrementalRelation.getEndTs()))
- .collect(Collectors.toList());
- if (getSummaryProfile() != null) {
-
getSummaryProfile().addExternalTableGetFileScanTasksTime(System.currentTimeMillis()
- startTime);
+ }
+
+ private void getPartitionSplits(HivePartition partition, List<Split>
splits) throws Exception {
+ getPartitionSplits(partition, splits, true);
+ }
+
+ private void getPartitionSplits(
+ HivePartition partition, List<Split> splits, boolean
useStatementCache) throws Exception {
+ List<HudiSplit> plannedSplits;
+ if (useStatementCache) {
+ HudiFileScanTaskCacheKey cacheKey = new HudiFileScanTaskCacheKey(
+ hmsTable.getCatalog().getId(), hmsTable.getId(),
queryInstant,
+ canUseNativeReader(),
sessionVariable.isEnableRuntimeFilterPartitionPrune(), partition);
+ plannedSplits = getOrLoadExternalScanTasks(
+ cacheKey, () -> planPartitionSplits(partition));
Review Comment:
[P1] Bound ordinary Hudi task retention
The new bypasses cover batch and incremental planning, but an ordinary
snapshot scan below the partition-count batch threshold still stores every
planned `HudiSplit` in the unweighted statement cache and then deep-copies
every split for the consumer. A large single-partition MOR table therefore
keeps the complete source graph plus per-file copies (including copied
schema/log lists) until statement cleanup even when no alias reuses it;
previously there was only the directly planned graph. Please add a cumulative
task/byte cap with an oversize direct-use fallback, or cache a compact
representation, and exercise a many-file non-batch snapshot scan.
##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/source/PaimonScanNode.java:
##########
@@ -743,52 +747,235 @@ public List<org.apache.paimon.table.source.Split>
getPaimonSplitFromAPI() throws
if (PaimonScanParams.isPinnedEmptyScan(resolvedOptions)) {
return Collections.emptyList();
}
- Optional<Long> fileCreationTime =
PaimonScanParams.getPinnedFileCreationTime(resolvedOptions);
- if (fileCreationTime.isPresent()) {
- if (!(paimonTable instanceof FileStoreTable)) {
- throw new UserException("Paimon file-creation OPTIONS
require a data table.");
+ int[] projectedColumns = new int[0];
+ if
(!PaimonScanParams.getPinnedFileCreationTime(resolvedOptions).isPresent()) {
+ List<String> fieldNames =
paimonTable.rowType().getFieldNames();
+ projectedColumns = desc.getSlots().stream().mapToInt(
+ slot -> getFieldIndex(fieldNames,
slot.getColumn().getName()))
+ .toArray();
+ if (Arrays.stream(projectedColumns).anyMatch(index -> index <
0)) {
+ throw new UserException("Paimon scan schema does not
contain all bound Doris columns.");
}
- FileStoreTable fileStoreTable = (FileStoreTable) paimonTable;
- SnapshotReader snapshotReader =
fileStoreTable.newSnapshotReader()
- .withMode(ScanMode.ALL)
- .withSnapshot(Long.parseLong(
-
paimonTable.options().get(CoreOptions.SCAN_SNAPSHOT_ID.key())))
- .withManifestEntryFilter(entry ->
- entry.file().creationTimeEpochMillis() >=
fileCreationTime.get());
- preserveBatchScanFilters(fileStoreTable, snapshotReader);
- if (predicates != null) {
- predicates.forEach(snapshotReader::withFilter);
- }
- return snapshotReader.read().splits();
- }
- List<String> fieldNames = paimonTable.rowType().getFieldNames();
- int[] projected = desc.getSlots().stream().mapToInt(
- slot -> getFieldIndex(fieldNames,
slot.getColumn().getName()))
- .toArray();
- if (Arrays.stream(projected).anyMatch(index -> index < 0)) {
- throw new UserException("Paimon scan schema does not contain
all bound Doris columns.");
}
- ReadBuilder readBuilder = paimonTable.newReadBuilder();
- TableScan scan = readBuilder.withFilter(predicates)
- .withProjection(projected)
- .newScan();
- PaimonMetricRegistry registry = new PaimonMetricRegistry();
- if (scan instanceof InnerTableScan) {
- scan = ((InnerTableScan) scan).withMetricRegistry(registry);
- }
- List<org.apache.paimon.table.source.Split> splits =
scan.plan().splits();
- PaimonScanMetricsReporter.report(source.getTargetTable(),
paimonTable.name(), registry);
- if (!registry.getAllGroups().isEmpty()) {
- registry.clear();
+ int[] projected = projectedColumns;
+ PaimonSplitTaskCacheKey cacheKey = createPaimonSplitTaskCacheKey(
+ relationSnapshot, paimonTable, resolvedOptions,
+ scanParams != null && scanParams.incrementalRead()
+ ? getIncrReadParams() : Collections.emptyMap(),
+ projected);
+ List<PaimonSerializedScanTask> serializedSplits;
+ try {
+ serializedSplits = getOrLoadExternalScanTasks(cacheKey,
+ () -> serializePaimonSplitsWithinLimit(
Review Comment:
[P2] Stop encoding once the generation budget is spent
The serializer sees the fixed 16 MiB per-node limit, not this cache
generation's remaining allowance. If one key has already retained 10 MiB, a
distinct 10 MiB plan is therefore completely Java-serialized here; only
afterward does the weighted cache reject/remove it against the remaining 6 MiB,
return the byte arrays, and make this caller deserialize every task. Each later
distinct alias repeats that throwaway encode/decode and transient allocation,
whereas the individual-oversize path stops early and returns the original
planned list. Please reserve/expose the remaining budget to the loader, or
report non-retention so the raw tasks can be consumed directly, and test two
individually fitting keys whose sum exceeds the cap.
##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hive/source/HiveScanNode.java:
##########
@@ -320,8 +323,25 @@ private void
getFileSplitByPartitions(HiveExternalMetaCache cache, List<HivePart
}
} else {
boolean withCache = Config.max_external_file_cache_num > 0;
- fileCaches = cache.getFilesByPartitions(partitions, withCache,
partitions.size() > 1,
- directoryLister, hmsTable);
+ if (isBatchMode) {
+ // Batch mode bounds FE memory by retaining only the
partitions currently in flight.
+ // Keeping every completed partition in the statement cache
would materialize the
+ // full scan again and defeat that bound.
+ fileCaches = cache.getFilesByPartitions(partitions, withCache,
partitions.size() > 1,
+ directoryLister, hmsTable);
+ } else {
+ HiveFileScanTaskCacheKey cacheKey = new
HiveFileScanTaskCacheKey(
+ hmsTable.getCatalog().getId(), hmsTable.getId(),
partitions);
+ try {
+ fileCaches = getOrLoadExternalScanTasks(cacheKey,
+ () -> cache.getFilesByPartitions(partitions,
withCache, partitions.size() > 1,
Review Comment:
[P1] Honor the disabled file-cache memory fence
When `max_external_file_cache_num <= 0`, `withCache` deliberately disables
the global Hive file cache, but this unweighted statement-cache call still
retains the full `FileCacheValue`/`HiveFileStatus` graph until statement
cleanup. Under the default 1024-partition threshold, an unpartitioned table
remains non-batch regardless of file count, so an arbitrarily large listing
stays owned even after it has been converted into independent `FileSplit`s;
before this change the method-local list could be collected. Please bypass
statement retention when the global file cache is disabled, or apply a real
cumulative file/status weight cap with an oversize fallback, and cover a
one-partition many-file scan under the disabled-cache setting.
--
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]