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 b9d0a461d4 [core] Support row-count based bucket calculation for 
postpone compact. (#8434)
b9d0a461d4 is described below

commit b9d0a461d47af17f544bda70db79fb3b92e01a85
Author: Wenchao Wu <[email protected]>
AuthorDate: Tue Jul 7 15:58:10 2026 +0800

    [core] Support row-count based bucket calculation for postpone compact. 
(#8434)
    
    Support calculating postpone bucket count by configured target row count
    per bucket.
    
    A new table option is added:
    
    - `postpone.target-row-num-per-bucket`
    
    When compacting postpone bucket files, bucket count is resolved in this
    order:
    
    1. Reuse known bucket count if the partition was already compacted
    before.
    2. If `postpone.target-row-num-per-bucket` is configured, compute bucket
    count from active postpone row count.
    3. Fall back to `postpone.default-bucket-num`.
---
 docs/docs/primary-key-table/data-distribution.md   |  7 ++
 docs/generated/core_configuration.html             |  6 ++
 .../main/java/org/apache/paimon/CoreOptions.java   | 11 +++
 .../org/apache/paimon/table/PostponeUtils.java     | 67 +++++++++++++++
 .../org/apache/paimon/table/PostponeUtilsTest.java | 99 ++++++++++++++++++++++
 .../apache/paimon/flink/action/CompactAction.java  | 28 +++---
 .../paimon/flink/PostponeBucketTableITCase.java    | 51 +++++++++++
 .../procedure/SparkPostponeCompactProcedure.scala  | 37 +++++++-
 .../paimon/spark/sql/PostponeBucketTableTest.scala | 41 +++++++++
 9 files changed, 334 insertions(+), 13 deletions(-)

diff --git a/docs/docs/primary-key-table/data-distribution.md 
b/docs/docs/primary-key-table/data-distribution.md
index 95e8c01aa6..49846b2ea3 100644
--- a/docs/docs/primary-key-table/data-distribution.md
+++ b/docs/docs/primary-key-table/data-distribution.md
@@ -79,6 +79,13 @@ you need to run a compaction job.
 See `compact` [procedure](../flink/procedures).
 The bucket number for the partitions compacted for the first time
 is configured by the option `postpone.default-bucket-num`, whose default value 
is `1`.
+You can also configure `postpone.target-row-num-per-bucket` to calculate the 
bucket number
+from the row count of the files in the postpone bucket directory.
+The calculated bucket number is `ceil(row_count / 
postpone.target-row-num-per-bucket)`,
+and is at least `1`.
+When this option is configured, it takes precedence over 
`postpone.default-bucket-num`
+for partitions compacted for the first time.
+Partitions that already have real bucket files keep their existing bucket 
number.
 
 Finally, when you feel that the bucket number of some partition is too small,
 you can also run a rescale job.
diff --git a/docs/generated/core_configuration.html 
b/docs/generated/core_configuration.html
index 2bb0099a49..6fa06a5ce3 100644
--- a/docs/generated/core_configuration.html
+++ b/docs/generated/core_configuration.html
@@ -1217,6 +1217,12 @@ This config option does not affect the default 
filesystem metastore.</td>
             <td>Integer</td>
             <td>Bucket number for the partitions compacted for the first time 
in postpone bucket tables.</td>
         </tr>
+        <tr>
+            <td><h5>postpone.target-row-num-per-bucket</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>Long</td>
+            <td>Target row number per bucket for partitions compacted from 
postpone bucket files for the first time.</td>
+        </tr>
         <tr>
             <td><h5>primary-key</h5></td>
             <td style="word-wrap: break-word;">(none)</td>
diff --git a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java 
b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
index 0302d10995..85ed36dace 100644
--- a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
@@ -2593,6 +2593,13 @@ public class CoreOptions implements Serializable {
                     .withDescription(
                             "Bucket number for the partitions compacted for 
the first time in postpone bucket tables.");
 
+    public static final ConfigOption<Long> POSTPONE_TARGET_ROW_NUM_PER_BUCKET =
+            key("postpone.target-row-num-per-bucket")
+                    .longType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "Target row number per bucket for partitions 
compacted from postpone bucket files for the first time.");
+
     public static final ConfigOption<Long> GLOBAL_INDEX_ROW_COUNT_PER_SHARD =
             key("global-index.row-count-per-shard")
                     .longType()
@@ -4160,6 +4167,10 @@ public class CoreOptions implements Serializable {
         return options.get(POSTPONE_DEFAULT_BUCKET_NUM);
     }
 
+    public Optional<Long> postponeTargetRowNumPerBucket() {
+        return options.getOptional(POSTPONE_TARGET_ROW_NUM_PER_BUCKET);
+    }
+
     public long globalIndexRowCountPerShard() {
         return options.get(GLOBAL_INDEX_ROW_COUNT_PER_SHARD);
     }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/PostponeUtils.java 
b/paimon-core/src/main/java/org/apache/paimon/table/PostponeUtils.java
index 097921df54..62fb41a9e9 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/PostponeUtils.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/PostponeUtils.java
@@ -19,12 +19,17 @@
 package org.apache.paimon.table;
 
 import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.SimpleFileEntry;
 
+import javax.annotation.Nullable;
+
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.apache.paimon.CoreOptions.BUCKET;
 import static org.apache.paimon.CoreOptions.WRITE_ONLY;
@@ -32,6 +37,56 @@ import static org.apache.paimon.CoreOptions.WRITE_ONLY;
 /** Utils for postpone table. */
 public class PostponeUtils {
 
+    public static int computeBucketNumByRowCount(long rowCount, long 
targetRowNumPerBucket) {
+        if (targetRowNumPerBucket <= 0) {
+            throw new IllegalArgumentException(
+                    "Option 'postpone.target-row-num-per-bucket' must be 
greater than 0.");
+        }
+
+        long bucketNum = rowCount <= 0 ? 1 : (rowCount - 1) / 
targetRowNumPerBucket + 1;
+        if (bucketNum > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException(
+                    "Computed postpone bucket number "
+                            + bucketNum
+                            + " exceeds the maximum integer value 
(Integer.MAX_VALUE = "
+                            + Integer.MAX_VALUE
+                            + "). Consider increasing 
'postpone.target-row-num-per-bucket' "
+                            + "to reduce the bucket count.");
+        }
+        return (int) bucketNum;
+    }
+
+    public static int determineBucketNum(
+            BinaryRow partition,
+            Map<BinaryRow, Integer> knownNumBuckets,
+            Optional<Long> targetRowNumPerBucket,
+            Map<BinaryRow, Long> postponeRowCounts,
+            int defaultBucketNum) {
+        return determineBucketNum(
+                partition,
+                knownNumBuckets,
+                targetRowNumPerBucket.orElse(null),
+                postponeRowCounts,
+                defaultBucketNum);
+    }
+
+    public static int determineBucketNum(
+            BinaryRow partition,
+            Map<BinaryRow, Integer> knownNumBuckets,
+            @Nullable Long targetRowNumPerBucket,
+            Map<BinaryRow, Long> postponeRowCounts,
+            int defaultBucketNum) {
+        Integer knownBucketNum = knownNumBuckets.get(partition);
+        if (knownBucketNum != null) {
+            return knownBucketNum;
+        } else if (targetRowNumPerBucket != null) {
+            return computeBucketNumByRowCount(
+                    postponeRowCounts.getOrDefault(partition, 0L), 
targetRowNumPerBucket);
+        } else {
+            return defaultBucketNum;
+        }
+    }
+
     public static Map<BinaryRow, Integer> getKnownNumBuckets(FileStoreTable 
table) {
         Map<BinaryRow, Integer> knownNumBuckets = new HashMap<>();
         List<SimpleFileEntry> simpleFileEntries =
@@ -54,6 +109,18 @@ public class PostponeUtils {
         return knownNumBuckets;
     }
 
+    /** Returns row counts of current active files in the postpone bucket. */
+    public static Map<BinaryRow, Long> getPostponeRowCounts(FileStoreTable 
table) {
+        Map<BinaryRow, Long> rowCounts = new HashMap<>();
+        Iterator<ManifestEntry> iterator =
+                
table.newSnapshotReader().withBucket(BucketMode.POSTPONE_BUCKET).readFileIterator();
+        while (iterator.hasNext()) {
+            ManifestEntry entry = iterator.next();
+            rowCounts.merge(entry.partition(), entry.file().rowCount(), 
Long::sum);
+        }
+        return rowCounts;
+    }
+
     public static FileStoreTable tableForFixBucketWrite(FileStoreTable table) {
         Map<String, String> batchWriteOptions = new HashMap<>();
         batchWriteOptions.put(WRITE_ONLY.key(), "true");
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/PostponeUtilsTest.java 
b/paimon-core/src/test/java/org/apache/paimon/table/PostponeUtilsTest.java
new file mode 100644
index 0000000000..50d9bc2077
--- /dev/null
+++ b/paimon-core/src/test/java/org/apache/paimon/table/PostponeUtilsTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.BinaryRowWriter;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link PostponeUtils}. */
+public class PostponeUtilsTest {
+
+    @Test
+    public void testComputeBucketNumByRowCount() {
+        assertThat(PostponeUtils.computeBucketNumByRowCount(0, 
100)).isEqualTo(1);
+        assertThat(PostponeUtils.computeBucketNumByRowCount(1, 
100)).isEqualTo(1);
+        assertThat(PostponeUtils.computeBucketNumByRowCount(100, 
100)).isEqualTo(1);
+        assertThat(PostponeUtils.computeBucketNumByRowCount(101, 
100)).isEqualTo(2);
+        assertThat(PostponeUtils.computeBucketNumByRowCount(999, 
200)).isEqualTo(5);
+        assertThat(PostponeUtils.computeBucketNumByRowCount(1000, 
200)).isEqualTo(5);
+    }
+
+    @Test
+    public void testComputeBucketNumByRowCountRejectsInvalidTarget() {
+        assertThatThrownBy(() -> PostponeUtils.computeBucketNumByRowCount(100, 
0))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining(
+                        "Option 'postpone.target-row-num-per-bucket' must be 
greater than 0.");
+    }
+
+    @Test
+    public void testComputeBucketNumByRowCountRejectsOverflow() {
+        assertThatThrownBy(() -> 
PostponeUtils.computeBucketNumByRowCount(Long.MAX_VALUE, 1))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("exceeds the maximum integer value")
+                .hasMessageContaining("Consider increasing 
'postpone.target-row-num-per-bucket'");
+    }
+
+    @Test
+    public void testDetermineBucketNum() {
+        Map<BinaryRow, Integer> knownNumBuckets = new HashMap<>();
+        Map<BinaryRow, Long> postponeRowCounts = new HashMap<>();
+
+        BinaryRow knownPartition = partition(1);
+        BinaryRow targetPartition = partition(2);
+        BinaryRow defaultPartition = partition(3);
+
+        knownNumBuckets.put(knownPartition, 4);
+        postponeRowCounts.put(knownPartition, 1000L);
+        postponeRowCounts.put(targetPartition, 450L);
+
+        assertThat(
+                        PostponeUtils.determineBucketNum(
+                                knownPartition, knownNumBuckets, 200L, 
postponeRowCounts, 1))
+                .isEqualTo(4);
+        assertThat(
+                        PostponeUtils.determineBucketNum(
+                                targetPartition, knownNumBuckets, 200L, 
postponeRowCounts, 1))
+                .isEqualTo(3);
+        assertThat(
+                        PostponeUtils.determineBucketNum(
+                                defaultPartition,
+                                knownNumBuckets,
+                                (Long) null,
+                                postponeRowCounts,
+                                7))
+                .isEqualTo(7);
+    }
+
+    private static BinaryRow partition(int value) {
+        BinaryRow row = new BinaryRow(1);
+        BinaryRowWriter writer = new BinaryRowWriter(row);
+        writer.writeInt(0, value);
+        writer.complete();
+        return row;
+    }
+}
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CompactAction.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CompactAction.java
index 7c46763975..cbdcf825f2 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CompactAction.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/CompactAction.java
@@ -36,7 +36,6 @@ import org.apache.paimon.flink.sink.FlinkSinkBuilder;
 import org.apache.paimon.flink.sink.FlinkStreamPartitioner;
 import org.apache.paimon.flink.sink.RowDataChannelComputer;
 import org.apache.paimon.flink.source.CompactorSourceBuilder;
-import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.PartitionPredicate;
 import org.apache.paimon.predicate.PartitionPredicateVisitor;
@@ -45,6 +44,7 @@ import org.apache.paimon.predicate.PredicateBuilder;
 import org.apache.paimon.predicate.PredicateProjectionConverter;
 import org.apache.paimon.table.BucketMode;
 import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.PostponeUtils;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.InternalRowPartitionComputer;
 import org.apache.paimon.utils.Pair;
@@ -66,10 +66,10 @@ import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static 
org.apache.paimon.partition.PartitionPredicate.createBinaryPartitions;
 import static 
org.apache.paimon.partition.PartitionPredicate.createPartitionPredicate;
@@ -291,6 +291,13 @@ public class CompactAction extends TableActionBase {
 
         Options options = new Options(table.options());
         int defaultBucketNum = 
options.get(CoreOptions.POSTPONE_DEFAULT_BUCKET_NUM);
+        Optional<Long> targetRowNumPerBucket =
+                
options.getOptional(CoreOptions.POSTPONE_TARGET_ROW_NUM_PER_BUCKET);
+        Map<BinaryRow, Integer> knownNumBuckets = 
PostponeUtils.getKnownNumBuckets(table);
+        Map<BinaryRow, Long> postponeRowCounts =
+                targetRowNumPerBucket.isPresent()
+                        ? PostponeUtils.getPostponeRowCounts(table)
+                        : Collections.emptyMap();
 
         // change bucket to a positive value, so we can scan files from the 
bucket = -2 directory
         Map<String, String> bucketOptions = new HashMap<>(table.options());
@@ -322,16 +329,13 @@ public class CompactAction extends TableActionBase {
         String commitUser = CoreOptions.createCommitUser(options);
         List<DataStream<Committable>> dataStreams = new ArrayList<>();
         for (BinaryRow partition : partitions) {
-            int bucketNum = defaultBucketNum;
-
-            Iterator<ManifestEntry> it =
-                    table.newSnapshotReader()
-                            
.withPartitionFilter(Collections.singletonList(partition))
-                            .onlyReadRealBuckets()
-                            .readFileIterator();
-            if (it.hasNext()) {
-                bucketNum = it.next().totalBuckets();
-            }
+            int bucketNum =
+                    PostponeUtils.determineBucketNum(
+                            partition,
+                            knownNumBuckets,
+                            targetRowNumPerBucket,
+                            postponeRowCounts,
+                            defaultBucketNum);
 
             bucketOptions = new HashMap<>(table.options());
             bucketOptions.put(CoreOptions.BUCKET.key(), 
String.valueOf(bucketNum));
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/PostponeBucketTableITCase.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/PostponeBucketTableITCase.java
index d285fbb044..d9f8781da3 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/PostponeBucketTableITCase.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/PostponeBucketTableITCase.java
@@ -438,6 +438,57 @@ public class PostponeBucketTableITCase extends 
AbstractTestBase {
         
assertThat(collect(tEnv.executeSql(query))).hasSameElementsAs(expectedData);
     }
 
+    @Test
+    public void testCompactWithTargetRowNumPerBucket() throws Exception {
+        String warehouse = getTempDirPath();
+        TableEnvironment tEnv =
+                tableEnvironmentBuilder()
+                        .batchMode()
+                        .setConf(TableConfigOptions.TABLE_DML_SYNC, true)
+                        .build();
+
+        tEnv.executeSql(
+                "CREATE CATALOG mycat WITH (\n"
+                        + "  'type' = 'paimon',\n"
+                        + "  'warehouse' = '"
+                        + warehouse
+                        + "'\n"
+                        + ")");
+        tEnv.executeSql("USE CATALOG mycat");
+        tEnv.executeSql(
+                "CREATE TABLE T (\n"
+                        + "  pt INT,\n"
+                        + "  k INT,\n"
+                        + "  v INT,\n"
+                        + "  PRIMARY KEY (pt, k) NOT ENFORCED\n"
+                        + ") PARTITIONED BY (pt) WITH (\n"
+                        + "  'bucket' = '-2',\n"
+                        + "  'postpone.target-row-num-per-bucket' = '200',\n"
+                        + "  'postpone.batch-write-fixed-bucket' = 'false'\n"
+                        + ")");
+
+        List<String> values = new ArrayList<>();
+        for (int j = 0; j < 100; j++) {
+            values.add(String.format("(0, %d, %d)", j, j));
+        }
+        for (int j = 0; j < 450; j++) {
+            values.add(String.format("(1, %d, %d)", j, j));
+        }
+        tEnv.executeSql("INSERT INTO T VALUES " + String.join(", ", 
values)).await();
+        assertThat(collect(tEnv.executeSql("SELECT * FROM T"))).isEmpty();
+
+        tEnv.executeSql("CALL sys.compact(`table` => 'default.T')").await();
+
+        assertThat(collect(tEnv.executeSql("SELECT pt, COUNT(*) FROM T GROUP 
BY pt")))
+                .containsExactlyInAnyOrder("+I[0, 100]", "+I[1, 450]");
+        assertThat(
+                        collect(
+                                tEnv.executeSql(
+                                        "SELECT `partition`, COUNT(DISTINCT 
bucket) FROM `T$files` "
+                                                + "GROUP BY `partition`")))
+                .containsExactlyInAnyOrder("+I[{0}, 1]", "+I[{1}, 3]");
+    }
+
     @Timeout(TIMEOUT)
     @Test
     public void testInputChangelogProducer() throws Exception {
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/procedure/SparkPostponeCompactProcedure.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/procedure/SparkPostponeCompactProcedure.scala
index d714f47541..4725ce09f9 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/procedure/SparkPostponeCompactProcedure.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/procedure/SparkPostponeCompactProcedure.scala
@@ -24,6 +24,7 @@ import org.apache.paimon.data.BinaryRow
 import org.apache.paimon.io.{CompactIncrement, DataFileMeta, DataIncrement}
 import org.apache.paimon.partition.PartitionPredicate
 import org.apache.paimon.postpone.BucketFiles
+import org.apache.paimon.spark.PaimonImplicits._
 import org.apache.paimon.spark.commands.{EncoderSerDeGroup, 
PostponeFixBucketProcessor}
 import org.apache.paimon.spark.schema.SparkSystemColumns.{BUCKET_COL, 
ROW_KIND_COL}
 import org.apache.paimon.spark.util.{ScanPlanHelper, SparkRowUtils}
@@ -63,13 +64,26 @@ case class SparkPostponeCompactProcedure(
   // Create bucket computer to determine bucket count for each partition
   private lazy val postponePartitionBucketComputer = {
     val knownNumBuckets = PostponeUtils.getKnownNumBuckets(table)
+    val targetRowNumPerBucket: Option[java.lang.Long] =
+      table.coreOptions.postponeTargetRowNumPerBucket
+    val postponeRowCounts =
+      if (targetRowNumPerBucket.isDefined) {
+        PostponeUtils.getPostponeRowCounts(table)
+      } else {
+        Collections.emptyMap[BinaryRow, java.lang.Long]()
+      }
     val defaultBucketNum =
       if 
(table.coreOptions.toConfiguration.contains(CoreOptions.POSTPONE_DEFAULT_BUCKET_NUM))
 {
         table.coreOptions.postponeDefaultBucketNum
       } else {
         spark.sparkContext.defaultParallelism
       }
-    (p: BinaryRow) => knownNumBuckets.getOrDefault(p, defaultBucketNum)
+
+    SparkPostponeCompactProcedure.PostponePartitionBucketComputer(
+      knownNumBuckets,
+      targetRowNumPerBucket,
+      postponeRowCounts,
+      defaultBucketNum)
   }
 
   private def partitionCols(df: DataFrame): Seq[Column] = {
@@ -237,3 +251,24 @@ case class SparkPostponeCompactProcedure(
     LOG.info("Successfully committed postpone bucket compaction for table: 
{}.", table.name())
   }
 }
+
+object SparkPostponeCompactProcedure {
+
+  private[procedure] case class PostponePartitionBucketComputer(
+      knownNumBuckets: java.util.Map[BinaryRow, Integer],
+      targetRowNumPerBucket: Option[java.lang.Long],
+      postponeRowCounts: java.util.Map[BinaryRow, java.lang.Long],
+      defaultBucketNum: Int)
+    extends (BinaryRow => Integer)
+    with Serializable {
+
+    override def apply(p: BinaryRow): Integer = {
+      PostponeUtils.determineBucketNum(
+        p,
+        knownNumBuckets,
+        targetRowNumPerBucket.orNull,
+        postponeRowCounts,
+        defaultBucketNum)
+    }
+  }
+}
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PostponeBucketTableTest.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PostponeBucketTableTest.scala
index d984f1385c..1bc3cc1888 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PostponeBucketTableTest.scala
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/PostponeBucketTableTest.scala
@@ -292,6 +292,47 @@ class PostponeBucketTableTest extends PaimonSparkTestBase {
     }
   }
 
+  test("Postpone partition bucket table: compact with target row num per 
bucket") {
+    withTable("t") {
+      sql("""
+            |CREATE TABLE t (
+            |  k INT,
+            |  v STRING,
+            |  pt INT
+            |) PARTITIONED BY (pt)
+            |TBLPROPERTIES (
+            |  'primary-key' = 'k, pt',
+            |  'bucket' = '-2',
+            |  'postpone.target-row-num-per-bucket' = '200',
+            |  'postpone.batch-write-fixed-bucket' = 'false'
+            |)
+            |""".stripMargin)
+
+      sql("""
+            |INSERT INTO t SELECT /*+ REPARTITION(4) */
+            |id AS k,
+            |CAST(id AS STRING) AS v,
+            |CASE WHEN id < 100 THEN 0 ELSE 1 END AS pt
+            |FROM range (0, 550)
+            |""".stripMargin)
+
+      checkAnswer(sql("SELECT count(*) FROM t"), Seq(Row(0)))
+      checkAnswer(sql("SELECT distinct(bucket) FROM `t$buckets` ORDER BY 
bucket"), Seq(Row(-2)))
+
+      sql("CALL sys.compact(table => 't')")
+
+      checkAnswer(sql("SELECT count(*) FROM t"), Seq(Row(550)))
+      checkAnswer(
+        sql("SELECT distinct(bucket) FROM `t$buckets` WHERE partition = '{0}' 
ORDER BY bucket"),
+        Seq(Row(0))
+      )
+      checkAnswer(
+        sql("SELECT distinct(bucket) FROM `t$buckets` WHERE partition = '{1}' 
ORDER BY bucket"),
+        Seq(Row(0), Row(1), Row(2))
+      )
+    }
+  }
+
   test("Postpone bucket table: skip clustering in writing phase") {
     withTable("t") {
       sql("""

Reply via email to