diff --git a/docs/docs/primary-key-table/data-distribution.md b/docs/docs/primary-key-table/data-distribution.md
index b7a0fecb7c64..360febf904d9 100644
--- a/docs/docs/primary-key-table/data-distribution.md
+++ b/docs/docs/primary-key-table/data-distribution.md
@@ -70,16 +70,18 @@ Postpone bucket mode is configured by `'bucket' = '-2'`.
This mode aims to solve the difficulty to determine a fixed number of buckets
and support different buckets for different partitions.
-By default, `postpone.batch-write-fixed-bucket` is `true`. This staged fixed-bucket flow uses
-Spark's DataSource V1 write path, even when `spark.paimon.write.use-v2-write` is enabled. Spark
-completes each batch in three steps:
+By default, `postpone.batch-write-fixed-bucket` is `true`. The fixed-bucket flow uses Spark's
+DataSource V1 write path, even when `spark.paimon.write.use-v2-write` is enabled. Unless direct
+writing applies, Spark completes each batch in three steps:
1. Write the current batch to uncommitted bucket `-2` files. Spark derives each partition's row
count and file size directly from the staged file metadata; there is no extra input scan, cache,
or per-row statistics pass.
-2. Calculate the required bucket number per touched partition. `postpone.target-row-num-per-bucket`,
- when configured, takes precedence over `postpone.target-size-per-bucket` (default `1 GB`). The
- result is at least `1`, rounded up to a power of two, and capped by
+2. Calculate the required bucket number per touched partition. For a partition without real
+ buckets, an explicitly configured `postpone.default-bucket-num` is used exactly. Otherwise,
+ `postpone.target-row-num-per-bucket`, when configured, takes precedence over
+ `postpone.target-size-per-bucket` (default `1 GB`). An inferred result is at least `1`, rounded
+ up to a power of two, and capped by
`postpone.batch-write-fixed-bucket.max-parallelism`.
3. Route the staged records to real buckets and commit them. The current batch becomes visible only
in this commit.
@@ -91,6 +93,14 @@ larger than the existing layout. Different partitions may have different target
The rescale is a separate overwrite commit which changes real buckets only; the current batch is
appended in the following commit.
+`postpone.default-bucket-num` has no default value. When it is explicitly configured, Spark can
+skip the staged bucket `-2` files and write directly to real buckets for `INSERT OVERWRITE`, or
+when the base snapshot contains no real buckets. An overwrite always uses the configured number
+exactly and does not rescale the replaced layout. An append to an existing real-bucket partition
+ignores this option and still uses the staged batch to decide whether rescaling is required. If a
+batch mixes existing and new real-bucket partitions, the whole batch remains staged; only the new
+partitions use the configured default.
+
Previously committed bucket `-2` files are not included in the calculation, read, rewritten, or
deleted by an append or rescale. They remain available to merge-on-read and regular postpone
compaction. `INSERT OVERWRITE` still follows its normal replacement semantics.
@@ -100,14 +110,13 @@ records are first stored in the `bucket-postpone` directory of each partition
and are not available to readers.
To move these records into the correct bucket and make them readable, 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.
+The bucket number for partitions compacted for the first time can be configured by the option
+`postpone.default-bucket-num`. Its value is used exactly and takes precedence over automatic
+estimation. Otherwise, `postpone.target-row-num-per-bucket`, when configured, calculates the
+bucket number as `ceil(row_count / target_row_count)`. If it is not configured, Paimon calculates
+the bucket number as `ceil(postpone_file_size / postpone.target-size-per-bucket)`; the target size
+defaults to `1 GB`. Both estimates are at least `1`. Execution parallelism does not determine the
+logical bucket number.
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,
diff --git a/docs/generated/core_configuration.html b/docs/generated/core_configuration.html
index 0c0c1a0a8d46..a7c8114dd8b4 100644
--- a/docs/generated/core_configuration.html
+++ b/docs/generated/core_configuration.html
@@ -1316,9 +1316,9 @@
postpone.default-bucket-num |
- 1 |
+ (none) |
Integer |
- Bucket number for the partitions compacted for the first time in postpone bucket tables. |
+ Optional bucket number for partitions receiving real buckets for the first time and for fixed-bucket overwrite writes. The configured value is used exactly and takes precedence over automatic bucket estimation. When unset, Paimon estimates the bucket number from the target row count or target file size. |
postpone.merge-on-read |
@@ -1330,13 +1330,13 @@
postpone.target-row-num-per-bucket |
(none) |
Long |
- Target row number per bucket when estimating the required bucket number from the current staged batch or compacting postpone bucket files. |
+ Target postpone row count per bucket when estimating the required bucket number from staged or committed postpone files. When configured, this option takes precedence over 'postpone.target-size-per-bucket'. |
postpone.target-size-per-bucket |
1 gb |
MemorySize |
- Target staged file size per bucket when Spark estimates the required bucket number from the current staged batch. This option is ignored when 'postpone.target-row-num-per-bucket' is configured. |
+ Target postpone file size per bucket when estimating the required bucket number from staged or committed postpone files. This option is ignored when 'postpone.target-row-num-per-bucket' is configured. |
primary-key |
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 96fdbf731b0b..3be4d072e121 100644
--- a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
@@ -2766,23 +2766,23 @@ public String toString() {
public static final ConfigOption POSTPONE_DEFAULT_BUCKET_NUM =
key("postpone.default-bucket-num")
.intType()
- .defaultValue(1)
+ .noDefaultValue()
.withDescription(
- "Bucket number for the partitions compacted for the first time in postpone bucket tables.");
+ "Optional bucket number for partitions receiving real buckets for the first time and for fixed-bucket overwrite writes. The configured value is used exactly and takes precedence over automatic bucket estimation. When unset, Paimon estimates the bucket number from the target row count or target file size.");
public static final ConfigOption POSTPONE_TARGET_ROW_NUM_PER_BUCKET =
key("postpone.target-row-num-per-bucket")
.longType()
.noDefaultValue()
.withDescription(
- "Target row number per bucket when estimating the required bucket number from the current staged batch or compacting postpone bucket files.");
+ "Target postpone row count per bucket when estimating the required bucket number from staged or committed postpone files. When configured, this option takes precedence over 'postpone.target-size-per-bucket'.");
public static final ConfigOption POSTPONE_TARGET_SIZE_PER_BUCKET =
key("postpone.target-size-per-bucket")
.memoryType()
.defaultValue(MemorySize.parse("1 gb"))
.withDescription(
- "Target staged file size per bucket when Spark estimates the required bucket number from the current staged batch. "
+ "Target postpone file size per bucket when estimating the required bucket number from staged or committed postpone files. "
+ "This option is ignored when 'postpone.target-row-num-per-bucket' is configured.");
public static final ConfigOption GLOBAL_INDEX_ROW_COUNT_PER_SHARD =
@@ -4465,8 +4465,15 @@ public int postponeBatchWriteFixedBucketRescaleLoadFactor() {
return options.get(POSTPONE_BATCH_WRITE_FIXED_BUCKET_RESCALE_LOAD_FACTOR);
}
- public int postponeDefaultBucketNum() {
- return options.get(POSTPONE_DEFAULT_BUCKET_NUM);
+ public Optional postponeDefaultBucketNum() {
+ Optional bucketNum = options.getOptional(POSTPONE_DEFAULT_BUCKET_NUM);
+ bucketNum.ifPresent(
+ value ->
+ checkArgument(
+ value > 0,
+ "Option '%s' must be greater than 0.",
+ POSTPONE_DEFAULT_BUCKET_NUM.key()));
+ return bucketNum;
}
public Optional postponeTargetRowNumPerBucket() {
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 0d2a77ad0b54..7ea59f78a99f 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
@@ -127,51 +127,59 @@ public static List groupPostponeFiles(List splits) {
return result;
}
- public static PostponeBucketAssigner createPostponeBucketAssigner(
- FileStoreTable table, long snapshotId, int defaultParallelism) {
- return loadPostponeBucketAssigner(table, snapshotId, defaultParallelism, null);
- }
-
- private static PostponeBucketAssigner loadPostponeBucketAssigner(
- FileStoreTable table,
- long snapshotId,
- int defaultParallelism,
- @Nullable PartitionPredicate partitionFilter) {
- Map knownNumBuckets =
- getKnownNumBuckets(table, snapshotId, partitionFilter);
- Map postponeRowCounts =
- !table.coreOptions().postponeTargetRowNumPerBucket().isPresent()
- ? Collections.emptyMap()
- : getPostponeRowCounts(table, snapshotId, partitionFilter);
- Long targetRowNumPerBucket =
- table.coreOptions().postponeTargetRowNumPerBucket().orElse(null);
- int defaultBucketNum =
- table.coreOptions()
- .toConfiguration()
- .contains(CoreOptions.POSTPONE_DEFAULT_BUCKET_NUM)
- ? table.coreOptions().postponeDefaultBucketNum()
- : defaultParallelism;
- return new PostponeBucketAssigner(
- knownNumBuckets, targetRowNumPerBucket, postponeRowCounts, defaultBucketNum);
- }
-
- /** Creates snapshot-bound routing metadata. */
+ public static PostponeBucketNumResolver createPostponeBucketNumResolver(
+ FileStoreTable table, long snapshotId) {
+ return loadPostponeBucketNumResolver(table, snapshotId, null);
+ }
+
+ private static PostponeBucketNumResolver loadPostponeBucketNumResolver(
+ FileStoreTable table, long snapshotId, @Nullable List postponePartitions) {
+ CoreOptions options = table.coreOptions();
+ Map numBucketsByPartition =
+ postponePartitions == null
+ ? getKnownNumBuckets(table, snapshotId)
+ : getKnownNumBuckets(table, snapshotId, postponePartitions);
+ Integer configuredDefaultBucketNum = options.postponeDefaultBucketNum().orElse(null);
+ if (configuredDefaultBucketNum == null) {
+ Optional targetRowNumPerBucket = options.postponeTargetRowNumPerBucket();
+ if (targetRowNumPerBucket.isPresent()) {
+ checkArgument(
+ targetRowNumPerBucket.get() > 0,
+ "Option '%s' must be greater than 0.",
+ CoreOptions.POSTPONE_TARGET_ROW_NUM_PER_BUCKET.key());
+ addEstimatedBucketNums(
+ numBucketsByPartition,
+ getPostponeRowCounts(
+ postponeFileIterator(table, snapshotId, postponePartitions)),
+ targetRowNumPerBucket.get(),
+ CoreOptions.POSTPONE_TARGET_ROW_NUM_PER_BUCKET.key());
+ } else {
+ long targetSizePerBucket = options.postponeTargetSizePerBucket();
+ checkArgument(
+ targetSizePerBucket > 0,
+ "Option '%s' must be greater than 0.",
+ CoreOptions.POSTPONE_TARGET_SIZE_PER_BUCKET.key());
+ addEstimatedBucketNums(
+ numBucketsByPartition,
+ getPostponeFileSizes(
+ postponeFileIterator(table, snapshotId, postponePartitions)),
+ targetSizePerBucket,
+ CoreOptions.POSTPONE_TARGET_SIZE_PER_BUCKET.key());
+ }
+ }
+ return new PostponeBucketNumResolver(numBucketsByPartition, configuredDefaultBucketNum);
+ }
+
+ /** Creates snapshot-bound routing metadata for partitions containing postpone files. */
public static PostponeBucketRouter createPostponeBucketRouter(
- FileStoreTable table,
- long snapshotId,
- int defaultParallelism,
- @Nullable PartitionPredicate partitionFilter) {
+ FileStoreTable table, long snapshotId, List postponePartitions) {
return newPostponeBucketRouter(
- table,
- loadPostponeBucketAssigner(table, snapshotId, defaultParallelism, partitionFilter));
+ table, loadPostponeBucketNumResolver(table, snapshotId, postponePartitions));
}
/** Creates routing metadata from bucket numbers decided by an execution engine. */
public static PostponeBucketRouter createPostponeBucketRouter(
- FileStoreTable table,
- Map numBucketsByPartition,
- int defaultBucketNum) {
- checkArgument(defaultBucketNum > 0, "Default postpone bucket number must be positive.");
+ FileStoreTable table, Map numBucketsByPartition) {
Map copied = new HashMap<>();
for (Map.Entry entry : numBucketsByPartition.entrySet()) {
checkArgument(
@@ -179,13 +187,11 @@ public static PostponeBucketRouter createPostponeBucketRouter(
"Postpone bucket number must be positive.");
copied.put(entry.getKey().copy(), entry.getValue());
}
- return newPostponeBucketRouter(
- table,
- new PostponeBucketAssigner(copied, null, Collections.emptyMap(), defaultBucketNum));
+ return newPostponeBucketRouter(table, new PostponeBucketNumResolver(copied, null));
}
private static PostponeBucketRouter newPostponeBucketRouter(
- FileStoreTable table, PostponeBucketAssigner bucketAssigner) {
+ FileStoreTable table, PostponeBucketNumResolver bucketNumResolver) {
List trimmedPrimaryKeys = table.schema().trimmedPrimaryKeys();
int[] bucketKeyMapping =
table.schema().bucketKeys().stream()
@@ -202,7 +208,7 @@ private static PostponeBucketRouter newPostponeBucketRouter(
PrimaryKeyTableUtils.PrimaryKeyFieldsExtractor.EXTRACTOR.keyFields(
table.schema()));
return new PostponeBucketRouter(
- bucketAssigner,
+ bucketNumResolver,
keyType,
table.schema().logicalBucketKeyType(),
bucketKeyMapping,
@@ -210,52 +216,57 @@ private static PostponeBucketRouter newPostponeBucketRouter(
}
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.");
- }
+ return computeBucketNum(
+ rowCount,
+ targetRowNumPerBucket,
+ CoreOptions.POSTPONE_TARGET_ROW_NUM_PER_BUCKET.key());
+ }
- long bucketNum = rowCount <= 0 ? 1 : (rowCount - 1) / targetRowNumPerBucket + 1;
+ private static int computeBucketNum(
+ long value, long targetValuePerBucket, String targetOptionKey) {
+ checkArgument(
+ targetValuePerBucket > 0, "Option '%s' must be greater than 0.", targetOptionKey);
+ long bucketNum = value <= 0 ? 1 : (value - 1) / targetValuePerBucket + 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' "
+ + "). Consider increasing '"
+ + targetOptionKey
+ + "' "
+ "to reduce the bucket count.");
}
return (int) bucketNum;
}
- public static int determineBucketNum(
- BinaryRow partition,
- Map knownNumBuckets,
- Optional targetRowNumPerBucket,
- Map postponeRowCounts,
- int defaultBucketNum) {
- return determineBucketNum(
- partition,
- knownNumBuckets,
- targetRowNumPerBucket.orElse(null),
- postponeRowCounts,
- defaultBucketNum);
- }
-
- public static int determineBucketNum(
+ private static void addEstimatedBucketNums(
+ Map numBucketsByPartition,
+ Map valuesByPartition,
+ long targetValuePerBucket,
+ String targetOptionKey) {
+ for (Map.Entry entry : valuesByPartition.entrySet()) {
+ if (!numBucketsByPartition.containsKey(entry.getKey())) {
+ numBucketsByPartition.put(
+ entry.getKey(),
+ computeBucketNum(entry.getValue(), targetValuePerBucket, targetOptionKey));
+ }
+ }
+ }
+
+ static int determineBucketNum(
BinaryRow partition,
- Map knownNumBuckets,
- @Nullable Long targetRowNumPerBucket,
- Map postponeRowCounts,
- int defaultBucketNum) {
- Integer knownBucketNum = knownNumBuckets.get(partition);
- if (knownBucketNum != null) {
- return knownBucketNum;
- } else if (targetRowNumPerBucket != null) {
- return computeBucketNumByRowCount(
- postponeRowCounts.getOrDefault(partition, 0L), targetRowNumPerBucket);
+ Map numBucketsByPartition,
+ @Nullable Integer configuredDefaultBucketNum) {
+ Integer numBuckets = numBucketsByPartition.get(partition);
+ if (numBuckets != null) {
+ return numBuckets;
+ } else if (configuredDefaultBucketNum != null) {
+ return configuredDefaultBucketNum;
} else {
- return defaultBucketNum;
+ throw new IllegalArgumentException(
+ "Missing postpone bucket number for partition " + partition + ".");
}
}
@@ -278,6 +289,11 @@ public static FixedBucketDecision decideFixedBucketNum(
existingBucketNum == null || existingBucketNum > 0,
"Existing bucket number must be positive.");
+ Optional configuredDefaultBucketNum = options.postponeDefaultBucketNum();
+ if (existingBucketNum == null && configuredDefaultBucketNum.isPresent()) {
+ return new FixedBucketDecision(configuredDefaultBucketNum.get(), false);
+ }
+
int maxBucketNum = options.postponeBatchWriteFixedBucketMaxParallelism();
checkArgument(
maxBucketNum > 0,
@@ -453,48 +469,69 @@ private static Map getPostponeRowCounts(Iterator
return rowCounts;
}
- public static FileStoreTable tableForPostponeCompact(
+ private static Iterator postponeFileIterator(
+ FileStoreTable table, long snapshotId, @Nullable List postponePartitions) {
+ SnapshotReader reader =
+ table.newSnapshotReader()
+ .withSnapshot(snapshotId)
+ .withBucket(BucketMode.POSTPONE_BUCKET);
+ if (postponePartitions != null) {
+ reader.withPartitionFilter(postponePartitions);
+ }
+ return reader.readFileIterator();
+ }
+
+ static Map getPostponeFileSizes(
+ FileStoreTable table, long snapshotId, @Nullable PartitionPredicate partitionFilter) {
+ SnapshotReader reader =
+ table.newSnapshotReader()
+ .withSnapshot(snapshotId)
+ .withBucket(BucketMode.POSTPONE_BUCKET);
+ if (partitionFilter != null) {
+ reader.withPartitionFilter(partitionFilter);
+ }
+
+ return getPostponeFileSizes(reader.readFileIterator());
+ }
+
+ private static Map getPostponeFileSizes(Iterator iterator) {
+ Map fileSizes = new HashMap<>();
+ while (iterator.hasNext()) {
+ ManifestEntry entry = iterator.next();
+ fileSizes.merge(
+ entry.partition(),
+ entry.file().fileSize(),
+ (left, right) -> Math.addExact(left, right));
+ }
+ return fileSizes;
+ }
+
+ public static FileStoreTable tableForPostponeRewrite(
FileStoreTable table, int numBuckets, long snapshotId) {
- Map compactOptions = new HashMap<>();
- compactOptions.put(BUCKET.key(), String.valueOf(numBuckets));
- compactOptions.put(WRITE_ONLY.key(), "false");
- compactOptions.put(COMMIT_STRICT_MODE_LAST_SAFE_SNAPSHOT.key(), String.valueOf(snapshotId));
- return table.copy(compactOptions);
+ Map rewriteOptions = new HashMap<>();
+ rewriteOptions.put(BUCKET.key(), String.valueOf(numBuckets));
+ rewriteOptions.put(WRITE_ONLY.key(), "false");
+ rewriteOptions.put(COMMIT_STRICT_MODE_LAST_SAFE_SNAPSHOT.key(), String.valueOf(snapshotId));
+ return table.copy(rewriteOptions);
}
- /** Snapshot-bound bucket-count assignment. */
- public static final class PostponeBucketAssigner implements Serializable {
+ /** Resolves the snapshot-bound bucket count of a partition. */
+ public static final class PostponeBucketNumResolver implements Serializable {
private static final long serialVersionUID = 1L;
- private final Map knownNumBuckets;
- @Nullable private final Long targetRowNumPerBucket;
- private final Map postponeRowCounts;
- private final int defaultBucketNum;
+ private final Map numBucketsByPartition;
+ @Nullable private final Integer configuredDefaultBucketNum;
- private PostponeBucketAssigner(
- Map knownNumBuckets,
- @Nullable Long targetRowNumPerBucket,
- Map postponeRowCounts,
- int defaultBucketNum) {
- this.knownNumBuckets = knownNumBuckets;
- this.targetRowNumPerBucket = targetRowNumPerBucket;
- this.postponeRowCounts = postponeRowCounts;
- this.defaultBucketNum = defaultBucketNum;
+ private PostponeBucketNumResolver(
+ Map numBucketsByPartition,
+ @Nullable Integer configuredDefaultBucketNum) {
+ this.numBucketsByPartition = numBucketsByPartition;
+ this.configuredDefaultBucketNum = configuredDefaultBucketNum;
}
- public int assign(BinaryRow partition) {
- return determineBucketNum(
- partition,
- knownNumBuckets,
- targetRowNumPerBucket,
- postponeRowCounts,
- defaultBucketNum);
- }
-
- private PostponeBucketAssigner withDefaultBucketNum(int newDefaultBucketNum) {
- return new PostponeBucketAssigner(
- knownNumBuckets, targetRowNumPerBucket, postponeRowCounts, newDefaultBucketNum);
+ public int numBuckets(BinaryRow partition) {
+ return determineBucketNum(partition, numBucketsByPartition, configuredDefaultBucketNum);
}
}
@@ -523,7 +560,7 @@ public static final class PostponeBucketRouter implements Serializable {
private static final long serialVersionUID = 1L;
- private final PostponeBucketAssigner bucketAssigner;
+ private final PostponeBucketNumResolver bucketNumResolver;
private final RowType keyType;
private final RowType bucketKeyType;
private final int[] bucketKeyMapping;
@@ -532,12 +569,12 @@ public static final class PostponeBucketRouter implements Serializable {
@Nullable private transient BucketFunction bucketFunction;
private PostponeBucketRouter(
- PostponeBucketAssigner bucketAssigner,
+ PostponeBucketNumResolver bucketNumResolver,
RowType keyType,
RowType bucketKeyType,
int[] bucketKeyMapping,
CoreOptions.BucketFunctionType bucketFunctionType) {
- this.bucketAssigner = bucketAssigner;
+ this.bucketNumResolver = bucketNumResolver;
this.keyType = keyType;
this.bucketKeyType = bucketKeyType;
this.bucketKeyMapping = bucketKeyMapping;
@@ -556,18 +593,7 @@ public int bucket(BinaryRow partition, InternalRow trimmedPrimaryKey) {
}
public int numBuckets(BinaryRow partition) {
- return bucketAssigner.assign(partition);
- }
-
- public PostponeBucketRouter withDefaultBucketNum(int newDefaultBucketNum) {
- checkArgument(
- newDefaultBucketNum > 0, "Default postpone bucket number must be positive.");
- return new PostponeBucketRouter(
- bucketAssigner.withDefaultBucketNum(newDefaultBucketNum),
- keyType,
- bucketKeyType,
- bucketKeyMapping,
- bucketFunctionType);
+ return bucketNumResolver.numBuckets(partition);
}
}
diff --git a/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergePlan.java b/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergePlan.java
index a65e415ee07c..b2e6bc460e6f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergePlan.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergePlan.java
@@ -92,16 +92,6 @@ public long numPotentialBuckets() {
return numPotentialBuckets;
}
- PostponeMergePlan withDefaultBucketNum(int newDefaultBucketNum) {
- return new PostponeMergePlan(
- realSplits,
- postponeSplits,
- bucketRouter.withDefaultBucketNum(newDefaultBucketNum),
- keyType,
- resultReadType,
- mergeReadType);
- }
-
private static long numPotentialBuckets(
List realSplits,
List postponeSplits,
diff --git a/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergeReadBuilder.java b/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergeReadBuilder.java
index c0561c09db17..147d12e05a69 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergeReadBuilder.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/source/PostponeMergeReadBuilder.java
@@ -21,6 +21,7 @@
import org.apache.paimon.CoreOptions;
import org.apache.paimon.KeyValueFileStore;
import org.apache.paimon.Snapshot;
+import org.apache.paimon.data.BinaryRow;
import org.apache.paimon.metrics.MetricRegistry;
import org.apache.paimon.partition.PartitionPredicate;
import org.apache.paimon.predicate.Predicate;
@@ -40,6 +41,7 @@
import java.io.Serializable;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -63,7 +65,6 @@ public final class PostponeMergeReadBuilder implements Serializable {
@Nullable private RowType readType;
@Nullable private transient MetricRegistry metricRegistry;
@Nullable private transient String readProtectionTagName;
- private int defaultBucketNum = 1;
private PostponeMergeReadBuilder(FileStoreTable table, @Nullable Snapshot snapshot) {
this.table = table;
@@ -180,12 +181,6 @@ public PostponeMergeReadBuilder withMetricRegistry(MetricRegistry metricRegistry
return this;
}
- public PostponeMergeReadBuilder withDefaultBucketNum(int defaultBucketNum) {
- checkArgument(defaultBucketNum > 0, "Default postpone bucket number must be positive.");
- this.defaultBucketNum = defaultBucketNum;
- return this;
- }
-
public PostponeMergePlan plan() {
checkArgument(snapshot != null, "Snapshot-bound postpone merge plan requires a snapshot.");
RowType resultReadType = resultReadType();
@@ -218,12 +213,28 @@ public PostponeMergePlan plan() {
postponeReader.withPartitionFilter(partitionFilter);
}
+ List realSplits = realReader.read().dataSplits();
+ List postponeSplits =
+ PostponeUtils.groupPostponeFiles(postponeReader.read().dataSplits());
+ PostponeUtils.PostponeBucketRouter bucketRouter;
+ if (postponeSplits.isEmpty()) {
+ bucketRouter = PostponeUtils.createPostponeBucketRouter(table, Collections.emptyMap());
+ } else {
+ List postponePartitions =
+ postponeSplits.stream()
+ .map(DataSplit::partition)
+ .distinct()
+ .collect(Collectors.toList());
+ bucketRouter =
+ PostponeUtils.createPostponeBucketRouter(
+ table, snapshot.id(), postponePartitions);
+ }
+
PostponeMergePlan plan =
new PostponeMergePlan(
- realReader.read().dataSplits(),
- PostponeUtils.groupPostponeFiles(postponeReader.read().dataSplits()),
- PostponeUtils.createPostponeBucketRouter(
- table, snapshot.id(), defaultBucketNum, partitionFilter),
+ realSplits,
+ postponeSplits,
+ bucketRouter,
keyType(),
resultReadType,
mergeReadType);
@@ -246,18 +257,6 @@ public PostponeMergePlan plan(
mergeReadType(resultReadType));
}
- /** Rebuilds only the routing metadata of an existing plan with a new default bucket number. */
- public PostponeMergePlan reroute(PostponeMergePlan plan, int newDefaultBucketNum) {
- checkArgument(newDefaultBucketNum > 0, "Default postpone bucket number must be positive.");
- if (table.coreOptions()
- .toConfiguration()
- .contains(CoreOptions.POSTPONE_DEFAULT_BUCKET_NUM)) {
- return plan;
- }
- defaultBucketNum = newDefaultBucketNum;
- return plan.withDefaultBucketNum(defaultBucketNum);
- }
-
@Nullable
public String readProtectionTagName() {
return readProtectionTagName;
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
index 1d07359b6db9..d6c6ddc5d3e2 100644
--- a/paimon-core/src/test/java/org/apache/paimon/table/PostponeUtilsTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/table/PostponeUtilsTest.java
@@ -117,6 +117,28 @@ public void testGetPostponeRowCountsFromSnapshot() {
verify(reader).withPartitionFilter(partitionFilter);
}
+ @Test
+ public void testGetPostponeFileSizesFromSnapshot() {
+ BinaryRow partition = partition(1);
+ PartitionPredicate partitionFilter = mock(PartitionPredicate.class);
+ DataFileMeta file = mock(DataFileMeta.class);
+ when(file.fileSize()).thenReturn(1024L);
+ ManifestEntry entry = mock(ManifestEntry.class);
+ when(entry.partition()).thenReturn(partition);
+ when(entry.file()).thenReturn(file);
+
+ SnapshotReader reader = mock(SnapshotReader.class, RETURNS_SELF);
+ when(reader.readFileIterator()).thenReturn(Collections.singletonList(entry).iterator());
+ FileStoreTable table = mock(FileStoreTable.class);
+ when(table.newSnapshotReader()).thenReturn(reader);
+
+ assertThat(PostponeUtils.getPostponeFileSizes(table, 5L, partitionFilter))
+ .containsEntry(partition, 1024L);
+ verify(reader).withSnapshot(5L);
+ verify(reader).withBucket(BucketMode.POSTPONE_BUCKET);
+ verify(reader).withPartitionFilter(partitionFilter);
+ }
+
@Test
public void testGetLevel0BucketsFromSnapshot() {
BinaryRow partition = partition(1);
@@ -223,7 +245,7 @@ public void testTableForPostponeCompact() {
FileStoreTable copied = mock(FileStoreTable.class);
when(table.copy(anyMap())).thenReturn(copied);
- assertThat(PostponeUtils.tableForPostponeCompact(table, 4, 5L)).isSameAs(copied);
+ assertThat(PostponeUtils.tableForPostponeRewrite(table, 4, 5L)).isSameAs(copied);
@SuppressWarnings("unchecked")
ArgumentCaptor