This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 84ffc3a8bb [core] Avoid unnecessary deletion vector I/O for empty
selections (#10470)
84ffc3a8bb is described below
commit 84ffc3a8bb0d3da80f7fc80d40fd1fa85caa6368
Author: sanshi <[email protected]>
AuthorDate: Fri Oct 9 21:53:01 2026 +0800
[core] Avoid unnecessary deletion vector I/O for empty selections (#10470)
---
.../paimon/operation/DataEvolutionSplitRead.java | 28 ++++++++++-
.../paimon/table/DataEvolutionFileIndexTest.java | 57 ++++++++++++++++++++++
2 files changed, 83 insertions(+), 2 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/DataEvolutionSplitRead.java
b/paimon-core/src/main/java/org/apache/paimon/operation/DataEvolutionSplitRead.java
index b053834633..ae2fc03d54 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/DataEvolutionSplitRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/DataEvolutionSplitRead.java
@@ -242,6 +242,9 @@ public class DataEvolutionSplitRead implements
SplitRead<InternalRow> {
if (fileIndexResult != null &&
!fileIndexResult.remain()) {
return new EmptyFileRecordReader<>();
}
+ if (isSelectionEmpty(file, rowRanges,
fileIndexResult)) {
+ return new EmptyFileRecordReader<>();
+ }
DeletionVectorWithRange deletionVector =
readDeletionVector(needMergeFiles,
deletionVectorFactory);
if (fileIndexResult != null
@@ -283,6 +286,12 @@ public class DataEvolutionSplitRead implements
SplitRead<InternalRow> {
.anyMatch(entry ->
!entry.result.remain())) {
return new EmptyFileRecordReader<>();
}
+ BitmapIndexResult groupSelection =
+ buildGroupSelection(needMergeFiles,
fileIndexResults);
+ if (isSelectionEmpty(
+ needMergeFiles.get(0), rowRanges,
groupSelection)) {
+ return new EmptyFileRecordReader<>();
+ }
DeletionVectorWithRange deletionVector =
readDeletionVector(needMergeFiles,
deletionVectorFactory);
if (deletionVector != null
@@ -293,9 +302,9 @@ public class DataEvolutionSplitRead implements
SplitRead<InternalRow> {
if (fileIndexResults == null) {
return new EmptyFileRecordReader<>();
}
+ groupSelection =
+ buildGroupSelection(needMergeFiles,
fileIndexResults);
}
- BitmapIndexResult groupSelection =
- buildGroupSelection(needMergeFiles,
fileIndexResults);
return createUnionReader(
needMergeFiles,
partition,
@@ -1049,6 +1058,21 @@ public class DataEvolutionSplitRead implements
SplitRead<InternalRow> {
return selection;
}
+ /** Returns whether exact bitmap candidates and the requested row ranges
are disjoint. */
+ private static boolean isSelectionEmpty(
+ DataFileMeta file,
+ @Nullable List<Range> rowRanges,
+ @Nullable FileIndexResult fileIndexResult) {
+ if (!(fileIndexResult instanceof BitmapIndexResult)) {
+ return false;
+ }
+ RoaringBitmap32 indexSelection = ((BitmapIndexResult)
fileIndexResult).get();
+ RoaringBitmap32 rangeSelection = file.toFileSelection(rowRanges);
+ return rangeSelection == null
+ ? indexSelection.isEmpty()
+ : RoaringBitmap32.and(rangeSelection,
indexSelection).isEmpty();
+ }
+
private boolean canPushDownGroupSelection(List<DataFileMeta> files) {
if (files.isEmpty()) {
return false;
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionFileIndexTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionFileIndexTest.java
index c2629f4f98..be205c1e3a 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionFileIndexTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionFileIndexTest.java
@@ -62,6 +62,7 @@ import org.apache.paimon.table.source.TableRead;
import org.apache.paimon.table.source.TableScan;
import org.apache.paimon.types.DataTypes;
import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.Range;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -583,6 +584,34 @@ public class DataEvolutionFileIndexTest extends
DataEvolutionTestBase {
assertThat(readWithFilter(table, equalF1(MISSING_F1))).isEmpty();
}
+ @Test
+ public void testSingleFileEmptySelectionSkipsBeforeReadingDeletionVector()
throws Exception {
+ Map<String, String> options = bitmapOptions("f1");
+ options.put(CoreOptions.DELETION_VECTORS_ENABLED.key(), "true");
+ FileStoreTable table = createTable("single_empty_selection_before_dv",
options);
+ writeAllColumns(table, ROW_COUNT);
+ deleteRows(table, 75);
+
+ FileStoreTable latest = getTable(identifier(table.name()));
+ DataSplit split = (DataSplit)
latest.newReadBuilder().newScan().plan().splits().get(0);
+ Path deletionVectorPath =
+ split.deletionFiles().get().stream()
+ .filter(Objects::nonNull)
+ .map(file -> new Path(file.path()))
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+ assertThat(latest.fileIO().delete(deletionVectorPath, false)).isTrue();
+
+ ReadBuilder readBuilder =
+ latest.newReadBuilder()
+ .withFilter(equalF1(f1(50)))
+ .withRowRanges(Collections.singletonList(new Range(0L,
49L)));
+ TableScan.Plan plan = readBuilder.newScan().plan();
+ assertThat(plan.splits()).hasSize(1);
+ assertThat(collect(readBuilder.newRead().executeFilter(), plan,
latest.rowType()))
+ .isEmpty();
+ }
+
@Test
public void testMergedGroupKeptWhenFilterColumnOverwritten() throws
Exception {
FileStoreTable table = createTable("overwritten",
Collections.emptyMap());
@@ -701,6 +730,34 @@ public class DataEvolutionFileIndexTest extends
DataEvolutionTestBase {
assertThat(readWithFilter(table, equalF1(MISSING_F1))).isEmpty();
}
+ @Test
+ public void
testMergedGroupEmptySelectionSkipsBeforeReadingDeletionVector() throws
Exception {
+ Map<String, String> options = new HashMap<>();
+ options.put(CoreOptions.DELETION_VECTORS_ENABLED.key(), "true");
+ FileStoreTable table = createTable("merged_empty_selection_before_dv",
options);
+ writeSplitColumns(table, ROW_COUNT, bitmapOptions("f1"),
Collections.emptyMap());
+ deleteRows(table, 75);
+
+ FileStoreTable latest = getTable(identifier(table.name()));
+ DataSplit split = (DataSplit)
latest.newReadBuilder().newScan().plan().splits().get(0);
+ Path deletionVectorPath =
+ split.deletionFiles().get().stream()
+ .filter(Objects::nonNull)
+ .map(file -> new Path(file.path()))
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+ assertThat(latest.fileIO().delete(deletionVectorPath, false)).isTrue();
+
+ ReadBuilder readBuilder =
+ latest.newReadBuilder()
+ .withFilter(equalF1(f1(50)))
+ .withRowRanges(Collections.singletonList(new Range(0L,
49L)));
+ TableScan.Plan plan = readBuilder.newScan().plan();
+ assertThat(plan.splits()).hasSize(1);
+ assertThat(collect(readBuilder.newRead().executeFilter(), plan,
latest.rowType()))
+ .isEmpty();
+ }
+
/** Commits a deletion vector for the anchor file of the only row id group
of {@code table}. */
private void deleteRows(FileStoreTable table, long... positions) throws
Exception {
FileStoreTable latest = getTable(identifier(table.name()));