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 2281336cc8 [core][flink][spark] Introduce postpone fixed-bucket write 
builder (#8992)
2281336cc8 is described below

commit 2281336cc87a7e4dbd050bb22dcc9f669583ac50
Author: Jingsong Lee <[email protected]>
AuthorDate: Mon Aug 3 15:35:42 2026 +0800

    [core][flink][spark] Introduce postpone fixed-bucket write builder (#8992)
---
 .../java/org/apache/paimon/KeyValueFileStore.java  | 27 +++++--
 .../paimon/operation/AbstractFileStoreWrite.java   | 83 ++++++++++++++++++----
 .../paimon/operation/FileStoreCommitImpl.java      | 16 +++--
 .../apache/paimon/operation/FileStoreWrite.java    | 10 +++
 .../paimon/operation/commit/ConflictDetection.java | 48 +++++++------
 .../paimon/privilege/PrivilegedFileStoreTable.java |  7 ++
 .../paimon/table/DelegatedFileStoreTable.java      |  6 ++
 .../org/apache/paimon/table/FileStoreTable.java    | 13 ++++
 .../org/apache/paimon/table/PostponeUtils.java     | 14 ----
 .../paimon/table/PrimaryKeyFileStoreTable.java     | 13 +++-
 .../paimon/table/sink/BatchWriteBuilderImpl.java   | 19 -----
 ...l.java => PostponeFixedBucketWriteBuilder.java} | 68 ++++++++----------
 .../apache/paimon/table/sink/TableWriteImpl.java   | 22 +++++-
 .../paimon/operation/FileStoreCommitTest.java      | 64 +++++++++++++++++
 .../paimon/table/PrimaryKeySimpleTableTest.java    | 55 ++++++++++++++
 .../org/apache/paimon/flink/sink/FlinkSink.java    | 24 +++++--
 .../apache/paimon/flink/sink/FlinkSinkBuilder.java |  5 +-
 .../flink/sink/PostponeBatchWriteOperator.java     | 29 +-------
 .../sink/PostponeFixedBucketChannelComputer.java   |  8 ++-
 .../paimon/flink/sink/PostponeFixedBucketSink.java | 47 ++++++------
 .../apache/paimon/flink/sink/StoreSinkWrite.java   | 28 ++++++++
 .../paimon/flink/sink/StoreSinkWriteImpl.java      | 42 ++++++++++-
 .../PostponeFixedBucketChannelComputerTest.java    | 30 ++++++++
 .../flink/sink/StoreCompactOperatorTest.java       |  5 ++
 .../paimon/spark/commands/PaimonSparkWriter.scala  | 18 ++---
 .../paimon/spark/write/PaimonDataWrite.scala       | 29 +++-----
 26 files changed, 506 insertions(+), 224 deletions(-)

diff --git a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java 
b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
index b3c06c98c1..8e072a0736 100644
--- a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
+++ b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
@@ -33,6 +33,7 @@ import org.apache.paimon.operation.KeyValueFileStoreScan;
 import org.apache.paimon.operation.KeyValueFileStoreWrite;
 import org.apache.paimon.operation.MergeFileSplitRead;
 import org.apache.paimon.operation.RawFileSplitRead;
+import org.apache.paimon.options.Options;
 import org.apache.paimon.postpone.PostponeBucketFileStoreWrite;
 import org.apache.paimon.schema.KeyValueFieldsExtractor;
 import org.apache.paimon.schema.SchemaManager;
@@ -162,19 +163,31 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
                     tableName,
                     writeId);
         }
+        return newFixedBucketWrite(commitUser, options);
+    }
+
+    /** Creates a merge-tree writer for fixed-bucket batch writes to a 
postpone-bucket table. */
+    public AbstractFileStoreWrite<KeyValue> newPostponeFixedBucketWrite(String 
commitUser) {
+        Options writeOptions = new Options(options.toMap());
+        writeOptions.set(CoreOptions.WRITE_ONLY, true);
+        return newFixedBucketWrite(commitUser, new CoreOptions(writeOptions));
+    }
+
+    private AbstractFileStoreWrite<KeyValue> newFixedBucketWrite(
+            String commitUser, CoreOptions writeOptions) {
         DynamicBucketIndexMaintainer.Factory indexFactory = null;
         if (bucketMode() == BucketMode.HASH_DYNAMIC) {
             indexFactory = new 
DynamicBucketIndexMaintainer.Factory(newIndexFileHandler());
         }
         BucketedDvMaintainer.Factory dvMaintainerFactory = null;
-        if (options.deletionVectorsEnabled()) {
+        if (writeOptions.deletionVectorsEnabled()) {
             dvMaintainerFactory = 
BucketedDvMaintainer.factory(newIndexFileHandler());
         }
         BucketedPrimaryKeyIndexMaintainer.Factory 
primaryKeyIndexMaintainerFactory = null;
-        if (options.primaryKeyVectorIndexEnabled()
-                || options.primaryKeyFullTextIndexEnabled()
-                || !options.primaryKeyBTreeIndexColumns().isEmpty()
-                || !options.primaryKeyBitmapIndexColumns().isEmpty()) {
+        if (writeOptions.primaryKeyVectorIndexEnabled()
+                || writeOptions.primaryKeyFullTextIndexEnabled()
+                || !writeOptions.primaryKeyBTreeIndexColumns().isEmpty()
+                || !writeOptions.primaryKeyBitmapIndexColumns().isEmpty()) {
             primaryKeyIndexMaintainerFactory =
                     BucketedPrimaryKeyIndexMaintainer.Factory.create(
                             newIndexFileHandler(), newReaderFactoryBuilder(), 
schema);
@@ -188,7 +201,7 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
                 keyType,
                 valueType,
                 keyComparatorSupplier,
-                () -> UserDefinedSeqComparator.create(valueType, options),
+                () -> UserDefinedSeqComparator.create(valueType, writeOptions),
                 logDedupEqualSupplier,
                 mfFactory,
                 pathFactory(),
@@ -198,7 +211,7 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
                 indexFactory,
                 dvMaintainerFactory,
                 primaryKeyIndexMaintainerFactory,
-                options,
+                writeOptions,
                 keyValueFieldsExtractor,
                 tableName);
     }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
index e83f5e81c1..2ee11440dc 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
@@ -72,6 +72,7 @@ import static 
org.apache.paimon.CoreOptions.PARTITION_DEFAULT_NAME;
 import static org.apache.paimon.io.DataFileMeta.getMaxSequenceNumber;
 import static 
org.apache.paimon.shade.guava30.com.google.common.base.MoreObjects.firstNonNull;
 import static 
org.apache.paimon.utils.FileStorePathFactory.getPartitionComputer;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
 
 /**
  * Base {@link FileStoreWrite} implementation.
@@ -185,6 +186,22 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
     @Override
     public void write(BinaryRow partition, int bucket, T data) throws 
Exception {
         WriterContainer<T> container = getWriterWrapper(partition, bucket);
+        write(container, data);
+    }
+
+    @Override
+    public void write(BinaryRow partition, int bucket, int totalBuckets, T 
data) throws Exception {
+        checkArgument(totalBuckets > 0, "Total number of buckets must be 
positive.");
+        checkArgument(
+                bucket >= 0 && bucket < totalBuckets,
+                "Bucket %s is out of range [0, %s).",
+                bucket,
+                totalBuckets);
+        WriterContainer<T> container = getWriterWrapper(partition, bucket, 
totalBuckets);
+        write(container, data);
+    }
+
+    private void write(WriterContainer<T> container, T data) throws Exception {
         container.writer.write(data);
         if (container.dynamicBucketMaintainer != null) {
             container.dynamicBucketMaintainer.notifyNewRecord((KeyValue) data);
@@ -460,13 +477,28 @@ public abstract class AbstractFileStoreWrite<T> 
implements FileStoreWrite<T> {
     }
 
     protected WriterContainer<T> getWriterWrapper(BinaryRow partition, int 
bucket) {
+        Map<Integer, WriterContainer<T>> buckets = 
getWriterContainers(partition);
+        return buckets.computeIfAbsent(
+                bucket, k -> createWriterContainer(partition.copy(), bucket));
+    }
+
+    private WriterContainer<T> getWriterWrapper(BinaryRow partition, int 
bucket, int totalBuckets) {
+        Map<Integer, WriterContainer<T>> buckets = 
getWriterContainers(partition);
+        if (!buckets.isEmpty()) {
+            checkNumBuckets(
+                    partition, totalBuckets, 
buckets.values().iterator().next().totalBuckets);
+        }
+        return buckets.computeIfAbsent(
+                bucket, k -> createWriterContainer(partition.copy(), bucket, 
totalBuckets));
+    }
+
+    private Map<Integer, WriterContainer<T>> getWriterContainers(BinaryRow 
partition) {
         Map<Integer, WriterContainer<T>> buckets = writers.get(partition);
         if (buckets == null) {
             buckets = new HashMap<>();
             writers.put(partition.copy(), buckets);
         }
-        return buckets.computeIfAbsent(
-                bucket, k -> createWriterContainer(partition.copy(), bucket));
+        return buckets;
     }
 
     public RecordWriter<T> createWriter(BinaryRow partition, int bucket) {
@@ -474,6 +506,16 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
     }
 
     public WriterContainer<T> createWriterContainer(BinaryRow partition, int 
bucket) {
+        return createWriterContainer(partition, bucket, numBuckets, 
!ignoreNumBucketCheck);
+    }
+
+    private WriterContainer<T> createWriterContainer(
+            BinaryRow partition, int bucket, int totalBuckets) {
+        return createWriterContainer(partition, bucket, totalBuckets, true);
+    }
+
+    private WriterContainer<T> createWriterContainer(
+            BinaryRow partition, int bucket, int expectedTotalBuckets, boolean 
validateNumBuckets) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("Creating writer for partition {}, bucket {}", 
partition, bucket);
         }
@@ -496,7 +538,9 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                         partition, bucket, latestSnapshot, 
ignorePreviousFiles);
         RestoreFiles restored = RestoreFiles.empty();
         if (!actualIgnorePreviousFiles) {
-            restored = scanExistingFileMetas(partition, bucket);
+            restored =
+                    scanExistingFileMetas(
+                            partition, bucket, expectedTotalBuckets, 
validateNumBuckets);
         }
 
         DynamicBucketIndexMaintainer indexMaintainer =
@@ -541,7 +585,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
         Snapshot previousSnapshot = restored.snapshot();
         return new WriterContainer<>(
                 writer,
-                firstNonNull(restored.totalBuckets(), numBuckets),
+                firstNonNull(restored.totalBuckets(), expectedTotalBuckets),
                 indexMaintainer,
                 dvMaintainer,
                 primaryKeyIndexMaintainer,
@@ -588,7 +632,8 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
         return this;
     }
 
-    private RestoreFiles scanExistingFileMetas(BinaryRow partition, int 
bucket) {
+    private RestoreFiles scanExistingFileMetas(
+            BinaryRow partition, int bucket, int expectedTotalBuckets, boolean 
validateNumBuckets) {
         Supplier<String> partInfo =
                 () ->
                         partitionType.getFieldCount() > 0
@@ -615,19 +660,33 @@ public abstract class AbstractFileStoreWrite<T> 
implements FileStoreWrite<T> {
                             partInfo.get(), bucket),
                     e);
         }
-        Integer restoredTotalBuckets = restored.totalBuckets();
-        int totalBuckets = numBuckets;
-        if (restoredTotalBuckets != null) {
-            totalBuckets = restoredTotalBuckets;
+        if (restored.totalBuckets() != null && validateNumBuckets) {
+            checkNumBuckets(partInfo.get(), expectedTotalBuckets, 
restored.totalBuckets());
         }
-        if (!ignoreNumBucketCheck && totalBuckets != numBuckets) {
+        return restored;
+    }
+
+    private void checkNumBuckets(BinaryRow partition, int expected, int 
previous) {
+        String partInfo =
+                partitionType.getFieldCount() > 0
+                        ? "partition "
+                                + getPartitionComputer(
+                                                partitionType,
+                                                
PARTITION_DEFAULT_NAME.defaultValue(),
+                                                legacyPartitionName)
+                                        .generatePartValues(partition)
+                        : "table";
+        checkNumBuckets(partInfo, expected, previous);
+    }
+
+    private void checkNumBuckets(String partInfo, int expected, int previous) {
+        if (expected != previous) {
             throw new RuntimeException(
                     String.format(
                             "Try to write %s with a new bucket num %d, but the 
previous bucket num is %d. "
                                     + "Please switch to batch mode, and 
perform INSERT OVERWRITE to rescale current data layout first.",
-                            partInfo.get(), numBuckets, totalBuckets));
+                            partInfo, expected, previous));
         }
-        return restored;
     }
 
     private ExecutorService compactExecutor() {
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
index 5725dc89a7..e07fcb443f 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
@@ -804,14 +804,14 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
         return retryCount + 1;
     }
 
-    private void checkSameBucketFromSnapshot(
+    private void checkSameFixedBucketFromSnapshot(
             List<ManifestEntry> deltaFiles, @Nullable Snapshot latestSnapshot) 
{
         if (latestSnapshot == null) {
             return;
         }
 
         Map<BinaryRow, Integer> expectedTotalBuckets =
-                conflictDetection.collectUncheckedBucketPartitions(deltaFiles);
+                
conflictDetection.collectUncheckedFixedBucketPartitions(deltaFiles);
         if (expectedTotalBuckets.isEmpty()) {
             return;
         }
@@ -820,14 +820,14 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
                 scanner.readTotalBuckets(
                         latestSnapshot, new 
ArrayList<>(expectedTotalBuckets.keySet()));
         Optional<RuntimeException> exception =
-                conflictDetection.checkSameBucketByTotalBuckets(
+                conflictDetection.checkSameFixedBucketByTotalBuckets(
                         expectedTotalBuckets, previousTotalBuckets);
         if (exception.isPresent()) {
             throw exception.get();
         }
     }
 
-    private boolean shouldCheckSameBucket(CommitKind commitKind) {
+    private boolean shouldCheckSameFixedBucket(CommitKind commitKind) {
         return commitKind == CommitKind.APPEND
                 && bucketMode == BucketMode.HASH_FIXED
                 && (isUnorderedWriteOnlyAppend() || 
isWriteOnlySnapshotSequenceAppend());
@@ -940,6 +940,10 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
             }
         }
 
+        if (latestSnapshot == null) {
+            conflictDetection.checkSameBucketWithinDelta(deltaFiles);
+        }
+
         List<BinaryRow> changedPartitions = null;
         if (strictModeChecker != null) {
             changedPartitions = changedPartitions(deltaFiles, indexFiles);
@@ -964,8 +968,8 @@ public class FileStoreCommitImpl implements FileStoreCommit 
{
         boolean checkConflicts = latestSnapshot != null && (discardDuplicate 
|| detectConflicts);
         // By default, if checkConflicts is required, we do not have to do the 
extra check bucket
         // here.
-        if (!checkConflicts && shouldCheckSameBucket(commitKind)) {
-            checkSameBucketFromSnapshot(deltaFiles, latestSnapshot);
+        if (!checkConflicts && shouldCheckSameFixedBucket(commitKind)) {
+            checkSameFixedBucketFromSnapshot(deltaFiles, latestSnapshot);
         }
         if (checkConflicts) {
             // latestSnapshotId is different from the snapshot id we've 
checked for conflicts,
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java
index b94bd04fc4..9f85047fb6 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java
@@ -96,6 +96,16 @@ public interface FileStoreWrite<T> extends 
Restorable<List<FileStoreWrite.State<
      */
     void write(BinaryRow partition, int bucket, T data) throws Exception;
 
+    /**
+     * Write data with the total number of buckets explicitly determined at 
runtime.
+     *
+     * <p>This is used when a partition's bucket count is not a static table 
option. All writes to
+     * the same partition must use the same {@code totalBuckets}.
+     */
+    default void write(BinaryRow partition, int bucket, int totalBuckets, T 
data) throws Exception {
+        throw new UnsupportedOperationException("Runtime bucket counts are not 
supported.");
+    }
+
     /**
      * Compact data stored in given partition and bucket. Note that compaction 
process is only
      * submitted and may not be completed when the method returns.
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/commit/ConflictDetection.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/commit/ConflictDetection.java
index 34c5a29a46..4cdc5f997b 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/commit/ConflictDetection.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/commit/ConflictDetection.java
@@ -75,7 +75,7 @@ import static 
org.apache.paimon.utils.Preconditions.checkState;
 public class ConflictDetection {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ConflictDetection.class);
-    private static final int SAME_BUCKET_CHECK_CACHE_MAX_SIZE = 1000;
+    private static final int FIXED_BUCKET_CHECK_CACHE_MAX_SIZE = 1000;
 
     private final String tableName;
     private final String commitUser;
@@ -89,13 +89,15 @@ public class ConflictDetection {
     private final IndexFileHandler indexFileHandler;
     private final SnapshotManager snapshotManager;
     private final CommitScanner commitScanner;
-    private final Map<BinaryRow, Boolean> sameBucketCheckedPartitions =
-            new LinkedHashMap<BinaryRow, 
Boolean>(SAME_BUCKET_CHECK_CACHE_MAX_SIZE, 0.75f, false) {
-                @Override
-                protected boolean removeEldestEntry(Map.Entry<BinaryRow, 
Boolean> eldest) {
-                    return size() > SAME_BUCKET_CHECK_CACHE_MAX_SIZE;
-                }
-            };
+    private final Set<BinaryRow> checkedFixedBucketPartitions =
+            Collections.newSetFromMap(
+                    new LinkedHashMap<BinaryRow, Boolean>(
+                            FIXED_BUCKET_CHECK_CACHE_MAX_SIZE, 0.75f, false) {
+                        @Override
+                        protected boolean 
removeEldestEntry(Map.Entry<BinaryRow, Boolean> eldest) {
+                            return size() > FIXED_BUCKET_CHECK_CACHE_MAX_SIZE;
+                        }
+                    });
 
     private @Nullable PartitionExpire partitionExpire;
     private @Nullable Long rowIdCheckFromSnapshot = null;
@@ -248,13 +250,22 @@ public class ConflictDetection {
                 latestSnapshot, deltaEntries, deltaIndexEntries, 
rowIdColumnConflictChecker);
     }
 
-    public <T extends FileEntry> Map<BinaryRow, Integer> 
collectUncheckedBucketPartitions(
+    public <T extends FileEntry> Map<BinaryRow, Integer> 
collectUncheckedFixedBucketPartitions(
+            List<T> deltaEntries) {
+        Map<BinaryRow, Integer> totalBuckets = 
collectBucketPartitions(deltaEntries);
+        totalBuckets.keySet().removeAll(checkedFixedBucketPartitions);
+        return totalBuckets;
+    }
+
+    public <T extends FileEntry> void checkSameBucketWithinDelta(List<T> 
deltaEntries) {
+        collectBucketPartitions(deltaEntries);
+    }
+
+    private <T extends FileEntry> Map<BinaryRow, Integer> 
collectBucketPartitions(
             List<T> deltaEntries) {
         Map<BinaryRow, Integer> totalBuckets = new HashMap<>();
         for (T entry : deltaEntries) {
-            if (entry.kind() != FileKind.ADD
-                    || entry.totalBuckets() <= 0
-                    || 
sameBucketCheckedPartitions.containsKey(entry.partition())) {
+            if (entry.kind() != FileKind.ADD || entry.totalBuckets() <= 0) {
                 continue;
             }
 
@@ -266,7 +277,7 @@ public class ConflictDetection {
         return totalBuckets;
     }
 
-    public Optional<RuntimeException> checkSameBucketByTotalBuckets(
+    public Optional<RuntimeException> checkSameFixedBucketByTotalBuckets(
             Map<BinaryRow, Integer> expectedTotalBuckets,
             Map<BinaryRow, Integer> previousTotalBuckets) {
         for (Map.Entry<BinaryRow, Integer> entry : 
expectedTotalBuckets.entrySet()) {
@@ -275,9 +286,7 @@ public class ConflictDetection {
                 return Optional.of(bucketNumMismatch(entry.getKey(), 
entry.getValue(), previous));
             }
         }
-        for (BinaryRow partition : expectedTotalBuckets.keySet()) {
-            sameBucketCheckedPartitions.put(partition, Boolean.TRUE);
-        }
+        checkedFixedBucketPartitions.addAll(expectedTotalBuckets.keySet());
         return Optional.empty();
     }
 
@@ -297,10 +306,6 @@ public class ConflictDetection {
             if (entry.totalBuckets() <= 0) {
                 continue;
             }
-            if (sameBucketCheckedPartitions.containsKey(entry.partition())) {
-                continue;
-            }
-
             if (!totalBuckets.containsKey(entry.partition())) {
                 totalBuckets.put(entry.partition(), entry.totalBuckets());
                 continue;
@@ -327,9 +332,6 @@ public class ConflictDetection {
             LOG.warn("", conflictException.getLeft());
             return Optional.of(conflictException.getRight());
         }
-        for (BinaryRow partition : totalBuckets.keySet()) {
-            sameBucketCheckedPartitions.put(partition, Boolean.TRUE);
-        }
         return Optional.empty();
     }
 
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
 
b/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
index e1064e7045..36e8d03340 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
@@ -264,6 +264,13 @@ public class PrivilegedFileStoreTable extends 
DelegatedFileStoreTable {
         return wrapped.newWrite(commitUser, writeId);
     }
 
+    @Override
+    public TableWriteImpl<?> newPostponeFixedBucketWrite(
+            String commitUser, @Nullable Integer writeId) {
+        privilegeChecker.assertCanInsert(identifier);
+        return wrapped.newPostponeFixedBucketWrite(commitUser, writeId);
+    }
+
     @Override
     public TableCommitImpl newCommit(String commitUser) {
         privilegeChecker.assertCanInsert(identifier);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
index f9bcbeee01..9a5e81bcb6 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
@@ -345,6 +345,12 @@ public abstract class DelegatedFileStoreTable implements 
FileStoreTable {
         return wrapped.newWrite(commitUser, writeId);
     }
 
+    @Override
+    public TableWriteImpl<?> newPostponeFixedBucketWrite(
+            String commitUser, @Nullable Integer writeId) {
+        return wrapped.newPostponeFixedBucketWrite(commitUser, writeId);
+    }
+
     @Override
     public TableCommitImpl newCommit(String commitUser) {
         return wrapped.newCommit(commitUser);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/FileStoreTable.java 
b/paimon-core/src/main/java/org/apache/paimon/table/FileStoreTable.java
index 5a3f87d0ed..9704934305 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/FileStoreTable.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/FileStoreTable.java
@@ -29,6 +29,7 @@ import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.stats.Statistics;
 import org.apache.paimon.table.query.LocalTableQuery;
 import org.apache.paimon.table.sink.BatchTableCommit;
+import org.apache.paimon.table.sink.PostponeFixedBucketWriteBuilder;
 import org.apache.paimon.table.sink.RowKeyExtractor;
 import org.apache.paimon.table.sink.TableCommitImpl;
 import org.apache.paimon.table.sink.TableWriteImpl;
@@ -131,8 +132,20 @@ public interface FileStoreTable extends DataTable {
     @Override
     TableWriteImpl<?> newWrite(String commitUser);
 
+    /** Returns a builder for fixed-bucket batch writes to a postpone-bucket 
table. */
+    default PostponeFixedBucketWriteBuilder 
newPostponeFixedBucketWriteBuilder() {
+        return new PostponeFixedBucketWriteBuilder(this);
+    }
+
     TableWriteImpl<?> newWrite(String commitUser, @Nullable Integer writeId);
 
+    /** Creates a fixed-bucket merge-tree write for a postpone-bucket batch 
write. */
+    default TableWriteImpl<?> newPostponeFixedBucketWrite(
+            String commitUser, @Nullable Integer writeId) {
+        throw new UnsupportedOperationException(
+                "Postpone fixed-bucket writes are only supported by 
primary-key tables.");
+    }
+
     @Override
     TableCommitImpl newCommit(String commitUser);
 
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 5335830988..60b464fd15 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
@@ -341,15 +341,6 @@ public class PostponeUtils {
         return rowCounts;
     }
 
-    public static FileStoreTable tableForFixBucketWrite(FileStoreTable table) {
-        Map<String, String> batchWriteOptions = new HashMap<>();
-        batchWriteOptions.put(WRITE_ONLY.key(), "true");
-        // It's just used to create merge tree writer for writing files to 
fixed bucket.
-        // The real bucket number is determined at runtime.
-        batchWriteOptions.put(BUCKET.key(), "1");
-        return table.copy(batchWriteOptions);
-    }
-
     public static FileStoreTable tableForPostponeCompact(
             FileStoreTable table, int numBuckets, long snapshotId) {
         Map<String, String> compactOptions = new HashMap<>();
@@ -359,11 +350,6 @@ public class PostponeUtils {
         return table.copy(compactOptions);
     }
 
-    public static FileStoreTable tableForCommit(FileStoreTable table) {
-        return table.copy(
-                Collections.singletonMap(BUCKET.key(), 
String.valueOf(BucketMode.POSTPONE_BUCKET)));
-    }
-
     /** Snapshot-bound bucket-count assignment. */
     public static final class PostponeBucketAssigner implements Serializable {
 
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/PrimaryKeyFileStoreTable.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/PrimaryKeyFileStoreTable.java
index 3030b3504e..5e48557d76 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/PrimaryKeyFileStoreTable.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/PrimaryKeyFileStoreTable.java
@@ -26,6 +26,7 @@ import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.mergetree.compact.LookupMergeFunction;
 import org.apache.paimon.mergetree.compact.MergeFunctionFactory;
+import org.apache.paimon.operation.AbstractFileStoreWrite;
 import org.apache.paimon.operation.FileStoreScan;
 import org.apache.paimon.operation.KeyValueFileStoreScan;
 import org.apache.paimon.predicate.Predicate;
@@ -173,10 +174,20 @@ public class PrimaryKeyFileStoreTable extends 
AbstractFileStoreTable {
 
     @Override
     public TableWriteImpl<KeyValue> newWrite(String commitUser, @Nullable 
Integer writeId) {
+        return newWrite(store().newWrite(commitUser, writeId));
+    }
+
+    @Override
+    public TableWriteImpl<KeyValue> newPostponeFixedBucketWrite(
+            String commitUser, @Nullable Integer writeId) {
+        return newWrite(store().newPostponeFixedBucketWrite(commitUser));
+    }
+
+    private TableWriteImpl<KeyValue> newWrite(AbstractFileStoreWrite<KeyValue> 
storeWrite) {
         KeyValue kv = new KeyValue();
         return new TableWriteImpl<>(
                 rowType(),
-                store().newWrite(commitUser, writeId),
+                storeWrite,
                 createRowKeyExtractor(),
                 (record, rowKind) ->
                         kv.replace(
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
index c06bd9de42..d8c97405e2 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
@@ -21,7 +21,6 @@ package org.apache.paimon.table.sink;
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.table.InnerTable;
-import org.apache.paimon.table.Table;
 import org.apache.paimon.types.RowType;
 
 import javax.annotation.Nullable;
@@ -40,7 +39,6 @@ public class BatchWriteBuilderImpl implements 
BatchWriteBuilder {
     private final String commitUser;
 
     private Map<String, String> staticPartition;
-    private boolean appendCommitCheckConflict = false;
     private @Nullable Long rowIdCheckFromSnapshot = null;
 
     public BatchWriteBuilderImpl(InnerTable table) {
@@ -48,13 +46,6 @@ public class BatchWriteBuilderImpl implements 
BatchWriteBuilder {
         this.commitUser = createCommitUser(new Options(table.options()));
     }
 
-    private BatchWriteBuilderImpl(
-            InnerTable table, String commitUser, @Nullable Map<String, String> 
staticPartition) {
-        this.table = table;
-        this.commitUser = commitUser;
-        this.staticPartition = staticPartition;
-    }
-
     @Override
     public String tableName() {
         return table.name();
@@ -86,7 +77,6 @@ public class BatchWriteBuilderImpl implements 
BatchWriteBuilder {
         InnerTableCommit commit =
                 table.newCommit(commitUser)
                         .withOverwrite(staticPartition)
-                        .appendCommitCheckConflict(appendCommitCheckConflict)
                         .rowIdCheckConflict(rowIdCheckFromSnapshot);
         commit.ignoreEmptyCommit(
                 Options.fromMap(table.options())
@@ -95,15 +85,6 @@ public class BatchWriteBuilderImpl implements 
BatchWriteBuilder {
         return commit;
     }
 
-    public BatchWriteBuilderImpl copyWithNewTable(Table newTable) {
-        return new BatchWriteBuilderImpl((InnerTable) newTable, commitUser, 
staticPartition);
-    }
-
-    public BatchWriteBuilderImpl appendCommitCheckConflict(boolean 
appendCommitCheckConflict) {
-        this.appendCommitCheckConflict = appendCommitCheckConflict;
-        return this;
-    }
-
     public BatchWriteBuilderImpl rowIdCheckConflict(@Nullable Long 
rowIdCheckFromSnapshot) {
         this.rowIdCheckFromSnapshot = rowIdCheckFromSnapshot;
         return this;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
 
b/paimon-core/src/main/java/org/apache/paimon/table/sink/PostponeFixedBucketWriteBuilder.java
similarity index 50%
copy from 
paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
copy to 
paimon-core/src/main/java/org/apache/paimon/table/sink/PostponeFixedBucketWriteBuilder.java
index c06bd9de42..a3b28e47cc 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/table/sink/BatchWriteBuilderImpl.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/table/sink/PostponeFixedBucketWriteBuilder.java
@@ -20,8 +20,8 @@ package org.apache.paimon.table.sink;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.options.Options;
-import org.apache.paimon.table.InnerTable;
-import org.apache.paimon.table.Table;
+import org.apache.paimon.table.BucketMode;
+import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.types.RowType;
 
 import javax.annotation.Nullable;
@@ -30,31 +30,26 @@ import java.util.Map;
 import java.util.Optional;
 
 import static org.apache.paimon.CoreOptions.createCommitUser;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
 
-/** Implementation for {@link WriteBuilder}. */
-public class BatchWriteBuilderImpl implements BatchWriteBuilder {
+/** Write builder for assigning fixed buckets at runtime to a postpone-bucket 
table. */
+public class PostponeFixedBucketWriteBuilder implements BatchWriteBuilder {
 
     private static final long serialVersionUID = 1L;
 
-    private final InnerTable table;
+    private final FileStoreTable table;
     private final String commitUser;
 
-    private Map<String, String> staticPartition;
-    private boolean appendCommitCheckConflict = false;
-    private @Nullable Long rowIdCheckFromSnapshot = null;
+    @Nullable private Map<String, String> staticPartition;
 
-    public BatchWriteBuilderImpl(InnerTable table) {
+    public PostponeFixedBucketWriteBuilder(FileStoreTable table) {
+        checkArgument(
+                table.bucketMode() == BucketMode.POSTPONE_MODE,
+                "Postpone fixed-bucket write requires a postpone-bucket 
table.");
         this.table = table;
         this.commitUser = createCommitUser(new Options(table.options()));
     }
 
-    private BatchWriteBuilderImpl(
-            InnerTable table, String commitUser, @Nullable Map<String, String> 
staticPartition) {
-        this.table = table;
-        this.commitUser = commitUser;
-        this.staticPartition = staticPartition;
-    }
-
     @Override
     public String tableName() {
         return table.name();
@@ -71,41 +66,34 @@ public class BatchWriteBuilderImpl implements 
BatchWriteBuilder {
     }
 
     @Override
-    public BatchWriteBuilder withOverwrite(@Nullable Map<String, String> 
staticPartition) {
+    public PostponeFixedBucketWriteBuilder withOverwrite(
+            @Nullable Map<String, String> staticPartition) {
         this.staticPartition = staticPartition;
         return this;
     }
 
     @Override
-    public BatchTableWrite newWrite() {
-        return 
table.newWrite(commitUser).withIgnorePreviousFiles(staticPartition != null);
+    public TableWriteImpl<?> newWrite() {
+        return newWrite(commitUser, 
null).withIgnorePreviousFiles(staticPartition != null);
+    }
+
+    public TableWriteImpl<?> newWrite(String commitUser, @Nullable Integer 
writeId) {
+        return table.newPostponeFixedBucketWrite(commitUser, writeId);
     }
 
     @Override
-    public BatchTableCommit newCommit() {
-        InnerTableCommit commit =
-                table.newCommit(commitUser)
-                        .withOverwrite(staticPartition)
-                        .appendCommitCheckConflict(appendCommitCheckConflict)
-                        .rowIdCheckConflict(rowIdCheckFromSnapshot);
-        commit.ignoreEmptyCommit(
+    public TableCommitImpl newCommit() {
+        boolean ignoreEmpty =
                 Options.fromMap(table.options())
                         .getOptional(CoreOptions.SNAPSHOT_IGNORE_EMPTY_COMMIT)
-                        .orElse(true));
-        return commit;
+                        .orElse(true);
+        return newCommit(commitUser, ignoreEmpty);
     }
 
-    public BatchWriteBuilderImpl copyWithNewTable(Table newTable) {
-        return new BatchWriteBuilderImpl((InnerTable) newTable, commitUser, 
staticPartition);
-    }
-
-    public BatchWriteBuilderImpl appendCommitCheckConflict(boolean 
appendCommitCheckConflict) {
-        this.appendCommitCheckConflict = appendCommitCheckConflict;
-        return this;
-    }
-
-    public BatchWriteBuilderImpl rowIdCheckConflict(@Nullable Long 
rowIdCheckFromSnapshot) {
-        this.rowIdCheckFromSnapshot = rowIdCheckFromSnapshot;
-        return this;
+    public TableCommitImpl newCommit(String commitUser, boolean 
ignoreEmptyCommit) {
+        return table.newCommit(commitUser)
+                .withOverwrite(staticPartition)
+                .appendCommitCheckConflict(true)
+                .ignoreEmptyCommit(ignoreEmptyCommit);
     }
 }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java 
b/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java
index 101a51e8a5..9c815db10d 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/sink/TableWriteImpl.java
@@ -181,6 +181,21 @@ public class TableWriteImpl<T> implements InnerTableWrite, 
Restorable<List<State
 
     @Nullable
     public SinkRecord writeAndReturn(InternalRow row, int bucket) throws 
Exception {
+        return writeAndReturn(row, bucket, null);
+    }
+
+    /**
+     * Write a row to a bucket whose partition-level total bucket count is 
determined at runtime.
+     */
+    @Nullable
+    public SinkRecord writeAndReturn(InternalRow row, int bucket, int 
totalBuckets)
+            throws Exception {
+        return writeAndReturn(row, bucket, Integer.valueOf(totalBuckets));
+    }
+
+    @Nullable
+    private SinkRecord writeAndReturn(InternalRow row, int bucket, @Nullable 
Integer totalBuckets)
+            throws Exception {
         checkNullability(row);
         row = wrapDefaultValue(row);
         RowKind rowKind = RowKindGenerator.getRowKind(rowKindGenerator, row);
@@ -188,7 +203,12 @@ public class TableWriteImpl<T> implements InnerTableWrite, 
Restorable<List<State
             return null;
         }
         SinkRecord record = bucket == -1 ? toSinkRecord(row) : 
toSinkRecord(row, bucket);
-        write.write(record.partition(), record.bucket(), 
recordExtractor.extract(record, rowKind));
+        T extracted = recordExtractor.extract(record, rowKind);
+        if (totalBuckets == null) {
+            write.write(record.partition(), record.bucket(), extracted);
+        } else {
+            write.write(record.partition(), record.bucket(), totalBuckets, 
extracted);
+        }
         return record;
     }
 
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
index 98606bfb10..fb22df8c1f 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
@@ -107,6 +107,7 @@ import java.util.stream.Collectors;
 import static org.apache.paimon.index.HashIndexFile.HASH_INDEX;
 import static 
org.apache.paimon.partition.PartitionPredicate.createPartitionPredicate;
 import static org.apache.paimon.stats.SimpleStats.EMPTY_STATS;
+import static org.apache.paimon.table.BucketMode.POSTPONE_BUCKET;
 import static 
org.apache.paimon.testutils.assertj.PaimonAssertions.anyCauseMatches;
 import static org.apache.paimon.utils.HintFileUtils.LATEST;
 import static org.apache.paimon.utils.Preconditions.checkNotNull;
@@ -259,6 +260,69 @@ public class FileStoreCommitTest {
         }
     }
 
+    @Test
+    public void testPostponeBucketCheckIsNotSkippedByCache() throws Exception {
+        TestFileStore store = createStore(false, POSTPONE_BUCKET);
+        BinaryRow partition =
+                gen.getPartition(gen.nextInsert("20201110", 10, 1L, new int[] 
{1, 1}, "first"));
+
+        try (FileStoreCommitImpl commit = store.newCommit()) {
+            assertThat(
+                            commit.tryCommitOnce(
+                                            null,
+                                            
Collections.singletonList(addFile(partition, 0, 2, 0)),
+                                            Collections.emptyList(),
+                                            Collections.emptyList(),
+                                            0,
+                                            null,
+                                            new HashMap<>(),
+                                            Snapshot.CommitKind.APPEND,
+                                            false,
+                                            null,
+                                            true,
+                                            null)
+                                    .isSuccess())
+                    .isTrue();
+
+            Snapshot latestSnapshot = store.snapshotManager().latestSnapshot();
+            assertThat(
+                            commit.tryCommitOnce(
+                                            null,
+                                            
Collections.singletonList(addFile(partition, 1, 2, 1)),
+                                            Collections.emptyList(),
+                                            Collections.emptyList(),
+                                            1,
+                                            null,
+                                            new HashMap<>(),
+                                            Snapshot.CommitKind.APPEND,
+                                            false,
+                                            latestSnapshot,
+                                            true,
+                                            null)
+                                    .isSuccess())
+                    .isTrue();
+
+            latestSnapshot = store.snapshotManager().latestSnapshot();
+            Snapshot finalLatestSnapshot = latestSnapshot;
+            assertThatThrownBy(
+                            () ->
+                                    commit.tryCommitOnce(
+                                            null,
+                                            
Collections.singletonList(addFile(partition, 2, 3, 2)),
+                                            Collections.emptyList(),
+                                            Collections.emptyList(),
+                                            2,
+                                            null,
+                                            new HashMap<>(),
+                                            Snapshot.CommitKind.APPEND,
+                                            false,
+                                            finalLatestSnapshot,
+                                            true,
+                                            null))
+                    .hasMessageContaining("changed from 2 to 3 without 
overwrite");
+        }
+    }
+
     @Test
     public void testFilterCommittedAfterExpire() throws Exception {
         testRandomConcurrentNoConflict(1, false, 
CoreOptions.ChangelogProducer.NONE);
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/PrimaryKeySimpleTableTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/PrimaryKeySimpleTableTest.java
index 4bda9faf3e..3ea3628fa5 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/table/PrimaryKeySimpleTableTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/PrimaryKeySimpleTableTest.java
@@ -60,6 +60,7 @@ import org.apache.paimon.table.sink.BatchWriteBuilder;
 import org.apache.paimon.table.sink.CommitMessage;
 import org.apache.paimon.table.sink.CommitMessageImpl;
 import org.apache.paimon.table.sink.InnerTableCommit;
+import org.apache.paimon.table.sink.PostponeFixedBucketWriteBuilder;
 import org.apache.paimon.table.sink.StreamTableCommit;
 import org.apache.paimon.table.sink.StreamTableWrite;
 import org.apache.paimon.table.sink.StreamWriteBuilder;
@@ -212,6 +213,60 @@ public class PrimaryKeySimpleTableTest extends 
SimpleTableTestBase {
         assertThat(file.valueStatsCols()).isEmpty();
     }
 
+    @Test
+    public void testPostponeFixedBucketWriteBuilder() throws Exception {
+        FileStoreTable table =
+                createFileStoreTable(options -> options.set(BUCKET, 
BucketMode.POSTPONE_BUCKET));
+        PostponeFixedBucketWriteBuilder builder = 
table.newPostponeFixedBucketWriteBuilder();
+
+        List<CommitMessage> conflictingMessages = new ArrayList<>();
+        try (TableWriteImpl<?> write = builder.newWrite()) {
+            write.writeAndReturn(rowData(1, 1, 1L), 0, 2);
+            conflictingMessages.addAll(write.prepareCommit());
+        }
+        try (TableWriteImpl<?> write = builder.newWrite()) {
+            write.writeAndReturn(rowData(1, 2, 2L), 1, 3);
+            conflictingMessages.addAll(write.prepareCommit());
+        }
+        try (BatchTableCommit commit = builder.newCommit()) {
+            assertThatThrownBy(() -> commit.commit(conflictingMessages))
+                    .hasMessageContaining("new bucket num 3")
+                    .hasMessageContaining("previous bucket num is 2");
+        }
+        assertThat(table.latestSnapshot()).isEmpty();
+
+        List<CommitMessage> messages;
+        try (TableWriteImpl<?> write = builder.newWrite();
+                BatchTableCommit commit = builder.newCommit()) {
+            assertThatThrownBy(() -> write.writeAndReturn(rowData(1, 1, 1L), 
0, 0))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessageContaining("must be positive");
+            assertThatThrownBy(() -> write.writeAndReturn(rowData(1, 1, 1L), 
2, 2))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessageContaining("out of range");
+            write.writeAndReturn(rowData(1, 1, 1L), 0, 2);
+            assertThatThrownBy(() -> write.writeAndReturn(rowData(1, 3, 3L), 
1, 3))
+                    .hasMessageContaining("new bucket num 3")
+                    .hasMessageContaining("previous bucket num is 2");
+            write.writeAndReturn(rowData(2, 2, 2L), 2, 3);
+            messages = write.prepareCommit();
+            assertThat(messages)
+                    .extracting(message -> ((CommitMessageImpl) 
message).totalBuckets())
+                    .containsExactlyInAnyOrder(2, 3);
+            commit.commit(messages);
+        }
+
+        assertThat(PostponeUtils.getKnownNumBuckets(table))
+                .containsEntry(binaryRow(1), 2)
+                .containsEntry(binaryRow(2), 3);
+
+        try (TableWriteImpl<?> write = builder.newWrite()) {
+            assertThatThrownBy(() -> write.writeAndReturn(rowData(1, 3, 3L), 
0, 3))
+                    .hasMessageContaining("new bucket num 3")
+                    .hasMessageContaining("previous bucket num is 2");
+        }
+    }
+
     @ParameterizedTest(name = "format-{0}")
     @ValueSource(strings = {"avro", "parquet"})
     public void testStatsModePerLevel(String format) throws Exception {
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSink.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSink.java
index b323301c79..5d00584606 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSink.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSink.java
@@ -136,14 +136,10 @@ public abstract class FlinkSink<T> implements 
Serializable {
         StreamExecutionEnvironment env = input.getExecutionEnvironment();
         boolean isStreaming = isStreaming(input);
 
-        boolean writeOnly = table.coreOptions().writeOnly();
+        boolean writeOnly = writeOnly();
         StoreSinkWrite.Provider writeProvider =
-                StoreSinkWrite.createWriteProvider(
-                        table,
-                        env.getCheckpointConfig(),
-                        isStreaming,
-                        ignorePreviousFiles,
-                        hasSinkMaterializer(input));
+                createWriteProvider(
+                        env.getCheckpointConfig(), isStreaming, 
hasSinkMaterializer(input));
         writeProvider =
                 StoreSinkWrite.withBlobDescriptorReaderFactory(
                         writeProvider, blobDescriptorReaderFactory);
@@ -199,6 +195,20 @@ public abstract class FlinkSink<T> implements Serializable 
{
         return written;
     }
 
+    protected boolean writeOnly() {
+        return table.coreOptions().writeOnly();
+    }
+
+    protected StoreSinkWrite.Provider createWriteProvider(
+            CheckpointConfig checkpointConfig, boolean isStreaming, boolean 
hasSinkMaterializer) {
+        return StoreSinkWrite.createWriteProvider(
+                table, checkpointConfig, isStreaming, ignorePreviousFiles, 
hasSinkMaterializer);
+    }
+
+    protected boolean ignorePreviousFiles() {
+        return ignorePreviousFiles;
+    }
+
     public DataStreamSink<?> doCommit(DataStream<Committable> written, String 
commitUser) {
         StreamExecutionEnvironment env = written.getExecutionEnvironment();
         CheckpointConfig checkpointConfig = env.getCheckpointConfig();
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSinkBuilder.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSinkBuilder.java
index 55ed3cf3a0..656a68db6e 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSinkBuilder.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/FlinkSinkBuilder.java
@@ -368,11 +368,8 @@ public class FlinkSinkBuilder {
                             new 
PostponeFixedBucketChannelComputer(table.schema(), knownNumBuckets),
                             parallelism);
 
-            FileStoreTable tableForWrite = 
PostponeUtils.tableForFixBucketWrite(table);
-
             return configureBlobDescriptorReaderFactory(
-                            new PostponeFixedBucketSink(
-                                    tableForWrite, overwritePartition, 
knownNumBuckets))
+                            new PostponeFixedBucketSink(table, 
overwritePartition, knownNumBuckets))
                     .sinkFrom(partitioned);
         }
     }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeBatchWriteOperator.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeBatchWriteOperator.java
index 5fc50542f0..a9438e1033 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeBatchWriteOperator.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeBatchWriteOperator.java
@@ -27,7 +27,6 @@ import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.flink.utils.RuntimeContextUtils;
 import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.table.FileStoreTable;
-import org.apache.paimon.table.sink.CommitMessageImpl;
 import org.apache.paimon.table.sink.RowPartitionKeyExtractor;
 import org.apache.paimon.table.sink.SinkRecord;
 
@@ -35,14 +34,9 @@ import 
org.apache.flink.streaming.api.operators.StreamOperatorParameters;
 
 import javax.annotation.Nullable;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
-import static org.apache.paimon.utils.Preconditions.checkNotNull;
-
 /** Writer to write {@link InternalRow} to postpone fixed bucket. */
 public class PostponeBatchWriteOperator extends 
StatelessRowDataStoreWriteOperator {
 
@@ -83,7 +77,6 @@ public class PostponeBatchWriteOperator extends 
StatelessRowDataStoreWriteOperat
         this.bucketKeyProjection =
                 CodeGenUtils.newProjection(
                         schema.logicalRowType(), 
schema.projection(schema.bucketKeys()));
-        ((StoreSinkWriteImpl) 
write).getWrite().getWrite().withIgnoreNumBucketCheck(true);
     }
 
     @Override
@@ -92,26 +85,6 @@ public class PostponeBatchWriteOperator extends 
StatelessRowDataStoreWriteOperat
         BinaryRow partition = partitionKeyExtractor.partition(row);
         int numBuckets = knownNumBuckets.computeIfAbsent(partition.copy(), p 
-> defaultNumBuckets);
         int bucket = bucketFunction.bucket(bucketKeyProjection.apply(row), 
numBuckets);
-        return write.write(row, bucket);
-    }
-
-    @Override
-    protected List<Committable> prepareCommit(boolean waitCompaction, long 
checkpointId)
-            throws IOException {
-        List<Committable> committables = new ArrayList<>();
-        for (Committable committable : super.prepareCommit(waitCompaction, 
checkpointId)) {
-            CommitMessageImpl message = (CommitMessageImpl) 
committable.commitMessage();
-            committables.add(
-                    new Committable(
-                            committable.checkpointId(),
-                            new CommitMessageImpl(
-                                    message.partition(),
-                                    message.bucket(),
-                                    
checkNotNull(knownNumBuckets.get(message.partition())),
-                                    message.newFilesIncrement(),
-                                    message.compactIncrement())));
-        }
-
-        return committables;
+        return write.write(row, bucket, numBuckets);
     }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputer.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputer.java
index e1bf08d843..8e1c7325f2 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputer.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputer.java
@@ -18,6 +18,7 @@
 
 package org.apache.paimon.flink.sink;
 
+import org.apache.paimon.CoreOptions;
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.schema.TableSchema;
@@ -36,6 +37,7 @@ public class PostponeFixedBucketChannelComputer implements 
ChannelComputer<Inter
 
     private final TableSchema schema;
     private final Map<BinaryRow, Integer> knownNumBuckets;
+    private final int maxNumBuckets;
 
     private transient int numChannels;
     private transient FixedBucketRowKeyExtractor keyExtractor;
@@ -44,6 +46,8 @@ public class PostponeFixedBucketChannelComputer implements 
ChannelComputer<Inter
             TableSchema schema, Map<BinaryRow, Integer> knownNumBuckets) {
         this.schema = schema;
         this.knownNumBuckets = knownNumBuckets;
+        this.maxNumBuckets =
+                new 
CoreOptions(schema.options()).postponeBatchWriteFixedBucketMaxParallelism();
     }
 
     @Override
@@ -56,7 +60,9 @@ public class PostponeFixedBucketChannelComputer implements 
ChannelComputer<Inter
     public int channel(InternalRow record) {
         keyExtractor.setRecord(record);
         BinaryRow partition = keyExtractor.partition();
-        int numBuckets = knownNumBuckets.computeIfAbsent(partition, p -> 
numChannels);
+        int numBuckets =
+                knownNumBuckets.computeIfAbsent(
+                        partition.copy(), p -> Math.min(numChannels, 
maxNumBuckets));
         int bucket = keyExtractor.bucket(numBuckets);
         return ChannelComputer.select(partition, bucket, numChannels);
     }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketSink.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketSink.java
index 130804f73c..ad7d78cdff 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketSink.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/PostponeFixedBucketSink.java
@@ -22,8 +22,8 @@ import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.manifest.ManifestCommittable;
 import org.apache.paimon.table.FileStoreTable;
-import org.apache.paimon.table.PostponeUtils;
 
+import org.apache.flink.streaming.api.environment.CheckpointConfig;
 import org.apache.flink.streaming.api.operators.OneInputStreamOperatorFactory;
 import org.apache.flink.streaming.api.operators.StreamOperator;
 import org.apache.flink.streaming.api.operators.StreamOperatorParameters;
@@ -65,31 +65,28 @@ public class PostponeFixedBucketSink extends 
FlinkWriteSink<InternalRow> {
         return createRestoreOnlyCommittableStateManager(table);
     }
 
+    @Override
+    protected boolean writeOnly() {
+        return true;
+    }
+
+    @Override
+    protected StoreSinkWrite.Provider createWriteProvider(
+            CheckpointConfig checkpointConfig, boolean isStreaming, boolean 
hasSinkMaterializer) {
+        return StoreSinkWrite.createPostponeFixedBucketWriteProvider(
+                ignorePreviousFiles(), isStreaming, hasSinkMaterializer);
+    }
+
     @Override
     protected Committer.Factory<Committable, ManifestCommittable> 
createCommitterFactory() {
-        if (overwritePartition == null) {
-            // The table has copied bucket option outside, no need to change.
-            return context ->
-                    new StoreCommitter(
-                            table,
-                            table.newCommit(context.commitUser())
-                                    .withOverwrite(overwritePartition)
-                                    
.ignoreEmptyCommit(!context.streamingCheckpointEnabled())
-                                    // Need to check conflict
-                                    .appendCommitCheckConflict(true),
-                            context);
-        } else {
-            // When overwriting, the postpone bucket files need to be deleted, 
so using a postpone
-            // bucket table commit here
-            FileStoreTable tableForCommit = 
PostponeUtils.tableForCommit(table);
-            return context ->
-                    new StoreCommitter(
-                            tableForCommit,
-                            tableForCommit
-                                    .newCommit(context.commitUser())
-                                    .withOverwrite(overwritePartition)
-                                    
.ignoreEmptyCommit(!context.streamingCheckpointEnabled()),
-                            context);
-        }
+        return context ->
+                new StoreCommitter(
+                        table,
+                        table.newPostponeFixedBucketWriteBuilder()
+                                .withOverwrite(overwritePartition)
+                                .newCommit(
+                                        context.commitUser(),
+                                        !context.streamingCheckpointEnabled()),
+                        context);
     }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWrite.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWrite.java
index 5aee320f6e..474a996db6 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWrite.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWrite.java
@@ -59,6 +59,9 @@ public interface StoreSinkWrite {
     @Nullable
     SinkRecord write(InternalRow rowData, int bucket) throws Exception;
 
+    @Nullable
+    SinkRecord write(InternalRow rowData, int bucket, int totalBuckets) throws 
Exception;
+
     void compact(BinaryRow partition, int bucket, boolean fullCompaction) 
throws Exception;
 
     void notifyNewFiles(long snapshotId, BinaryRow partition, int bucket, 
List<DataFileMeta> files);
@@ -203,4 +206,29 @@ public interface StoreSinkWrite {
                     metricGroup);
         };
     }
+
+    static StoreSinkWrite.Provider createPostponeFixedBucketWriteProvider(
+            boolean ignorePreviousFiles, boolean isStreaming, boolean 
hasSinkMaterializer) {
+        return (table, commitUser, state, ioManager, memoryPoolFactory, 
metricGroup) -> {
+            Preconditions.checkArgument(
+                    !hasSinkMaterializer,
+                    String.format(
+                            "Sink materializer must not be used with Paimon 
sink. "
+                                    + "Please set '%s' to '%s' in Flink's 
config.",
+                            
ExecutionConfigOptions.TABLE_EXEC_SINK_UPSERT_MATERIALIZE.key(),
+                            
ExecutionConfigOptions.UpsertMaterialize.NONE.name()));
+            return new StoreSinkWriteImpl(
+                    table,
+                    commitUser,
+                    state,
+                    ioManager,
+                    ignorePreviousFiles,
+                    false,
+                    isStreaming,
+                    memoryPoolFactory,
+                    metricGroup,
+                    (t, user, writeId) ->
+                            
t.newPostponeFixedBucketWriteBuilder().newWrite(user, writeId));
+        };
+    }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWriteImpl.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWriteImpl.java
index e314b997c5..797f6ad86d 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWriteImpl.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreSinkWriteImpl.java
@@ -57,6 +57,7 @@ public class StoreSinkWriteImpl implements StoreSinkWrite {
     private final boolean isStreamingMode;
     private final MemoryPoolFactory memoryPoolFactory;
     @Nullable private final MetricGroup metricGroup;
+    private final TableWriteFactory tableWriteFactory;
 
     @Nullable private UriReaderFactory blobDescriptorReaderFactory;
 
@@ -72,6 +73,30 @@ public class StoreSinkWriteImpl implements StoreSinkWrite {
             boolean isStreamingMode,
             MemoryPoolFactory memoryPoolFactory,
             @Nullable MetricGroup metricGroup) {
+        this(
+                table,
+                commitUser,
+                state,
+                ioManager,
+                ignorePreviousFiles,
+                waitCompaction,
+                isStreamingMode,
+                memoryPoolFactory,
+                metricGroup,
+                FileStoreTable::newWrite);
+    }
+
+    StoreSinkWriteImpl(
+            FileStoreTable table,
+            String commitUser,
+            StoreSinkWriteState state,
+            IOManager ioManager,
+            boolean ignorePreviousFiles,
+            boolean waitCompaction,
+            boolean isStreamingMode,
+            MemoryPoolFactory memoryPoolFactory,
+            @Nullable MetricGroup metricGroup,
+            TableWriteFactory tableWriteFactory) {
         this.commitUser = commitUser;
         this.state = state;
         this.paimonIOManager = new 
IOManagerImpl(ioManager.getSpillingDirectoriesPaths());
@@ -80,12 +105,14 @@ public class StoreSinkWriteImpl implements StoreSinkWrite {
         this.isStreamingMode = isStreamingMode;
         this.memoryPoolFactory = memoryPoolFactory;
         this.metricGroup = metricGroup;
+        this.tableWriteFactory = tableWriteFactory;
         this.write = newTableWrite(table);
     }
 
     private TableWriteImpl<?> newTableWrite(FileStoreTable table) {
         TableWriteImpl<?> tableWrite =
-                table.newWrite(commitUser, state.getSubtaskId())
+                tableWriteFactory
+                        .create(table, commitUser, state.getSubtaskId())
                         .withIOManager(paimonIOManager)
                         .withIgnorePreviousFiles(ignorePreviousFiles)
                         .withMemoryPoolFactory(memoryPoolFactory);
@@ -122,6 +149,12 @@ public class StoreSinkWriteImpl implements StoreSinkWrite {
         return write.writeAndReturn(withBlobDescriptorReader(rowData), bucket);
     }
 
+    @Override
+    @Nullable
+    public SinkRecord write(InternalRow rowData, int bucket, int totalBuckets) 
throws Exception {
+        return write.writeAndReturn(withBlobDescriptorReader(rowData), bucket, 
totalBuckets);
+    }
+
     private InternalRow withBlobDescriptorReader(InternalRow rowData) {
         return blobDescriptorReaderFactory == null
                 ? rowData
@@ -198,4 +231,11 @@ public class StoreSinkWriteImpl implements StoreSinkWrite {
     public TableWriteImpl<?> getWrite() {
         return write;
     }
+
+    @FunctionalInterface
+    interface TableWriteFactory {
+
+        TableWriteImpl<?> create(
+                FileStoreTable table, String commitUser, @Nullable Integer 
writeId);
+    }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputerTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputerTest.java
index 991ab5d2dd..be05255938 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputerTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/PostponeFixedBucketChannelComputerTest.java
@@ -166,4 +166,34 @@ public class PostponeFixedBucketChannelComputerTest {
             
assertThat(computer.channel(row1)).isEqualTo(computer.channel(row2));
         }
     }
+
+    @Test
+    public void testDefaultBucketNumberRespectsMaxParallelism() throws 
Exception {
+        RowType rowType =
+                RowType.of(
+                        new DataType[] {DataTypes.INT(), DataTypes.BIGINT()},
+                        new String[] {"pt", "k"});
+
+        SchemaManager schemaManager =
+                new SchemaManager(LocalFileIO.create(), new 
Path(tempDir.toString()));
+        Map<String, String> options = new HashMap<>();
+        options.put("bucket", "-2");
+        options.put("postpone.batch-write-fixed-bucket.max-parallelism", "2");
+        TableSchema schema =
+                schemaManager.createTable(
+                        new Schema(
+                                rowType.getFields(),
+                                Collections.singletonList("pt"),
+                                Arrays.asList("pt", "k"),
+                                options,
+                                ""));
+
+        Map<BinaryRow, Integer> knownNumBuckets = new HashMap<>();
+        PostponeFixedBucketChannelComputer computer =
+                new PostponeFixedBucketChannelComputer(schema, 
knownNumBuckets);
+        computer.setup(8);
+        computer.channel(GenericRow.of(1, 1L));
+
+        assertThat(knownNumBuckets).containsValue(2);
+    }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/StoreCompactOperatorTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/StoreCompactOperatorTest.java
index 5e2cb10e0e..cf78c43192 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/StoreCompactOperatorTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/StoreCompactOperatorTest.java
@@ -326,6 +326,11 @@ public class StoreCompactOperatorTest extends 
TableTestBase {
             return null;
         }
 
+        @Override
+        public SinkRecord write(InternalRow rowData, int bucket, int 
totalBuckets) {
+            return null;
+        }
+
         @Override
         public void compact(BinaryRow partition, int bucket, boolean 
fullCompaction) {
             compactTime++;
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/commands/PaimonSparkWriter.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/commands/PaimonSparkWriter.scala
index aaba0f1ca0..e1301703da 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/commands/PaimonSparkWriter.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/commands/PaimonSparkWriter.scala
@@ -88,12 +88,11 @@ case class PaimonSparkWriter(
     table.bucketMode() == POSTPONE_MODE && 
coreOptions.postponeBatchWriteFixedBucket()
 
   val writeBuilder: BatchWriteBuilder = {
-    val tableForWrite = if (postponeBatchWriteFixedBucket) {
-      PostponeUtils.tableForFixBucketWrite(table)
+    if (postponeBatchWriteFixedBucket) {
+      table.newPostponeFixedBucketWriteBuilder()
     } else {
-      table
+      table.newBatchWriteBuilder()
     }
-    tableForWrite.newBatchWriteBuilder()
   }
 
   def writeOnly(): PaimonSparkWriter = {
@@ -438,16 +437,7 @@ case class PaimonSparkWriter(
   }
 
   def commit(commitMessages: Seq[CommitMessage], operation: 
Snapshot.Operation): Unit = {
-    val finalWriteBuilder = if (postponeBatchWriteFixedBucket) {
-      writeBuilder
-        .asInstanceOf[BatchWriteBuilderImpl]
-        .copyWithNewTable(PostponeUtils.tableForCommit(table))
-        // Need to check conflict
-        .appendCommitCheckConflict(true)
-    } else {
-      writeBuilder
-    }
-    val tableCommit = finalWriteBuilder.newCommit()
+    val tableCommit = writeBuilder.newCommit()
     if (operation != null) {
       tableCommit.withOperation(operation)
     }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonDataWrite.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonDataWrite.scala
index a954bd64b2..af20144f52 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonDataWrite.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonDataWrite.scala
@@ -50,9 +50,6 @@ case class PaimonDataWrite(
     if (writeRowTracking) {
       _write.withWriteType(writeType)
     }
-    if (postponePartitionBucketComputer.isDefined) {
-      _write.getWrite.withIgnoreNumBucketCheck(true)
-    }
     _write
   }
 
@@ -65,27 +62,17 @@ case class PaimonDataWrite(
   }
 
   def write(row: Row, bucket: Int): Unit = {
-    postWrite(write.writeAndReturn(toPaimonRow(row), bucket))
+    val paimonRow = toPaimonRow(row)
+    val sinkRecord = postponePartitionBucketComputer match {
+      case Some(numBuckets) =>
+        write.writeAndReturn(paimonRow, bucket, 
numBuckets(write.getPartition(paimonRow)))
+      case None => write.writeAndReturn(paimonRow, bucket)
+    }
+    postWrite(sinkRecord)
   }
 
   override def commitImpl(): Seq[CommitMessage] = {
-    val commitMessages = write.prepareCommit().asScala.toSeq
-
-    if (postponePartitionBucketComputer.isDefined) {
-      commitMessages.map {
-        case message: CommitMessageImpl =>
-          new CommitMessageImpl(
-            message.partition(),
-            message.bucket(),
-            postponePartitionBucketComputer.get.apply(message.partition()),
-            message.newFilesIncrement(),
-            message.compactIncrement()
-          )
-        case _ => throw new RuntimeException()
-      }
-    } else {
-      commitMessages
-    }
+    write.prepareCommit().asScala.toSeq
   }
 
   override def close(): Unit = {

Reply via email to