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 2f671d6e02 [core] Push partition and bucket filters into the uncached 
manifest read path (#9739)
2f671d6e02 is described below

commit 2f671d6e02772ac47e1737619c91efcacdac3e9b
Author: Dapeng Sun(孙大鹏) <[email protected]>
AuthorDate: Fri Sep 11 15:36:30 2026 +0800

    [core] Push partition and bucket filters into the uncached manifest read 
path (#9739)
---
 .../apache/paimon/manifest/ManifestEntryCache.java | 36 ++++++++++++++
 .../org/apache/paimon/manifest/ManifestFile.java   | 13 ++++-
 .../java/org/apache/paimon/utils/ObjectsCache.java |  8 ++-
 .../apache/paimon/manifest/ManifestFileTest.java   | 58 +++++++++++++++++++++-
 4 files changed, 112 insertions(+), 3 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestEntryCache.java 
b/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestEntryCache.java
index 75af92c75a..ab5b87551e 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestEntryCache.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestEntryCache.java
@@ -65,13 +65,49 @@ import static 
org.apache.paimon.manifest.ManifestEntrySerializer.totalBucketGett
 @ThreadSafe
 public class ManifestEntryCache extends ObjectsCache<Path, ManifestEntry, 
ManifestEntrySegments> {
 
+    @Nullable private final FilteredReader filteredReader;
+
     public ManifestEntryCache(
             SegmentsCache<Path> cache,
             ObjectSerializer<ManifestEntry> projectedSerializer,
             RowType formatSchema,
             FunctionWithIOException<Path, Long> fileSizeFunction,
             BiFunctionWithIOE<Path, Long, CloseableIterator<InternalRow>> 
reader) {
+        this(cache, projectedSerializer, formatSchema, fileSizeFunction, 
reader, null);
+    }
+
+    public ManifestEntryCache(
+            SegmentsCache<Path> cache,
+            ObjectSerializer<ManifestEntry> projectedSerializer,
+            RowType formatSchema,
+            FunctionWithIOException<Path, Long> fileSizeFunction,
+            BiFunctionWithIOE<Path, Long, CloseableIterator<InternalRow>> 
reader,
+            @Nullable FilteredReader filteredReader) {
         super(cache, projectedSerializer, formatSchema, fileSizeFunction, 
reader);
+        this.filteredReader = filteredReader;
+    }
+
+    /** Uncached reads skip non-matching partitions and buckets before 
decoding their stats. */
+    @Override
+    protected CloseableIterator<InternalRow> createFilteredIterator(
+            Path path, @Nullable Long fileSize, Filters<ManifestEntry> 
filters) throws IOException {
+        if (filteredReader != null && filters instanceof ManifestEntryFilters) 
{
+            ManifestEntryFilters manifestFilters = (ManifestEntryFilters) 
filters;
+            return filteredReader.read(
+                    path, fileSize, manifestFilters.partitionFilter, 
manifestFilters.bucketFilter);
+        }
+        return super.createFilteredIterator(path, fileSize, filters);
+    }
+
+    /** Reader of manifest rows which can skip entries by partition and bucket 
while decoding. */
+    @FunctionalInterface
+    public interface FilteredReader {
+        CloseableIterator<InternalRow> read(
+                Path path,
+                @Nullable Long fileSize,
+                @Nullable PartitionPredicate partitionFilter,
+                @Nullable BucketFilter bucketFilter)
+                throws IOException;
     }
 
     @Override
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestFile.java 
b/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestFile.java
index 0088744dc5..0dc99a0470 100644
--- a/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestFile.java
+++ b/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestFile.java
@@ -91,7 +91,18 @@ public class ManifestFile extends ObjectsFile<ManifestEntry> 
{
     protected ManifestEntryCache createCache(
             @Nullable SegmentsCache<Path> cache, RowType formatType) {
         return new ManifestEntryCache(
-                cache, serializer, formatType, super::fileSize, 
this::createIterator);
+                cache,
+                serializer,
+                formatType,
+                super::fileSize,
+                this::createIterator,
+                (path, fileSize, partitionFilter, bucketFilter) ->
+                        createManifestIterator(
+                                fileIO,
+                                path,
+                                ManifestEntry.MANIFEST_ROW_TYPE,
+                                partitionFilter,
+                                bucketFilter));
     }
 
     @Override
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsCache.java 
b/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsCache.java
index d978fdbbd1..4530f698e2 100644
--- a/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsCache.java
+++ b/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsCache.java
@@ -90,7 +90,7 @@ public abstract class ObjectsCache<K, V, S extends Segments> {
                 return readFromSegments(segments, filters, convertor);
             } else {
                 return readFromIterator(
-                        reader.apply(key, fileSize),
+                        createFilteredIterator(key, fileSize, filters),
                         projectedSerializer,
                         filters.readFilter(),
                         filters.readVFilter(),
@@ -99,6 +99,12 @@ public abstract class ObjectsCache<K, V, S extends Segments> 
{
         }
     }
 
+    /** Iterator for a file too large to cache; subclasses may push {@code 
filters} into it. */
+    protected CloseableIterator<InternalRow> createFilteredIterator(
+            K key, @Nullable Long fileSize, Filters<V> filters) throws 
IOException {
+        return reader.apply(key, fileSize);
+    }
+
     protected abstract <R> List<R> readFromSegments(
             S segments, Filters<V> filters, Function<V, R> convertor) throws 
IOException;
 
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileTest.java 
b/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileTest.java
index 38194fd2a8..c3a50f4ef1 100644
--- a/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileTest.java
@@ -31,6 +31,7 @@ import org.apache.paimon.fs.PositionOutputStream;
 import org.apache.paimon.fs.local.LocalFileIO;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.io.DataFileMetaWriteColsLegacySerializer;
+import org.apache.paimon.options.MemorySize;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.PartitionPredicate;
 import org.apache.paimon.schema.FileSystemSchemaManager;
@@ -40,6 +41,8 @@ import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.CloseableIterator;
 import org.apache.paimon.utils.FailingFileIO;
 import org.apache.paimon.utils.FileStorePathFactory;
+import org.apache.paimon.utils.Filter;
+import org.apache.paimon.utils.SegmentsCache;
 
 import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.Test;
@@ -48,6 +51,8 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
+import javax.annotation.Nullable;
+
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.ByteBuffer;
@@ -1224,6 +1229,11 @@ public class ManifestFileTest {
     }
 
     private ManifestFile createManifestFile(String pathStr, long 
suggestedFileSize) {
+        return createManifestFile(pathStr, suggestedFileSize, null);
+    }
+
+    private ManifestFile createManifestFile(
+            String pathStr, long suggestedFileSize, @Nullable 
SegmentsCache<Path> cache) {
         Path path = new Path(pathStr);
         FileStorePathFactory pathFactory =
                 new FileStorePathFactory(
@@ -1251,10 +1261,56 @@ public class ManifestFileTest {
                         "zstd",
                         pathFactory,
                         suggestedFileSize,
-                        null)
+                        cache)
                 .create();
     }
 
+    @Test
+    void testBucketFilterPushedDownWhenManifestExceedsCacheElementSize() 
throws Exception {
+        List<ManifestEntry> entries = generateData();
+        Set<Integer> buckets =
+                
entries.stream().map(ManifestEntry::bucket).collect(Collectors.toSet());
+        assertThat(buckets.size()).isGreaterThan(1);
+
+        // A manifest above the cache element size limit is read uncached; the 
bucket filter must
+        // still reach the Avro reader instead of being applied after decoding 
every entry.
+        SegmentsCache<Path> tinyElementCache =
+                new SegmentsCache<>(1024, MemorySize.ofMebiBytes(8), 1L);
+        ManifestFile manifestFile =
+                createManifestFile(tempDir.toString(), Long.MAX_VALUE, 
tinyElementCache);
+        List<ManifestFileMeta> metas = manifestFile.write(entries);
+        assertThat(metas).hasSize(1);
+        ManifestFileMeta meta = metas.get(0);
+
+        for (int bucket : buckets) {
+            BucketFilter bucketFilter = BucketFilter.create(false, bucket, 
null, null);
+            List<ManifestEntry> actual =
+                    manifestFile.read(
+                            meta.fileName(),
+                            meta.fileSize(),
+                            null,
+                            bucketFilter,
+                            Filter.alwaysTrue(),
+                            Filter.alwaysTrue());
+            List<ManifestEntry> expected =
+                    entries.stream()
+                            .filter(entry -> entry.bucket() == bucket)
+                            .collect(Collectors.toList());
+            assertThat(actual).isEqualTo(expected);
+        }
+
+        // Without any pushdown filter every entry must still be returned.
+        assertThat(
+                        manifestFile.read(
+                                meta.fileName(),
+                                meta.fileSize(),
+                                null,
+                                null,
+                                Filter.alwaysTrue(),
+                                Filter.alwaysTrue()))
+                .isEqualTo(entries);
+    }
+
     private ManifestFileMeta writeSingleManifest(
             ManifestFile manifestFile, List<ManifestEntry> entries) {
         List<ManifestFileMeta> manifests = manifestFile.write(entries);

Reply via email to