This is an automated email from the ASF dual-hosted git repository.

junhao 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 02c54f591 [core] Introduce bucket entries to optimize Spark compact 
(#4162)
02c54f591 is described below

commit 02c54f59128d0861f3d7e0b0af43ce70abeb88d4
Author: Jingsong Lee <[email protected]>
AuthorDate: Wed Sep 11 14:15:48 2024 +0800

    [core] Introduce bucket entries to optimize Spark compact (#4162)
---
 docs/content/maintenance/system-tables.md          |  16 ++
 .../org/apache/paimon/manifest/BucketEntry.java    | 143 +++++++++++
 .../paimon/operation/AbstractFileStoreScan.java    |  18 +-
 .../org/apache/paimon/operation/FileStoreScan.java |   3 +
 .../table/source/snapshot/SnapshotReader.java      |   3 +
 .../table/source/snapshot/SnapshotReaderImpl.java  |   6 +
 .../apache/paimon/table/system/AuditLogTable.java  |   6 +
 .../apache/paimon/table/system/BucketsTable.java   | 277 +++++++++------------
 ...{BucketsTable.java => CompactBucketsTable.java} |  16 +-
 .../paimon/table/system/PartitionsTable.java       |   1 -
 .../paimon/table/system/SystemTableLoader.java     |   2 +
 .../paimon/table/system/BucketsTableTest.java      |  80 ++++++
 .../flink/compact/MultiAwareBucketTableScan.java   |   9 +-
 .../flink/sink/BucketsRowChannelComputer.java      |   4 +-
 .../CombinedTableCompactorSourceBuilder.java       |   4 +-
 .../flink/source/CompactorSourceBuilder.java       |  27 +-
 .../source/operator/MultiTablesReadOperator.java   |  10 +-
 .../paimon/spark/procedure/CompactProcedure.java   |   5 +-
 18 files changed, 424 insertions(+), 206 deletions(-)

diff --git a/docs/content/maintenance/system-tables.md 
b/docs/content/maintenance/system-tables.md
index cc542697a..f19c75756 100644
--- a/docs/content/maintenance/system-tables.md
+++ b/docs/content/maintenance/system-tables.md
@@ -309,6 +309,22 @@ SELECT * FROM my_table$partitions;
 */
 ```
 
+### Buckets Table
+
+You can query the bucket files of the table.
+
+```sql
+SELECT * FROM my_table$buckets;
+
+/*
++---------------+--------+----------------+--------------------+--------------------+------------------------+
+|  partition    | bucket |   record_count |  file_size_in_bytes|          
file_count|        last_update_time|
++---------------+--------+----------------+--------------------+--------------------+------------------------+
+|  [1]          |   0    |           1    |             645    |               
 1   | 2024-06-24 10:25:57.400|
++---------------+--------+----------------+--------------------+--------------------+------------------------+
+*/
+```
+
 ## Global System Table
 
 Global system tables contain the statistical information of all the tables 
exists in paimon. For convenient of searching, we create a reference system 
database called `sys`.
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/manifest/BucketEntry.java 
b/paimon-core/src/main/java/org/apache/paimon/manifest/BucketEntry.java
new file mode 100644
index 000000000..3e93bc8cc
--- /dev/null
+++ b/paimon-core/src/main/java/org/apache/paimon/manifest/BucketEntry.java
@@ -0,0 +1,143 @@
+/*
+ * 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.paimon.manifest;
+
+import org.apache.paimon.annotation.Public;
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.utils.Pair;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/** Entry representing a bucket. */
+@Public
+public class BucketEntry {
+
+    private final BinaryRow partition;
+    private final int bucket;
+    private final long recordCount;
+    private final long fileSizeInBytes;
+    private final long fileCount;
+    private final long lastFileCreationTime;
+
+    public BucketEntry(
+            BinaryRow partition,
+            int bucket,
+            long recordCount,
+            long fileSizeInBytes,
+            long fileCount,
+            long lastFileCreationTime) {
+        this.partition = partition;
+        this.bucket = bucket;
+        this.recordCount = recordCount;
+        this.fileSizeInBytes = fileSizeInBytes;
+        this.fileCount = fileCount;
+        this.lastFileCreationTime = lastFileCreationTime;
+    }
+
+    public BinaryRow partition() {
+        return partition;
+    }
+
+    public int bucket() {
+        return bucket;
+    }
+
+    public long recordCount() {
+        return recordCount;
+    }
+
+    public long fileSizeInBytes() {
+        return fileSizeInBytes;
+    }
+
+    public long fileCount() {
+        return fileCount;
+    }
+
+    public long lastFileCreationTime() {
+        return lastFileCreationTime;
+    }
+
+    public BucketEntry merge(BucketEntry entry) {
+        return new BucketEntry(
+                partition,
+                bucket,
+                recordCount + entry.recordCount,
+                fileSizeInBytes + entry.fileSizeInBytes,
+                fileCount + entry.fileCount,
+                Math.max(lastFileCreationTime, entry.lastFileCreationTime));
+    }
+
+    public static BucketEntry fromManifestEntry(ManifestEntry entry) {
+        PartitionEntry partitionEntry = 
PartitionEntry.fromManifestEntry(entry);
+        return new BucketEntry(
+                partitionEntry.partition(),
+                entry.bucket(),
+                partitionEntry.recordCount(),
+                partitionEntry.fileSizeInBytes(),
+                partitionEntry.fileCount(),
+                partitionEntry.lastFileCreationTime());
+    }
+
+    public static Collection<BucketEntry> merge(Collection<ManifestEntry> 
fileEntries) {
+        Map<Pair<BinaryRow, Integer>, BucketEntry> buckets = new HashMap<>();
+        for (ManifestEntry entry : fileEntries) {
+            BucketEntry bucketEntry = fromManifestEntry(entry);
+            buckets.compute(
+                    Pair.of(entry.partition(), entry.bucket()),
+                    (part, old) -> old == null ? bucketEntry : 
old.merge(bucketEntry));
+        }
+        return buckets.values();
+    }
+
+    public static void merge(
+            Collection<BucketEntry> from, Map<Pair<BinaryRow, Integer>, 
BucketEntry> to) {
+        for (BucketEntry entry : from) {
+            to.compute(
+                    Pair.of(entry.partition(), entry.bucket),
+                    (part, old) -> old == null ? entry : old.merge(entry));
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        BucketEntry that = (BucketEntry) o;
+        return recordCount == that.recordCount
+                && fileSizeInBytes == that.fileSizeInBytes
+                && fileCount == that.fileCount
+                && lastFileCreationTime == that.lastFileCreationTime
+                && bucket == that.bucket
+                && Objects.equals(partition, that.partition);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                partition, bucket, recordCount, fileSizeInBytes, fileCount, 
lastFileCreationTime);
+    }
+}
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
index f1a95aec9..824f90797 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
@@ -22,6 +22,7 @@ import org.apache.paimon.CoreOptions;
 import org.apache.paimon.Snapshot;
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.manifest.FileEntry;
 import org.apache.paimon.manifest.ManifestCacheFilter;
 import org.apache.paimon.manifest.ManifestEntry;
@@ -55,7 +56,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -259,18 +259,28 @@ public abstract class AbstractFileStoreScan implements 
FileStoreScan {
     public List<PartitionEntry> readPartitionEntries() {
         List<ManifestFileMeta> manifests = readManifests().getRight();
         Map<BinaryRow, PartitionEntry> partitions = new ConcurrentHashMap<>();
-        // Can be executed in disorder
-        ThreadPoolExecutor executor = 
getExecutorService(scanManifestParallelism);
         Consumer<ManifestFileMeta> processor =
                 m ->
                         PartitionEntry.merge(
                                 PartitionEntry.merge(readManifestFileMeta(m)), 
partitions);
-        randomlyOnlyExecute(executor, processor, manifests);
+        randomlyOnlyExecute(getExecutorService(scanManifestParallelism), 
processor, manifests);
         return partitions.values().stream()
                 .filter(p -> p.fileCount() > 0)
                 .collect(Collectors.toList());
     }
 
+    @Override
+    public List<BucketEntry> readBucketEntries() {
+        List<ManifestFileMeta> manifests = readManifests().getRight();
+        Map<Pair<BinaryRow, Integer>, BucketEntry> buckets = new 
ConcurrentHashMap<>();
+        Consumer<ManifestFileMeta> processor =
+                m -> 
BucketEntry.merge(BucketEntry.merge(readManifestFileMeta(m)), buckets);
+        randomlyOnlyExecute(getExecutorService(scanManifestParallelism), 
processor, manifests);
+        return buckets.values().stream()
+                .filter(p -> p.fileCount() > 0)
+                .collect(Collectors.toList());
+    }
+
     private Pair<Snapshot, List<ManifestEntry>> doPlan() {
         long started = System.nanoTime();
         Pair<Snapshot, List<ManifestFileMeta>> snapshotListPair = 
readManifests();
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreScan.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreScan.java
index 427e958e8..b1e0a2bd9 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreScan.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreScan.java
@@ -21,6 +21,7 @@ package org.apache.paimon.operation;
 import org.apache.paimon.Snapshot;
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.manifest.FileKind;
 import org.apache.paimon.manifest.ManifestCacheFilter;
 import org.apache.paimon.manifest.ManifestEntry;
@@ -98,6 +99,8 @@ public interface FileStoreScan {
 
     List<PartitionEntry> readPartitionEntries();
 
+    List<BucketEntry> readBucketEntries();
+
     default List<BinaryRow> listPartitions() {
         return readPartitionEntries().stream()
                 .map(PartitionEntry::partition)
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
index 96605ceeb..13a458d4a 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReader.java
@@ -21,6 +21,7 @@ package org.apache.paimon.table.source.snapshot;
 import org.apache.paimon.Snapshot;
 import org.apache.paimon.consumer.ConsumerManager;
 import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.PartitionEntry;
 import org.apache.paimon.metrics.MetricRegistry;
@@ -88,6 +89,8 @@ public interface SnapshotReader {
 
     List<PartitionEntry> partitionEntries();
 
+    List<BucketEntry> bucketEntries();
+
     /** Result plan of this scan. */
     interface Plan extends TableScan.Plan {
 
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
index 47e697130..03b6144be 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/source/snapshot/SnapshotReaderImpl.java
@@ -27,6 +27,7 @@ import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.index.IndexFileHandler;
 import org.apache.paimon.index.IndexFileMeta;
 import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.manifest.FileKind;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.PartitionEntry;
@@ -335,6 +336,11 @@ public class SnapshotReaderImpl implements SnapshotReader {
         return scan.readPartitionEntries();
     }
 
+    @Override
+    public List<BucketEntry> bucketEntries() {
+        return scan.readBucketEntries();
+    }
+
     @Override
     public Plan readChanges() {
         withMode(ScanMode.DELTA);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java 
b/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
index 50028d890..38457f29b 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/system/AuditLogTable.java
@@ -27,6 +27,7 @@ import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.fs.Path;
+import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.manifest.IndexManifestEntry;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.ManifestFileMeta;
@@ -359,6 +360,11 @@ public class AuditLogTable implements DataTable, 
ReadonlyTable {
         public List<PartitionEntry> partitionEntries() {
             return snapshotReader.partitionEntries();
         }
+
+        @Override
+        public List<BucketEntry> bucketEntries() {
+            return snapshotReader.bucketEntries();
+        }
     }
 
     private class AuditLogBatchScan implements DataTableScan {
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java 
b/paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java
index ddf38b7b2..e4d0bab66 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java
@@ -18,219 +18,150 @@
 
 package org.apache.paimon.table.system;
 
-import org.apache.paimon.CoreOptions;
-import org.apache.paimon.Snapshot;
 import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.data.Timestamp;
 import org.apache.paimon.disk.IOManager;
-import org.apache.paimon.fs.FileIO;
-import org.apache.paimon.fs.Path;
-import org.apache.paimon.io.DataFileMeta;
-import org.apache.paimon.io.DataFileMetaSerializer;
-import org.apache.paimon.manifest.IndexManifestEntry;
-import org.apache.paimon.manifest.ManifestEntry;
-import org.apache.paimon.manifest.ManifestFileMeta;
+import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.reader.RecordReader;
-import org.apache.paimon.table.DataTable;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.ReadonlyTable;
-import org.apache.paimon.table.source.DataSplit;
-import org.apache.paimon.table.source.DataTableScan;
+import org.apache.paimon.table.Table;
 import org.apache.paimon.table.source.InnerTableRead;
+import org.apache.paimon.table.source.InnerTableScan;
+import org.apache.paimon.table.source.ReadOnceTableScan;
+import org.apache.paimon.table.source.SingletonSplit;
 import org.apache.paimon.table.source.Split;
-import org.apache.paimon.table.source.StreamDataTableScan;
 import org.apache.paimon.table.source.TableRead;
-import org.apache.paimon.table.source.snapshot.SnapshotReader;
 import org.apache.paimon.types.BigIntType;
-import org.apache.paimon.types.DataType;
-import org.apache.paimon.types.IntType;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
-import org.apache.paimon.types.VarCharType;
-import org.apache.paimon.utils.BranchManager;
 import org.apache.paimon.utils.IteratorRecordReader;
-import org.apache.paimon.utils.SimpleFileReader;
-import org.apache.paimon.utils.SnapshotManager;
-import org.apache.paimon.utils.TagManager;
+import org.apache.paimon.utils.ProjectedRow;
+import org.apache.paimon.utils.RowDataToObjectArrayConverter;
+import org.apache.paimon.utils.SerializationUtils;
 
-import javax.annotation.Nullable;
+import org.apache.paimon.shade.guava30.com.google.common.collect.Iterators;
 
-import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.OptionalLong;
 
-import static org.apache.paimon.utils.SerializationUtils.newBytesType;
-import static org.apache.paimon.utils.SerializationUtils.serializeBinaryRow;
+import static org.apache.paimon.catalog.Catalog.SYSTEM_TABLE_SPLITTER;
 
-/**
- * A table to produce modified partitions and buckets for each snapshot.
- *
- * <p>Only used internally by dedicated compact job sources.
- */
-public class BucketsTable implements DataTable, ReadonlyTable {
+/** A {@link Table} for showing buckets info. */
+public class BucketsTable implements ReadonlyTable {
 
     private static final long serialVersionUID = 1L;
 
-    private final FileStoreTable wrapped;
-    private final boolean isContinuous;
-
-    @Nullable private final String databaseName;
-
-    private static final RowType ROW_TYPE =
-            RowType.of(
-                    new DataType[] {
-                        new BigIntType(false),
-                        newBytesType(false),
-                        new IntType(false),
-                        newBytesType(false),
-                        new VarCharType(true, Integer.MAX_VALUE),
-                        new VarCharType(false, Integer.MAX_VALUE)
-                    },
-                    new String[] {
-                        "_SNAPSHOT_ID",
-                        "_PARTITION",
-                        "_BUCKET",
-                        "_FILES",
-                        "_DATABASE_NAME",
-                        "_TABLE_NAME"
-                    });
-
-    public BucketsTable(FileStoreTable wrapped, boolean isContinuous) {
-        this(wrapped, isContinuous, null);
-    }
-
-    // if need to specify the database of a table, use this method
-    public BucketsTable(FileStoreTable wrapped, boolean isContinuous, String 
databaseName) {
-        this.wrapped = wrapped;
-        this.isContinuous = isContinuous;
-        this.databaseName = databaseName;
-    }
-
-    @Override
-    public OptionalLong latestSnapshotId() {
-        return wrapped.latestSnapshotId();
-    }
-
-    @Override
-    public Snapshot snapshot(long snapshotId) {
-        return wrapped.snapshot(snapshotId);
-    }
+    public static final String BUCKETS = "buckets";
 
-    @Override
-    public SimpleFileReader<ManifestFileMeta> manifestListReader() {
-        return wrapped.manifestListReader();
-    }
+    public static final RowType TABLE_TYPE =
+            new RowType(
+                    Arrays.asList(
+                            new DataField(0, "partition", 
SerializationUtils.newStringType(true)),
+                            new DataField(1, "bucket", 
DataTypes.INT().notNull()),
+                            new DataField(2, "record_count", new 
BigIntType(false)),
+                            new DataField(3, "file_size_in_bytes", new 
BigIntType(false)),
+                            new DataField(4, "file_count", new 
BigIntType(false)),
+                            new DataField(5, "last_update_time", 
DataTypes.TIMESTAMP_MILLIS())));
 
-    @Override
-    public SimpleFileReader<ManifestEntry> manifestFileReader() {
-        return wrapped.manifestFileReader();
-    }
+    private final FileStoreTable storeTable;
 
-    @Override
-    public SimpleFileReader<IndexManifestEntry> indexManifestFileReader() {
-        return wrapped.indexManifestFileReader();
+    public BucketsTable(FileStoreTable storeTable) {
+        this.storeTable = storeTable;
     }
 
     @Override
-    public Path location() {
-        return wrapped.location();
-    }
-
-    @Override
-    public SnapshotManager snapshotManager() {
-        return wrapped.snapshotManager();
-    }
-
-    @Override
-    public TagManager tagManager() {
-        return wrapped.tagManager();
+    public String name() {
+        return storeTable.name() + SYSTEM_TABLE_SPLITTER + BUCKETS;
     }
 
     @Override
-    public BranchManager branchManager() {
-        return wrapped.branchManager();
+    public RowType rowType() {
+        return TABLE_TYPE;
     }
 
     @Override
-    public DataTable switchToBranch(String branchName) {
-        return new BucketsTable(wrapped.switchToBranch(branchName), 
isContinuous, databaseName);
+    public List<String> primaryKeys() {
+        return Arrays.asList("partition", "bucket");
     }
 
     @Override
-    public String name() {
-        return "__internal_buckets_" + wrapped.location().getName();
+    public InnerTableScan newScan() {
+        return new BucketsScan();
     }
 
     @Override
-    public RowType rowType() {
-        return ROW_TYPE;
+    public InnerTableRead newRead() {
+        return new BucketsRead(storeTable);
     }
 
     @Override
-    public Map<String, String> options() {
-        return wrapped.options();
+    public Table copy(Map<String, String> dynamicOptions) {
+        return new BucketsTable(storeTable.copy(dynamicOptions));
     }
 
-    @Override
-    public List<String> primaryKeys() {
-        return Collections.emptyList();
-    }
+    private static class BucketsScan extends ReadOnceTableScan {
 
-    @Override
-    public SnapshotReader newSnapshotReader() {
-        return wrapped.newSnapshotReader();
-    }
+        @Override
+        public InnerTableScan withFilter(Predicate predicate) {
+            return this;
+        }
 
-    @Override
-    public DataTableScan newScan() {
-        return wrapped.newScan();
+        @Override
+        public Plan innerPlan() {
+            return () -> Collections.singletonList(new BucketsSplit());
+        }
     }
 
-    @Override
-    public StreamDataTableScan newStreamScan() {
-        return wrapped.newStreamScan();
-    }
+    private static class BucketsSplit extends SingletonSplit {
 
-    @Override
-    public CoreOptions coreOptions() {
-        return wrapped.coreOptions();
-    }
+        private static final long serialVersionUID = 1L;
 
-    @Override
-    public InnerTableRead newRead() {
-        return new BucketsRead();
-    }
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            return o != null && getClass() == o.getClass();
+        }
 
-    @Override
-    public BucketsTable copy(Map<String, String> dynamicOptions) {
-        return new BucketsTable(wrapped.copy(dynamicOptions), isContinuous, 
databaseName);
+        @Override
+        public int hashCode() {
+            return 1;
+        }
     }
 
-    @Override
-    public FileIO fileIO() {
-        return wrapped.fileIO();
-    }
+    private static class BucketsRead implements InnerTableRead {
 
-    public static RowType getRowType() {
-        return ROW_TYPE;
-    }
+        private final FileStoreTable fileStoreTable;
 
-    private class BucketsRead implements InnerTableRead {
+        private int[][] projection;
 
-        private final DataFileMetaSerializer dataFileMetaSerializer = new 
DataFileMetaSerializer();
+        public BucketsRead(FileStoreTable table) {
+            this.fileStoreTable = table;
+        }
 
         @Override
         public InnerTableRead withFilter(Predicate predicate) {
-            // filter is done by scan
+            // TODO
             return this;
         }
 
         @Override
         public InnerTableRead withProjection(int[][] projection) {
-            throw new UnsupportedOperationException("BucketsRead does not 
support projection");
+            this.projection = projection;
+            return this;
         }
 
         @Override
@@ -239,30 +170,46 @@ public class BucketsTable implements DataTable, 
ReadonlyTable {
         }
 
         @Override
-        public RecordReader<InternalRow> createReader(Split split) throws 
IOException {
-            if (!(split instanceof DataSplit)) {
+        public RecordReader<InternalRow> createReader(Split split) {
+            if (!(split instanceof BucketsSplit)) {
                 throw new IllegalArgumentException("Unsupported split: " + 
split.getClass());
             }
 
-            DataSplit dataSplit = (DataSplit) split;
+            List<BucketEntry> buckets = 
fileStoreTable.newSnapshotReader().bucketEntries();
 
-            List<DataFileMeta> files = Collections.emptyList();
-            if (isContinuous) {
-                // Serialized files are only useful in streaming jobs.
-                // Batch compact jobs only run once, so they only need to know 
what buckets should
-                // be compacted and don't need to concern incremental new 
files.
-                files = dataSplit.dataFiles();
+            RowDataToObjectArrayConverter converter =
+                    new RowDataToObjectArrayConverter(
+                            fileStoreTable.schema().logicalPartitionType());
+
+            List<InternalRow> results = new ArrayList<>(buckets.size());
+            for (BucketEntry entry : buckets) {
+                results.add(toRow(entry, converter));
             }
-            InternalRow row =
-                    GenericRow.of(
-                            dataSplit.snapshotId(),
-                            serializeBinaryRow(dataSplit.partition()),
-                            dataSplit.bucket(),
-                            dataFileMetaSerializer.serializeList(files),
-                            BinaryString.fromString(databaseName),
-                            BinaryString.fromString(wrapped.name()));
-
-            return new 
IteratorRecordReader<>(Collections.singletonList(row).iterator());
+
+            Iterator<InternalRow> iterator = results.iterator();
+            if (projection != null) {
+                iterator =
+                        Iterators.transform(
+                                iterator, row -> 
ProjectedRow.from(projection).replaceRow(row));
+            }
+            return new IteratorRecordReader<>(iterator);
+        }
+
+        private GenericRow toRow(
+                BucketEntry entry, RowDataToObjectArrayConverter 
partitionConverter) {
+            BinaryString partitionId =
+                    BinaryString.fromString(
+                            
Arrays.toString(partitionConverter.convert(entry.partition())));
+            return GenericRow.of(
+                    partitionId,
+                    entry.bucket(),
+                    entry.recordCount(),
+                    entry.fileSizeInBytes(),
+                    entry.fileCount(),
+                    Timestamp.fromLocalDateTime(
+                            LocalDateTime.ofInstant(
+                                    
Instant.ofEpochMilli(entry.lastFileCreationTime()),
+                                    ZoneId.systemDefault())));
         }
     }
 }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java 
b/paimon-core/src/main/java/org/apache/paimon/table/system/CompactBucketsTable.java
similarity index 92%
copy from 
paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java
copy to 
paimon-core/src/main/java/org/apache/paimon/table/system/CompactBucketsTable.java
index ddf38b7b2..8f6476c7f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/system/BucketsTable.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/system/CompactBucketsTable.java
@@ -66,11 +66,12 @@ import static 
org.apache.paimon.utils.SerializationUtils.newBytesType;
 import static org.apache.paimon.utils.SerializationUtils.serializeBinaryRow;
 
 /**
- * A table to produce modified partitions and buckets for each snapshot.
+ * A table to produce modified partitions and buckets (also including files in 
streaming mode) for
+ * each snapshot.
  *
  * <p>Only used internally by dedicated compact job sources.
  */
-public class BucketsTable implements DataTable, ReadonlyTable {
+public class CompactBucketsTable implements DataTable, ReadonlyTable {
 
     private static final long serialVersionUID = 1L;
 
@@ -98,12 +99,12 @@ public class BucketsTable implements DataTable, 
ReadonlyTable {
                         "_TABLE_NAME"
                     });
 
-    public BucketsTable(FileStoreTable wrapped, boolean isContinuous) {
+    public CompactBucketsTable(FileStoreTable wrapped, boolean isContinuous) {
         this(wrapped, isContinuous, null);
     }
 
     // if need to specify the database of a table, use this method
-    public BucketsTable(FileStoreTable wrapped, boolean isContinuous, String 
databaseName) {
+    public CompactBucketsTable(FileStoreTable wrapped, boolean isContinuous, 
String databaseName) {
         this.wrapped = wrapped;
         this.isContinuous = isContinuous;
         this.databaseName = databaseName;
@@ -156,7 +157,8 @@ public class BucketsTable implements DataTable, 
ReadonlyTable {
 
     @Override
     public DataTable switchToBranch(String branchName) {
-        return new BucketsTable(wrapped.switchToBranch(branchName), 
isContinuous, databaseName);
+        return new CompactBucketsTable(
+                wrapped.switchToBranch(branchName), isContinuous, 
databaseName);
     }
 
     @Override
@@ -205,8 +207,8 @@ public class BucketsTable implements DataTable, 
ReadonlyTable {
     }
 
     @Override
-    public BucketsTable copy(Map<String, String> dynamicOptions) {
-        return new BucketsTable(wrapped.copy(dynamicOptions), isContinuous, 
databaseName);
+    public CompactBucketsTable copy(Map<String, String> dynamicOptions) {
+        return new CompactBucketsTable(wrapped.copy(dynamicOptions), 
isContinuous, databaseName);
     }
 
     @Override
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/system/PartitionsTable.java 
b/paimon-core/src/main/java/org/apache/paimon/table/system/PartitionsTable.java
index 76c0768ee..09cf25167 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/system/PartitionsTable.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/system/PartitionsTable.java
@@ -115,7 +115,6 @@ public class PartitionsTable implements ReadonlyTable {
 
         @Override
         public InnerTableScan withFilter(Predicate predicate) {
-            // TODO
             return this;
         }
 
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/system/SystemTableLoader.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/system/SystemTableLoader.java
index 10e0c587b..a84f41ec1 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/system/SystemTableLoader.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/system/SystemTableLoader.java
@@ -42,6 +42,7 @@ import static 
org.apache.paimon.table.system.AggregationFieldsTable.AGGREGATION_
 import static 
org.apache.paimon.table.system.AllTableOptionsTable.ALL_TABLE_OPTIONS;
 import static org.apache.paimon.table.system.AuditLogTable.AUDIT_LOG;
 import static org.apache.paimon.table.system.BranchesTable.BRANCHES;
+import static org.apache.paimon.table.system.BucketsTable.BUCKETS;
 import static 
org.apache.paimon.table.system.CatalogOptionsTable.CATALOG_OPTIONS;
 import static org.apache.paimon.table.system.ConsumersTable.CONSUMERS;
 import static org.apache.paimon.table.system.FilesTable.FILES;
@@ -67,6 +68,7 @@ public class SystemTableLoader {
                     .put(OPTIONS, OptionsTable::new)
                     .put(SCHEMAS, SchemasTable::new)
                     .put(PARTITIONS, PartitionsTable::new)
+                    .put(BUCKETS, BucketsTable::new)
                     .put(AUDIT_LOG, AuditLogTable::new)
                     .put(FILES, FilesTable::new)
                     .put(TAGS, TagsTable::new)
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/system/BucketsTableTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/system/BucketsTableTest.java
new file mode 100644
index 000000000..57cb6605a
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/system/BucketsTableTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.paimon.table.system;
+
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.fs.FileIO;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.schema.Schema;
+import org.apache.paimon.schema.SchemaManager;
+import org.apache.paimon.schema.SchemaUtils;
+import org.apache.paimon.schema.TableSchema;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.FileStoreTableFactory;
+import org.apache.paimon.table.Table;
+import org.apache.paimon.table.TableTestBase;
+import org.apache.paimon.types.DataTypes;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.paimon.catalog.Catalog.SYSTEM_TABLE_SPLITTER;
+import static org.apache.paimon.table.system.BucketsTable.BUCKETS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Unit tests for {@link BucketsTable}. */
+public class BucketsTableTest extends TableTestBase {
+
+    private Table bucketsTable;
+
+    @BeforeEach
+    public void before() throws Exception {
+        String tableName = "MyTable";
+        FileIO fileIO = LocalFileIO.create();
+        Path tablePath = new Path(String.format("%s/%s.db/%s", warehouse, 
database, tableName));
+        Schema schema =
+                Schema.newBuilder()
+                        .column("pk", DataTypes.INT())
+                        .column("pt", DataTypes.INT())
+                        .column("col1", DataTypes.INT())
+                        .partitionKeys("pt")
+                        .primaryKey("pk", "pt")
+                        .option("bucket", "2")
+                        .build();
+        TableSchema tableSchema =
+                SchemaUtils.forceCommit(new SchemaManager(fileIO, tablePath), 
schema);
+        FileStoreTable table =
+                FileStoreTableFactory.create(LocalFileIO.create(), tablePath, 
tableSchema);
+
+        bucketsTable = catalog.getTable(identifier(tableName + 
SYSTEM_TABLE_SPLITTER + BUCKETS));
+
+        write(table, GenericRow.of(1, 1, 1), GenericRow.of(1, 2, 5));
+        write(table, GenericRow.of(1, 1, 3), GenericRow.of(1, 2, 4));
+    }
+
+    @Test
+    public void testBucketsTable() throws Exception {
+        assertThat(read(bucketsTable, new int[][] {{0}, {1}, {2}, {4}}))
+                .containsExactlyInAnyOrder(
+                        GenericRow.of(BinaryString.fromString("[1]"), 0, 2L, 
2L),
+                        GenericRow.of(BinaryString.fromString("[2]"), 0, 2L, 
2L));
+    }
+}
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/compact/MultiAwareBucketTableScan.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/compact/MultiAwareBucketTableScan.java
index 68d66ec5f..747995d20 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/compact/MultiAwareBucketTableScan.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/compact/MultiAwareBucketTableScan.java
@@ -24,7 +24,7 @@ import org.apache.paimon.table.BucketMode;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.source.Split;
 import org.apache.paimon.table.source.StreamTableScan;
-import org.apache.paimon.table.system.BucketsTable;
+import org.apache.paimon.table.system.CompactBucketsTable;
 
 import org.apache.flink.api.java.tuple.Tuple2;
 
@@ -44,7 +44,7 @@ import static 
org.apache.paimon.flink.utils.MultiTablesCompactorUtil.compactOpti
  */
 public class MultiAwareBucketTableScan extends 
MultiTableScanBase<Tuple2<Split, String>> {
 
-    protected transient Map<Identifier, BucketsTable> tablesMap;
+    protected transient Map<Identifier, CompactBucketsTable> tablesMap;
     protected transient Map<Identifier, StreamTableScan> scansMap;
 
     public MultiAwareBucketTableScan(
@@ -87,8 +87,9 @@ public class MultiAwareBucketTableScan extends 
MultiTableScanBase<Tuple2<Split,
     @Override
     public void addScanTable(FileStoreTable fileStoreTable, Identifier 
identifier) {
         if (fileStoreTable.bucketMode() != BucketMode.BUCKET_UNAWARE) {
-            BucketsTable bucketsTable =
-                    new BucketsTable(fileStoreTable, isStreaming, 
identifier.getDatabaseName())
+            CompactBucketsTable bucketsTable =
+                    new CompactBucketsTable(
+                                    fileStoreTable, isStreaming, 
identifier.getDatabaseName())
                             .copy(compactOptions(isStreaming));
             tablesMap.put(identifier, bucketsTable);
             scansMap.put(identifier, 
bucketsTable.newReadBuilder().newStreamScan());
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/BucketsRowChannelComputer.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/BucketsRowChannelComputer.java
index d93d38e00..61c9dd018 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/BucketsRowChannelComputer.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/BucketsRowChannelComputer.java
@@ -20,13 +20,13 @@ package org.apache.paimon.flink.sink;
 
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.table.sink.ChannelComputer;
-import org.apache.paimon.table.system.BucketsTable;
+import org.apache.paimon.table.system.CompactBucketsTable;
 
 import org.apache.flink.table.data.RowData;
 
 import static org.apache.paimon.utils.SerializationUtils.deserializeBinaryRow;
 
-/** {@link ChannelComputer} to partition {@link RowData} from {@link 
BucketsTable}. */
+/** {@link ChannelComputer} to partition {@link RowData} from {@link 
CompactBucketsTable}. */
 public class BucketsRowChannelComputer implements ChannelComputer<RowData> {
 
     private transient int numberOfChannels;
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CombinedTableCompactorSourceBuilder.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CombinedTableCompactorSourceBuilder.java
index 384cdcdce..e5cbbe845 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CombinedTableCompactorSourceBuilder.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CombinedTableCompactorSourceBuilder.java
@@ -25,7 +25,7 @@ import 
org.apache.paimon.flink.source.operator.CombinedAwareBatchSourceFunction;
 import 
org.apache.paimon.flink.source.operator.CombinedAwareStreamingSourceFunction;
 import 
org.apache.paimon.flink.source.operator.CombinedUnawareBatchSourceFunction;
 import 
org.apache.paimon.flink.source.operator.CombinedUnawareStreamingSourceFunction;
-import org.apache.paimon.table.system.BucketsTable;
+import org.apache.paimon.table.system.CompactBucketsTable;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.Preconditions;
 
@@ -85,7 +85,7 @@ public class CombinedTableCompactorSourceBuilder {
 
     public DataStream<RowData> buildAwareBucketTableSource() {
         Preconditions.checkArgument(env != null, "StreamExecutionEnvironment 
should not be null.");
-        RowType produceType = BucketsTable.getRowType();
+        RowType produceType = CompactBucketsTable.getRowType();
         if (isContinuous) {
             return CombinedAwareStreamingSourceFunction.buildSource(
                     env,
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CompactorSourceBuilder.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CompactorSourceBuilder.java
index 10fc3b558..141f45ecd 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CompactorSourceBuilder.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/CompactorSourceBuilder.java
@@ -27,7 +27,7 @@ import org.apache.paimon.options.Options;
 import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.source.ReadBuilder;
-import org.apache.paimon.table.system.BucketsTable;
+import org.apache.paimon.table.system.CompactBucketsTable;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.Preconditions;
 
@@ -85,18 +85,19 @@ public class CompactorSourceBuilder {
         return this;
     }
 
-    private Source<RowData, ?, ?> buildSource(BucketsTable bucketsTable) {
+    private Source<RowData, ?, ?> buildSource(CompactBucketsTable 
compactBucketsTable) {
 
         if (isContinuous) {
-            bucketsTable = bucketsTable.copy(streamingCompactOptions());
+            compactBucketsTable = 
compactBucketsTable.copy(streamingCompactOptions());
             return new ContinuousFileStoreSource(
-                    
bucketsTable.newReadBuilder().withFilter(partitionPredicate),
-                    bucketsTable.options(),
+                    
compactBucketsTable.newReadBuilder().withFilter(partitionPredicate),
+                    compactBucketsTable.options(),
                     null);
         } else {
-            bucketsTable = bucketsTable.copy(batchCompactOptions());
-            ReadBuilder readBuilder = 
bucketsTable.newReadBuilder().withFilter(partitionPredicate);
-            Options options = bucketsTable.coreOptions().toConfiguration();
+            compactBucketsTable = 
compactBucketsTable.copy(batchCompactOptions());
+            ReadBuilder readBuilder =
+                    
compactBucketsTable.newReadBuilder().withFilter(partitionPredicate);
+            Options options = 
compactBucketsTable.coreOptions().toConfiguration();
             return new StaticFileStoreSource(
                     readBuilder,
                     null,
@@ -110,11 +111,11 @@ public class CompactorSourceBuilder {
             throw new IllegalArgumentException("StreamExecutionEnvironment 
should not be null.");
         }
 
-        BucketsTable bucketsTable = new BucketsTable(table, isContinuous);
-        RowType produceType = bucketsTable.rowType();
+        CompactBucketsTable compactBucketsTable = new 
CompactBucketsTable(table, isContinuous);
+        RowType produceType = compactBucketsTable.rowType();
         DataStreamSource<RowData> dataStream =
                 env.fromSource(
-                        buildSource(bucketsTable),
+                        buildSource(compactBucketsTable),
                         WatermarkStrategy.noWatermarks(),
                         tableIdentifier + "-compact-source",
                         
InternalTypeInfo.of(LogicalTypeConversion.toLogicalType(produceType)));
@@ -122,7 +123,7 @@ public class CompactorSourceBuilder {
             Preconditions.checkArgument(
                     partitionIdleTime == null, "Streaming mode does not 
support partitionIdleTime");
         } else if (partitionIdleTime != null) {
-            Map<BinaryRow, Long> partitionInfo = 
getPartitionInfo(bucketsTable);
+            Map<BinaryRow, Long> partitionInfo = 
getPartitionInfo(compactBucketsTable);
             long historyMilli =
                     LocalDateTime.now()
                             .minus(partitionIdleTime)
@@ -175,7 +176,7 @@ public class CompactorSourceBuilder {
         return this;
     }
 
-    private Map<BinaryRow, Long> getPartitionInfo(BucketsTable table) {
+    private Map<BinaryRow, Long> getPartitionInfo(CompactBucketsTable table) {
         List<PartitionEntry> partitions = 
table.newSnapshotReader().partitionEntries();
 
         return partitions.stream()
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MultiTablesReadOperator.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MultiTablesReadOperator.java
index 0b48a9bcc..73d46ae1e 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MultiTablesReadOperator.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MultiTablesReadOperator.java
@@ -29,7 +29,7 @@ import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.Table;
 import org.apache.paimon.table.source.Split;
 import org.apache.paimon.table.source.TableRead;
-import org.apache.paimon.table.system.BucketsTable;
+import org.apache.paimon.table.system.CompactBucketsTable;
 import org.apache.paimon.utils.CloseableIterator;
 import org.apache.paimon.utils.Preconditions;
 
@@ -80,7 +80,7 @@ public class MultiTablesReadOperator extends 
AbstractStreamOperator<RowData>
 
     private transient Catalog catalog;
     private transient IOManager ioManager;
-    private transient Map<Identifier, BucketsTable> tablesMap;
+    private transient Map<Identifier, CompactBucketsTable> tablesMap;
     private transient Map<Identifier, TableRead> readsMap;
     private transient StreamRecord<RowData> reuseRecord;
     private transient FlinkRowData reuseRow;
@@ -132,7 +132,7 @@ public class MultiTablesReadOperator extends 
AbstractStreamOperator<RowData>
     }
 
     private TableRead getTableRead(Identifier tableId) {
-        BucketsTable table = tablesMap.get(tableId);
+        CompactBucketsTable table = tablesMap.get(tableId);
         if (table == null) {
             try {
                 Table newTable = catalog.getTable(tableId);
@@ -141,7 +141,7 @@ public class MultiTablesReadOperator extends 
AbstractStreamOperator<RowData>
                         "Only FileStoreTable supports compact action. The 
table type is '%s'.",
                         newTable.getClass().getName());
                 table =
-                        new BucketsTable(
+                        new CompactBucketsTable(
                                         (FileStoreTable) newTable,
                                         isStreaming,
                                         tableId.getDatabaseName())
@@ -156,7 +156,7 @@ public class MultiTablesReadOperator extends 
AbstractStreamOperator<RowData>
         return readsMap.get(tableId);
     }
 
-    private Map<BinaryRow, Long> getPartitionInfo(BucketsTable table) {
+    private Map<BinaryRow, Long> getPartitionInfo(CompactBucketsTable table) {
         List<PartitionEntry> partitions = 
table.newSnapshotReader().partitionEntries();
 
         return partitions.stream()
diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CompactProcedure.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CompactProcedure.java
index 89f84f5f7..cc78d1d68 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CompactProcedure.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CompactProcedure.java
@@ -264,9 +264,8 @@ public class CompactProcedure extends BaseProcedure {
         Set<BinaryRow> partitionToBeCompacted =
                 getHistoryPartition(snapshotReader, partitionIdleTime);
         List<Pair<byte[], Integer>> partitionBuckets =
-                snapshotReader.read().splits().stream()
-                        .map(split -> (DataSplit) split)
-                        .map(dataSplit -> Pair.of(dataSplit.partition(), 
dataSplit.bucket()))
+                snapshotReader.bucketEntries().stream()
+                        .map(entry -> Pair.of(entry.partition(), 
entry.bucket()))
                         .distinct()
                         .filter(pair -> 
partitionToBeCompacted.contains(pair.getKey()))
                         .map(

Reply via email to