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 d817014975 Fix: partition index bucket overflow (#9977)
d817014975 is described below
commit d817014975d57db1c4b835519db28c2bb4856386
Author: Akash Reddy Jammula <[email protected]>
AuthorDate: Sat Sep 19 22:47:43 2026 -0700
Fix: partition index bucket overflow (#9977)
---
docs/docs/primary-key-table/data-distribution.md | 2 +-
docs/generated/core_configuration.html | 2 +-
.../main/java/org/apache/paimon/CoreOptions.java | 6 +-
.../apache/paimon/index/HashBucketAssigner.java | 1 +
.../org/apache/paimon/index/PartitionIndex.java | 33 ++++++++--
.../paimon/index/SimpleHashBucketAssigner.java | 7 ++-
.../apache/paimon/index/PartitionIndexTest.java | 70 ++++++++++++++++++++++
.../paimon/index/SimpleHashBucketAssignerTest.java | 11 ++++
.../pypaimon/common/options/core_options.py | 3 +-
paimon-python/pypaimon/index/dynamic_bucket.py | 23 ++++++-
.../pypaimon/tests/write/dynamic_bucket_test.py | 62 +++++++++++++++++--
.../write/simple_hash_bucket_assigner_test.py | 15 +++++
paimon-python/pypaimon/write/row_key_extractor.py | 9 ++-
13 files changed, 226 insertions(+), 18 deletions(-)
diff --git a/docs/docs/primary-key-table/data-distribution.md
b/docs/docs/primary-key-table/data-distribution.md
index 1ce17b35e6..71c5b05bc3 100644
--- a/docs/docs/primary-key-table/data-distribution.md
+++ b/docs/docs/primary-key-table/data-distribution.md
@@ -70,7 +70,7 @@ Do not configure `bucket-key` in this mode.
| --- | --- |
| `dynamic-bucket.target-row-num` | Target row count per bucket |
| `dynamic-bucket.initial-buckets` | Initial bucket count |
-| `dynamic-bucket.max-buckets` | Maximum bucket count |
+| `dynamic-bucket.max-buckets` | Maximum bucket count; use `-1` or a value
from `1` to `32768` |
:::warning Concurrent writers
diff --git a/docs/generated/core_configuration.html
b/docs/generated/core_configuration.html
index 4c8538b4f1..63bcf12a49 100644
--- a/docs/generated/core_configuration.html
+++ b/docs/generated/core_configuration.html
@@ -690,7 +690,7 @@ under the License.
<td><h5>dynamic-bucket.max-buckets</h5></td>
<td style="word-wrap: break-word;">-1</td>
<td>Integer</td>
- <td>Max buckets for a partition in dynamic bucket mode, It should
either be equal to -1 (unlimited), or it must be greater than 0 (fixed upper
bound).</td>
+ <td>Max buckets for a partition in dynamic bucket mode, It should
either be equal to -1 (unlimited), or it must be between 1 and 32768 (fixed
upper bound).</td>
</tr>
<tr>
<td><h5>dynamic-bucket.target-row-num</h5></td>
diff --git a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
index 3842623860..e561c8efc6 100644
--- a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
@@ -1741,6 +1741,8 @@ public class CoreOptions implements Serializable {
"If the bucket is -1, for primary key table, is
dynamic bucket mode, "
+ "this option controls the target row
number for one bucket.");
+ public static final int MAX_DYNAMIC_BUCKETS = Short.MAX_VALUE + 1;
+
@Immutable
public static final ConfigOption<Integer> DYNAMIC_BUCKET_INITIAL_BUCKETS =
key("dynamic-bucket.initial-buckets")
@@ -1755,7 +1757,9 @@ public class CoreOptions implements Serializable {
.defaultValue(-1)
.withDescription(
"Max buckets for a partition in dynamic bucket
mode, It should "
- + "either be equal to -1 (unlimited), or
it must be greater than 0 (fixed upper bound).");
+ + "either be equal to -1 (unlimited), or
it must be between 1 and "
+ + MAX_DYNAMIC_BUCKETS
+ + " (fixed upper bound).");
public static final ConfigOption<Integer>
DYNAMIC_BUCKET_ASSIGNER_PARALLELISM =
key("dynamic-bucket.assigner-parallelism")
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/HashBucketAssigner.java
b/paimon-core/src/main/java/org/apache/paimon/index/HashBucketAssigner.java
index 71cbb2211b..10bd7771ec 100644
--- a/paimon-core/src/main/java/org/apache/paimon/index/HashBucketAssigner.java
+++ b/paimon-core/src/main/java/org/apache/paimon/index/HashBucketAssigner.java
@@ -58,6 +58,7 @@ public class HashBucketAssigner implements BucketAssigner {
int assignId,
long targetBucketRowNumber,
int maxBucketsNum) {
+ PartitionIndex.validateMaxBuckets(maxBucketsNum);
this.snapshotManager = snapshotManager;
this.commitUser = commitUser;
this.indexFileHandler = indexFileHandler;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/PartitionIndex.java
b/paimon-core/src/main/java/org/apache/paimon/index/PartitionIndex.java
index 51add39ac3..7a0a0802eb 100644
--- a/paimon-core/src/main/java/org/apache/paimon/index/PartitionIndex.java
+++ b/paimon-core/src/main/java/org/apache/paimon/index/PartitionIndex.java
@@ -36,7 +36,10 @@ import java.util.Map;
import java.util.Set;
import java.util.function.IntPredicate;
+import static org.apache.paimon.CoreOptions.DYNAMIC_BUCKET_MAX_BUCKETS;
+import static org.apache.paimon.CoreOptions.MAX_DYNAMIC_BUCKETS;
import static org.apache.paimon.index.HashIndexFile.HASH_INDEX;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
/** Bucket Index Per Partition. */
public class PartitionIndex {
@@ -69,6 +72,7 @@ public class PartitionIndex {
}
public int assign(int hash, IntPredicate bucketFilter, int maxBucketsNum) {
+ validateMaxBuckets(maxBucketsNum);
accessed = true;
// 1. is it a key that has appeared before
@@ -85,14 +89,14 @@ public class PartitionIndex {
Long number = entry.getValue();
if (number < targetBucketRowNumber) {
entry.setValue(number + 1);
- hash2Bucket.put(hash, (short) bucket.intValue());
+ hash2Bucket.put(hash, toBucketShort(bucket));
return bucket;
} else {
iterator.remove();
}
}
- int globalMaxBucketId = (maxBucketsNum == -1 ? Short.MAX_VALUE :
maxBucketsNum) - 1;
+ int globalMaxBucketId = (maxBucketsNum == -1 ? MAX_DYNAMIC_BUCKETS :
maxBucketsNum) - 1;
if (!bucketUpperBoundReached) {
// 3. create a new bucket
for (int i = 0; i <= globalMaxBucketId; i++) {
@@ -100,7 +104,7 @@ public class PartitionIndex {
nonFullBucketInformation.put(i, 1L);
totalBucketSet.add(i);
totalBucketArray.add(i);
- hash2Bucket.put(hash, (short) i);
+ hash2Bucket.put(hash, toBucketShort(i));
return i;
}
}
@@ -115,7 +119,7 @@ public class PartitionIndex {
// 4. exceed buckets upper bound
int bucket = ListUtils.pickRandomly(totalBucketArray);
- hash2Bucket.put(hash, (short) bucket);
+ hash2Bucket.put(hash, toBucketShort(bucket));
return bucket;
}
@@ -129,6 +133,7 @@ public class PartitionIndex {
Int2ShortHashMap.Builder mapBuilder = Int2ShortHashMap.builder();
Map<Integer, Long> buckets = new HashMap<>();
for (IndexManifestEntry file : files) {
+ short loadedBucket = toBucketShort(file.bucket());
try (IntIterator iterator =
indexFileHandler
.hashIndex(file.partition(), file.bucket())
@@ -137,7 +142,7 @@ public class PartitionIndex {
try {
int hash = iterator.next();
if (loadFilter.test(hash)) {
- mapBuilder.put(hash, (short) file.bucket());
+ mapBuilder.put(hash, loadedBucket);
}
if (bucketFilter.test(file.bucket())) {
buckets.compute(
@@ -154,4 +159,22 @@ public class PartitionIndex {
}
return new PartitionIndex(mapBuilder.build(), buckets,
targetBucketRowNumber);
}
+
+ static void validateMaxBuckets(int maxBucketsNum) {
+ checkArgument(
+ maxBucketsNum == -1 || (maxBucketsNum > 0 && maxBucketsNum <=
MAX_DYNAMIC_BUCKETS),
+ "'%s' must be -1 or between 1 and %s, but was %s.",
+ DYNAMIC_BUCKET_MAX_BUCKETS.key(),
+ MAX_DYNAMIC_BUCKETS,
+ maxBucketsNum);
+ }
+
+ static short toBucketShort(int bucket) {
+ checkArgument(
+ bucket >= 0 && bucket < MAX_DYNAMIC_BUCKETS,
+ "Dynamic bucket id must be between 0 and %s, but was %s.",
+ MAX_DYNAMIC_BUCKETS - 1,
+ bucket);
+ return (short) bucket;
+ }
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/SimpleHashBucketAssigner.java
b/paimon-core/src/main/java/org/apache/paimon/index/SimpleHashBucketAssigner.java
index 964244415d..e0f0b56e85 100644
---
a/paimon-core/src/main/java/org/apache/paimon/index/SimpleHashBucketAssigner.java
+++
b/paimon-core/src/main/java/org/apache/paimon/index/SimpleHashBucketAssigner.java
@@ -30,6 +30,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import static org.apache.paimon.CoreOptions.MAX_DYNAMIC_BUCKETS;
+
/** When we need to overwrite the table, we should use this to avoid loading
index. */
public class SimpleHashBucketAssigner implements BucketAssigner {
@@ -42,6 +44,7 @@ public class SimpleHashBucketAssigner implements
BucketAssigner {
public SimpleHashBucketAssigner(
int numAssigners, int assignId, long targetBucketRowNumber, int
maxBucketsNum) {
+ PartitionIndex.validateMaxBuckets(maxBucketsNum);
this.numAssigners = numAssigners;
this.assignId = assignId;
this.targetBucketRowNumber = targetBucketRowNumber;
@@ -120,12 +123,12 @@ public class SimpleHashBucketAssigner implements
BucketAssigner {
}
return l + 1;
});
- hash2Bucket.put(hash, (short) currentBucket);
+ hash2Bucket.put(hash, PartitionIndex.toBucketShort(currentBucket));
return currentBucket;
}
private boolean loadNewBucket() {
- for (int i = 0; i < Short.MAX_VALUE; i++) {
+ for (int i = 0; i < MAX_DYNAMIC_BUCKETS; i++) {
if (isMyBucket(i) && !bucketInformation.containsKey(i)) {
// The new bucketId may still be larger than the upper
bound
if (-1 == maxBucketsNum || i <= maxBucketsNum - 1) {
diff --git
a/paimon-core/src/test/java/org/apache/paimon/index/PartitionIndexTest.java
b/paimon-core/src/test/java/org/apache/paimon/index/PartitionIndexTest.java
new file mode 100644
index 0000000000..eadc062215
--- /dev/null
+++ b/paimon-core/src/test/java/org/apache/paimon/index/PartitionIndexTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.index;
+
+import org.apache.paimon.utils.Int2ShortHashMap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.HashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link PartitionIndex}. */
+class PartitionIndexTest {
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 32768})
+ void testMaximumBucketCount(int maxBuckets) {
+ PartitionIndex index =
+ new PartitionIndex(new Int2ShortHashMap(), new HashMap<>(),
Long.MAX_VALUE);
+
+ int hash = 12345;
+ assertThat(index.assign(hash, bucket -> bucket == Short.MAX_VALUE,
maxBuckets))
+ .isEqualTo(Short.MAX_VALUE);
+ assertThat(index.assign(hash, bucket -> false,
maxBuckets)).isEqualTo(Short.MAX_VALUE);
+ }
+
+ @Test
+ void testRejectBucketCountAboveShortRange() {
+ PartitionIndex index =
+ new PartitionIndex(new Int2ShortHashMap(), new HashMap<>(),
Long.MAX_VALUE);
+
+ for (int i = 0; i < 2; i++) {
+ assertThatThrownBy(() -> index.assign(12345, bucket -> bucket >=
32768, 40000))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(
+ "'dynamic-bucket.max-buckets' must be -1 or
between 1 and 32768, but was 40000.");
+ }
+ }
+
+ @Test
+ void testRejectExistingBucketAboveShortRange() {
+ HashMap<Integer, Long> buckets = new HashMap<>();
+ buckets.put(32768, 0L);
+ PartitionIndex index = new PartitionIndex(new Int2ShortHashMap(),
buckets, Long.MAX_VALUE);
+
+ assertThatThrownBy(() -> index.assign(12345, bucket -> false, 32768))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Dynamic bucket id must be between 0 and 32767,
but was 32768.");
+ }
+}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/index/SimpleHashBucketAssignerTest.java
b/paimon-core/src/test/java/org/apache/paimon/index/SimpleHashBucketAssignerTest.java
index a8b2df1e52..c354eda5f5 100644
---
a/paimon-core/src/test/java/org/apache/paimon/index/SimpleHashBucketAssignerTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/index/SimpleHashBucketAssignerTest.java
@@ -86,6 +86,17 @@ public class SimpleHashBucketAssignerTest {
}
}
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 32768})
+ public void testMaximumBucketId(int maxBucketsNum) {
+ SimpleHashBucketAssigner assigner =
+ new SimpleHashBucketAssigner(32768, 32767, 1, maxBucketsNum);
+ BinaryRow partition = BinaryRow.EMPTY_ROW;
+
+ assertThat(assigner.assign(partition, 1)).isEqualTo(Short.MAX_VALUE);
+ assertThat(assigner.assign(partition, 1)).isEqualTo(Short.MAX_VALUE);
+ }
+
@Test
public void testAssign() {
SimpleHashBucketAssigner simpleHashBucketAssigner =
diff --git a/paimon-python/pypaimon/common/options/core_options.py
b/paimon-python/pypaimon/common/options/core_options.py
index bb312781bf..801b73e531 100644
--- a/paimon-python/pypaimon/common/options/core_options.py
+++ b/paimon-python/pypaimon/common/options/core_options.py
@@ -240,7 +240,8 @@ class CoreOptions:
.int_type()
.default_value(-1)
.with_description(
- "In dynamic bucket mode, max buckets per partition. -1 means
unlimited."
+ "In dynamic bucket mode, max buckets per partition. It must be -1 "
+ "or between 1 and 32768."
)
)
diff --git a/paimon-python/pypaimon/index/dynamic_bucket.py
b/paimon-python/pypaimon/index/dynamic_bucket.py
index c5bbd08b7d..90a1c49230 100644
--- a/paimon-python/pypaimon/index/dynamic_bucket.py
+++ b/paimon-python/pypaimon/index/dynamic_bucket.py
@@ -32,6 +32,7 @@ HASH_INDEX = "HASH"
_ADD = 0
_DELETE = 1
SHORT_MAX_VALUE = 32767
+MAX_DYNAMIC_BUCKETS = SHORT_MAX_VALUE + 1
_SNAPSHOT_UNSET = object()
@@ -64,6 +65,22 @@ def is_my_bucket(bucket: int, num_assigners: int, assign_id:
int) -> bool:
return bucket % num_assigners == assign_id % num_assigners
+def validate_max_buckets(max_buckets_num: int) -> None:
+ if max_buckets_num != -1 and not 1 <= max_buckets_num <=
MAX_DYNAMIC_BUCKETS:
+ raise ValueError(
+ "'dynamic-bucket.max-buckets' must be -1 or between 1 and "
+ f"{MAX_DYNAMIC_BUCKETS}, but was {max_buckets_num}."
+ )
+
+
+def validate_bucket_id(bucket: int) -> None:
+ if not 0 <= bucket < MAX_DYNAMIC_BUCKETS:
+ raise ValueError(
+ "Dynamic bucket id must be between 0 and "
+ f"{MAX_DYNAMIC_BUCKETS - 1}, but was {bucket}."
+ )
+
+
def _iter_hashes(table, entry: IndexManifestEntry) -> Iterator[int]:
meta = entry.index_file
path = meta.external_path
@@ -125,6 +142,7 @@ class _PartitionIndex:
max_buckets_num: int,
max_bucket_id: int,
) -> Tuple[int, bool]:
+ validate_max_buckets(max_buckets_num)
if key_hash in self.hash_to_bucket:
return self.hash_to_bucket[key_hash], False
@@ -136,7 +154,7 @@ class _PartitionIndex:
del self.non_full_bucket_information[bucket]
global_max_bucket_id = (
- SHORT_MAX_VALUE if max_buckets_num == -1 else max_buckets_num
+ MAX_DYNAMIC_BUCKETS if max_buckets_num == -1 else max_buckets_num
) - 1
if not self.total_bucket_set or max_bucket_id < global_max_bucket_id:
for bucket in range(global_max_bucket_id + 1):
@@ -176,6 +194,7 @@ class HashBucketAssigner:
ignore_existing: bool = False,
snapshot=_SNAPSHOT_UNSET,
) -> None:
+ validate_max_buckets(max_buckets_num)
if not 0 <= assign_id < num_channels:
raise ValueError(
f"assign_id must be in [0, {num_channels}), got {assign_id}"
@@ -286,6 +305,7 @@ class HashBucketAssigner:
)
entries_by_bucket = {}
for entry in entries:
+ validate_bucket_id(entry.bucket)
previous = entries_by_bucket.get(entry.bucket)
if previous is not None:
raise RuntimeError(
@@ -347,6 +367,7 @@ class HashBucketAssigner:
)
remaining = set(requested_hashes)
for entry in entries:
+ validate_bucket_id(entry.bucket)
for key_hash in _iter_hashes(self.table, entry):
if key_hash not in remaining:
continue
diff --git a/paimon-python/pypaimon/tests/write/dynamic_bucket_test.py
b/paimon-python/pypaimon/tests/write/dynamic_bucket_test.py
index ffe6c88319..be175f1e39 100644
--- a/paimon-python/pypaimon/tests/write/dynamic_bucket_test.py
+++ b/paimon-python/pypaimon/tests/write/dynamic_bucket_test.py
@@ -31,6 +31,7 @@ from pypaimon.index.dynamic_bucket import (
_iter_hashes,
compute_assigner,
to_signed_int32,
+ validate_bucket_id,
)
from pypaimon.index.index_file_handler import IndexFileHandler
from pypaimon.index.index_file_meta import IndexFileMeta
@@ -191,12 +192,65 @@ class DynamicBucketTest(unittest.TestCase):
bucket = index.assign(
key_hash=1,
- bucket_filter=lambda candidate: candidate == 32766,
+ bucket_filter=lambda candidate: candidate == 32767,
max_buckets_num=-1,
- max_bucket_id=32765,
+ max_bucket_id=32766,
)
- self.assertEqual(bucket, 32766)
+ self.assertEqual(bucket, 32767)
+
+ def test_rejects_bucket_count_above_java_short_range(self):
+ index = _PartitionIndex({}, {}, 1)
+
+ with self.assertRaisesRegex(
+ ValueError,
+ "'dynamic-bucket.max-buckets' must be -1 or between 1 and 32768",
+ ):
+ index.assign(
+ key_hash=1,
+ bucket_filter=lambda candidate: candidate >= 32768,
+ max_buckets_num=40000,
+ max_bucket_id=32767,
+ )
+
+ def test_rejects_bucket_id_above_java_short_range(self):
+ with self.assertRaisesRegex(
+ ValueError,
+ "Dynamic bucket id must be between 0 and 32767, but was 32768",
+ ):
+ validate_bucket_id(32768)
+
+ def test_restore_rejects_bucket_id_above_java_short_range(self):
+ assigner = HashBucketAssigner(
+ table=Mock(),
+ num_channels=1,
+ num_assigners=1,
+ assign_id=0,
+ target_bucket_row_number=1,
+ max_buckets_num=-1,
+ snapshot=Mock(),
+ )
+ entry = Mock()
+ entry.bucket = 32768
+
+ with patch(
+ 'pypaimon.index.dynamic_bucket.IndexFileHandler'
+ ) as handler:
+ handler.return_value.scan.return_value = [entry]
+ with patch.object(
+ assigner, '_active_data_buckets', return_value=set()
+ ):
+ with self.assertRaisesRegex(
+ ValueError,
+ "Dynamic bucket id must be between 0 and 32767",
+ ):
+ assigner._load_partition((), {1})
+
+ with self.assertRaisesRegex(
+ ValueError,
+ "Dynamic bucket id must be between 0 and 32767",
+ ):
+ assigner._restore_requested_hashes((), {1}, {})
def test_assigner_rejects_record_owned_by_another_writer(self):
with tempfile.TemporaryDirectory() as root:
@@ -238,7 +292,7 @@ class DynamicBucketTest(unittest.TestCase):
key_hash=1,
bucket_filter=lambda _: False,
max_buckets_num=-1,
- max_bucket_id=32766,
+ max_bucket_id=32767,
)
def test_corrupt_hash_index_rejects_trailing_bytes(self):
diff --git
a/paimon-python/pypaimon/tests/write/simple_hash_bucket_assigner_test.py
b/paimon-python/pypaimon/tests/write/simple_hash_bucket_assigner_test.py
index 6a7b78c2bd..0e27dde077 100644
--- a/paimon-python/pypaimon/tests/write/simple_hash_bucket_assigner_test.py
+++ b/paimon-python/pypaimon/tests/write/simple_hash_bucket_assigner_test.py
@@ -56,6 +56,21 @@ class SimpleHashBucketAssignerTest(unittest.TestCase):
for b in buckets[100:]:
self.assertEqual(b, 0)
+ def test_maximum_bucket_id(self):
+ for max_buckets in [-1, 32768]:
+ with self.subTest(max_buckets=max_buckets):
+ assigner = SimpleHashBucketAssigner(
+ 32768, 32767, 1, max_buckets)
+ self.assertEqual(assigner.assign((), 1), 32767)
+ self.assertEqual(assigner.assign((), 1), 32767)
+
+ def test_rejects_bucket_count_above_java_short_range(self):
+ with self.assertRaisesRegex(
+ ValueError,
+ "'dynamic-bucket.max-buckets' must be -1 or between 1 and 32768",
+ ):
+ SimpleHashBucketAssigner(1, 0, 100, 40000)
+
def test_register_each_bucket_once(self):
for num_assigners, assign_id, max_buckets, expected in [
(1, 0, 1, [0, 0, 0, 0, 0, 0]),
diff --git a/paimon-python/pypaimon/write/row_key_extractor.py
b/paimon-python/pypaimon/write/row_key_extractor.py
index 827b9f08b2..d5053e983f 100644
--- a/paimon-python/pypaimon/write/row_key_extractor.py
+++ b/paimon-python/pypaimon/write/row_key_extractor.py
@@ -26,7 +26,11 @@ import numpy as np
import pyarrow as pa
from pypaimon.common.options.core_options import CoreOptions
-from pypaimon.index.dynamic_bucket import SHORT_MAX_VALUE, is_my_bucket
+from pypaimon.index.dynamic_bucket import (
+ MAX_DYNAMIC_BUCKETS,
+ is_my_bucket,
+ validate_max_buckets,
+)
from pypaimon.schema.table_schema import TableSchema
from pypaimon.table.bucket_mode import BucketMode
from pypaimon.table.row.generic_row import GenericRow, GenericRowSerializer
@@ -376,7 +380,7 @@ class _SimplePartitionIndex:
def _load_new_bucket(
self, max_buckets_num: int, num_assigners: int, assign_id: int
) -> None:
- for i in range(SHORT_MAX_VALUE):
+ for i in range(MAX_DYNAMIC_BUCKETS):
if is_my_bucket(i, num_assigners, assign_id) and (
i not in self.bucket_information
):
@@ -392,6 +396,7 @@ class _SimplePartitionIndex:
class SimpleHashBucketAssigner:
def __init__(self, num_assigners, assign_id, target_bucket_row_number,
max_buckets_num):
+ validate_max_buckets(max_buckets_num)
self.num_assigners = num_assigners
self.assign_id = assign_id
self.target_bucket_row_number = target_bucket_row_number