This is an automated email from the ASF dual-hosted git repository.
mrhhsg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 33c460d7365 [fix](fe) Check automatic partition limit before creation
(#69128)
33c460d7365 is described below
commit 33c460d7365f7bba0111b01d2ee3adc82097669f
Author: Jerry Hu <[email protected]>
AuthorDate: Tue Oct 20 18:54:18 2026 +0800
[fix](fe) Check automatic partition limit before creation (#69128)
### What problem does this PR solve?
Issue Number: None
Automatic partition creation currently checks `max_auto_partition_num`
only after creating and logging every requested partition. If a load
brings more distinct partition values than the remaining capacity, the
RPC fails but the new partitions remain; repeated bad input can keep
growing table metadata beyond the configured limit.
This change counts only missing partition names before starting the
batch and rejects an over-limit request without creating any partitions.
It also checks the count under the table write lock immediately before
publication, so concurrent requests cannot publish an automatic
partition beyond the then-current limit. Existing-name requests at the
limit remain valid, and explicit partition DDL keeps its prior behavior.
A concurrent request can exhaust the remaining capacity partway through
another multi-partition batch. In that case, partitions already
published within the limit are not rolled back; the change enforces the
publication cap, not atomicity of the whole batch. It does not remove
existing oversized tables or change the image serialization format.
### Release note
Automatic partition requests exceeding `max_auto_partition_num` are
rejected before creating partitions in the normal batch path.
### Check List (For Author)
- Test:
- FE Unit Test:
`FrontendServiceImplTest#testCreatePartitionRangeLimitDoesNotLeavePartitions+testCreatePartitionListLimitCountsOnlyNewPartitions+testCreatePartitionLimitIsRecheckedAtPublication`
(3 passed)
- FE build: `DORIS_THIRDPARTY=<compatible installed snapshot> ./build.sh
--fe` (passed; fe-core Checkstyle 0 violations)
- SQL regression and real multi-threaded load test: not run; the
targeted FE RPC tests cover the limit and deterministic stale-precheck
boundary.
- Behavior changed: Yes. Over-limit automatic partition requests no
longer leave new partitions in the ordinary batch path.
- Does this need documentation: No.
---
.../main/java/org/apache/doris/catalog/Env.java | 12 ++-
.../apache/doris/datasource/InternalCatalog.java | 22 +++++
.../apache/doris/service/FrontendServiceImpl.java | 32 ++++++-
.../doris/service/FrontendServiceImplTest.java | 102 +++++++++++++++++++++
4 files changed, 162 insertions(+), 6 deletions(-)
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 2c041999427..7338b6727a1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -3851,8 +3851,18 @@ public class Env {
boolean writeEditLog,
List<Pair<PartitionPersistInfo,
Partition>> batchPartitions)
throws DdlException {
+ addPartition(db, tableName, addPartitionOp, isCreateTable,
generatedPartitionId,
+ writeEditLog, batchPartitions, false);
+ }
+
+ public void addPartition(Database db, String tableName, AddPartitionOp
addPartitionOp,
+ boolean isCreateTable, long
generatedPartitionId,
+ boolean writeEditLog,
+ List<Pair<PartitionPersistInfo,
Partition>> batchPartitions,
+ boolean enforceAutoPartitionLimit)
+ throws DdlException {
getInternalCatalog().addPartition(db, tableName, addPartitionOp,
- isCreateTable, generatedPartitionId, writeEditLog,
batchPartitions);
+ isCreateTable, generatedPartitionId, writeEditLog,
batchPartitions, enforceAutoPartitionLimit);
}
public void addMultiPartitions(Database db, String tableName,
AlterMultiPartitionOp multiPartitionOp)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
index da9dbeeed0b..49dc07252e4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
@@ -1527,6 +1527,16 @@ public class InternalCatalog implements
CatalogIf<Database> {
boolean writeEditLog,
List<Pair<PartitionPersistInfo,
Partition>> batchPartitions)
throws DdlException {
+ addPartition(db, tableName, addPartitionOp, isCreateTable,
generatedPartitionId,
+ writeEditLog, batchPartitions, false);
+ }
+
+ public void addPartition(Database db, String tableName, AddPartitionOp
addPartitionOp,
+ boolean isCreateTable, long
generatedPartitionId,
+ boolean writeEditLog,
+ List<Pair<PartitionPersistInfo,
Partition>> batchPartitions,
+ boolean enforceAutoPartitionLimit)
+ throws DdlException {
// in cloud mode, isCreateTable == true, create dynamic partition use,
so partitionId must have been generated.
// isCreateTable == false, other case, partitionId generate in below,
must be set 0
if (!FeConstants.runningUnitTest && Config.isCloudMode()
@@ -1809,6 +1819,18 @@ public class InternalCatalog implements
CatalogIf<Database> {
}
}
+ // Check at publication under the table write lock. A request
may have passed the
+ // batch precheck before another request published its own
auto partitions.
+ if (enforceAutoPartitionLimit) {
+ int partitionNum = olapTable.getPartitionNum();
+ int autoPartitionLimit = Config.max_auto_partition_num;
+ if (partitionNum >= autoPartitionLimit) {
+ throw new DdlException(String.format(
+ "partition numbers %d exceeded limit of
variable max_auto_partition_num %d",
+ (long) partitionNum + 1, autoPartitionLimit));
+ }
+ }
+
// check if meta changed
// rollup index may be added or dropped during add partition
operation.
// schema may be changed during add partition operation.
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
index 4825fd767ce..9ea0c09e5a9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
@@ -4801,10 +4801,34 @@ public class FrontendServiceImpl implements
FrontendService.Iface {
return result;
}
+ olapTable.readLock();
+ try {
+ int partitionNum = olapTable.getPartitionNum();
+ int newPartitionNum = 0;
+ for (String partitionName : addPartitionClauseMap.keySet()) {
+ if (!olapTable.checkPartitionNameExist(partitionName)) {
+ newPartitionNum++;
+ }
+ }
+ long expectedPartitionNum = (long) partitionNum + newPartitionNum;
+ int autoPartitionLimit = Config.max_auto_partition_num;
+ if (expectedPartitionNum > autoPartitionLimit) {
+ String errorMessage = String.format(
+ "partition numbers %d exceeded limit of variable
max_auto_partition_num %d",
+ expectedPartitionNum, autoPartitionLimit);
+ LOG.warn(errorMessage);
+ errorStatus.setErrorMsgs(Lists.newArrayList(errorMessage));
+ result.setStatus(errorStatus);
+ LOG.warn("send create partition error status: {}", result);
+ return result;
+ }
+ } finally {
+ olapTable.readUnlock();
+ }
+
for (AddPartitionOp addPartitionOp : addPartitionClauseMap.values()) {
try {
- // here maybe check and limit created partitions num
- Env.getCurrentEnv().addPartition(db, olapTable.getName(),
addPartitionOp, false, 0, true, null);
+ Env.getCurrentEnv().addPartition(db, olapTable.getName(),
addPartitionOp, false, 0, true, null, true);
} catch (DdlException e) {
LOG.warn(e);
errorStatus.setErrorMsgs(
@@ -4829,9 +4853,7 @@ public class FrontendServiceImpl implements
FrontendService.Iface {
olapTable.readLock();
try {
- // check partition's number limit. because partitions in
addPartitionClauseMap may be duplicated with
- // existing partitions, which would lead to false positive. so we
should check the partition number AFTER
- // adding new partitions using its ACTUAL NUMBER, rather than the
sum of existing and requested partitions.
+ // Retain the actual-count check for concurrent changes and
configuration updates during creation.
int partitionNum = olapTable.getPartitionNum();
int autoPartitionLimit = Config.max_auto_partition_num;
if (partitionNum > autoPartitionLimit) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
index 543ab7ebae7..21632f3594c 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
@@ -558,6 +558,108 @@ public class FrontendServiceImplTest extends
TestWithFeService {
Assertions.assertNotNull(p20230807);
}
+ @Test
+ public void testCreatePartitionRangeLimitDoesNotLeavePartitions() throws
Exception {
+ createTable("CREATE TABLE test.partition_range_limit (event_day
DATETIME NOT NULL, id INT) "
+ + "DUPLICATE KEY(event_day, id) "
+ + "AUTO PARTITION BY RANGE (date_trunc(event_day, 'day')) () "
+ + "DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES('replication_num' = '1')");
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("partition_range_limit");
+ int oldLimit = Config.max_auto_partition_num;
+ try {
+ Config.max_auto_partition_num = 1;
+ TCreatePartitionResult result = new
FrontendServiceImpl(exeEnv).createPartition(
+ createPartitionRequest(db, table, "2023-08-07 00:00:00",
"2023-08-08 00:00:00"));
+ Assertions.assertEquals(TStatusCode.RUNTIME_ERROR,
result.getStatus().getStatusCode());
+
Assertions.assertTrue(result.getStatus().getErrorMsgs().get(0).contains("max_auto_partition_num"));
+ Assertions.assertEquals(0, table.getPartitionNum());
+ } finally {
+ Config.max_auto_partition_num = oldLimit;
+ }
+ }
+
+ @Test
+ public void testCreatePartitionListLimitCountsOnlyNewPartitions() throws
Exception {
+ createTable("CREATE TABLE test.partition_list_limit (city_code
VARCHAR(100) NOT NULL, id INT) "
+ + "DUPLICATE KEY(city_code, id) "
+ + "AUTO PARTITION BY LIST (city_code) () "
+ + "DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES('replication_num' = '1')");
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("partition_list_limit");
+ int oldLimit = Config.max_auto_partition_num;
+ try {
+ Config.max_auto_partition_num = 1;
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TCreatePartitionResult first =
impl.createPartition(createPartitionRequest(db, table, "BEIJING"));
+ Assertions.assertEquals(TStatusCode.OK,
first.getStatus().getStatusCode());
+ Assertions.assertEquals(1, table.getPartitionNum());
+
+ TCreatePartitionResult existing =
impl.createPartition(createPartitionRequest(db, table, "BEIJING"));
+ Assertions.assertEquals(TStatusCode.OK,
existing.getStatus().getStatusCode());
+ Assertions.assertEquals(1, table.getPartitionNum());
+
+ TCreatePartitionResult overflow = impl.createPartition(
+ createPartitionRequest(db, table, "BEIJING", "SHANGHAI"));
+ Assertions.assertEquals(TStatusCode.RUNTIME_ERROR,
overflow.getStatus().getStatusCode());
+
Assertions.assertTrue(overflow.getStatus().getErrorMsgs().get(0).contains("max_auto_partition_num"));
+ Assertions.assertEquals(1, table.getPartitionNum());
+ } finally {
+ Config.max_auto_partition_num = oldLimit;
+ }
+ }
+
+ @Test
+ public void testCreatePartitionLimitIsRecheckedAtPublication() throws
Exception {
+ createTable("CREATE TABLE test.partition_list_concurrent_limit
(city_code VARCHAR(100) NOT NULL, id INT) "
+ + "DUPLICATE KEY(city_code, id) "
+ + "AUTO PARTITION BY LIST (city_code) () "
+ + "DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES('replication_num' = '1')");
+ Database db =
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+ OlapTable table = (OlapTable)
db.getTableOrAnalysisException("partition_list_concurrent_limit");
+ int oldLimit = Config.max_auto_partition_num;
+ try {
+ Config.max_auto_partition_num = 1;
+ FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+ TCreatePartitionResult first =
impl.createPartition(createPartitionRequest(db, table, "BEIJING"));
+ Assertions.assertEquals(TStatusCode.OK,
first.getStatus().getStatusCode());
+
+ // Model a concurrent publication after the request's read-lock
precheck.
+ OlapTable spyTable = Mockito.spy(table);
+ AtomicBoolean stalePrecheck = new AtomicBoolean(true);
+ db.unregisterTable(table.getName());
+ db.registerTable(spyTable);
+ try {
+ Mockito.doAnswer(invocation ->
stalePrecheck.compareAndSet(true, false)
+ ? 0 :
invocation.callRealMethod()).when(spyTable).getPartitionNum();
+ TCreatePartitionResult result =
impl.createPartition(createPartitionRequest(db, spyTable, "SHANGHAI"));
+ Assertions.assertEquals(TStatusCode.RUNTIME_ERROR,
result.getStatus().getStatusCode());
+
Assertions.assertTrue(result.getStatus().getErrorMsgs().get(0).contains("max_auto_partition_num"));
+ Assertions.assertFalse(stalePrecheck.get());
+ Assertions.assertEquals(1, spyTable.getPartitionNum());
+ } finally {
+ db.unregisterTable(spyTable.getName());
+ db.registerTable(table);
+ }
+ } finally {
+ Config.max_auto_partition_num = oldLimit;
+ }
+ }
+
+ private static TCreatePartitionRequest createPartitionRequest(Database db,
OlapTable table, String... values) {
+ List<List<TNullableStringLiteral>> partitionValues = new ArrayList<>();
+ for (String value : values) {
+ TNullableStringLiteral literal = new TNullableStringLiteral();
+ literal.setValue(value);
+ partitionValues.add(Collections.singletonList(literal));
+ }
+ TCreatePartitionRequest request = new TCreatePartitionRequest();
+ request.setDbId(db.getId());
+ request.setTableId(table.getId());
+ request.setPartitionValues(partitionValues);
+ return request;
+ }
+
@Test
public void testCreatePartitionWithRowBinlog() throws Exception {
String createOlapTblStmt = "CREATE TABLE
test.partition_range_with_row_binlog(\n"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]