This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new eaf1c85512d branch-4.2: [fix](fe) Check automatic partition limit 
before creation (#69502)
eaf1c85512d is described below

commit eaf1c85512d5bb96f703e3961f9e2cfe9887df79
Author: Jerry Hu <[email protected]>
AuthorDate: Fri Oct 23 17:36:36 2026 +0800

    branch-4.2: [fix](fe) Check automatic partition limit before creation 
(#69502)
    
    ## Summary
    
    Cherry-picked from #69128 for branch-4.2.
    
    Automatic partition creation previously checked `max_auto_partition_num`
    only after creating partitions, so an over-limit request could fail
    after leaving new partitions behind. This change counts only missing
    partition names under the table read lock and rejects an over-limit
    batch before creation. It also rechecks capacity under the table write
    lock before publishing each automatic partition, while preserving the
    existing behavior of explicit partition DDL.
    
    ## Backport notes
    
    The branch-4.2 implementation uses its existing `AddPartitionClause` API
    and JUnit 4 test conventions. If concurrent requests exhaust capacity
    during a batch, partitions already published within the limit can
    remain.
    
    ## Tests
    
    - `./build.sh --fe` (passed; FE Checkstyle: 0 violations).
    - `./run-fe-ut.sh --run
    
'org.apache.doris.service.FrontendServiceImplTest#testCreatePartitionRangeLimitDoesNotLeavePartitions+testCreatePartitionListLimitCountsOnlyNewPartitions+testCreatePartitionLimitIsRecheckedAtPublication'`
    (3 passed, 0 failures, 0 errors).
    - `git diff origin/branch-4.2...HEAD --check` (passed).
---
 .../main/java/org/apache/doris/catalog/Env.java    |  12 ++-
 .../apache/doris/datasource/InternalCatalog.java   |  22 +++++
 .../apache/doris/service/FrontendServiceImpl.java  |  33 ++++++-
 .../doris/service/FrontendServiceImplTest.java     | 102 +++++++++++++++++++++
 4 files changed, 163 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 e73f6c1c0ad..75f92510b71 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
@@ -3647,8 +3647,18 @@ public class Env {
                                              boolean writeEditLog,
                                              List<Pair<PartitionPersistInfo, 
Partition>> batchPartitions)
             throws DdlException {
+        addPartition(db, tableName, addPartitionClause, isCreateTable, 
generatedPartitionId,
+                writeEditLog, batchPartitions, false);
+    }
+
+    public void addPartition(Database db, String tableName, AddPartitionClause 
addPartitionClause,
+                                             boolean isCreateTable, long 
generatedPartitionId,
+                                             boolean writeEditLog,
+                                             List<Pair<PartitionPersistInfo, 
Partition>> batchPartitions,
+                                             boolean enforceAutoPartitionLimit)
+            throws DdlException {
         getInternalCatalog().addPartition(db, tableName, addPartitionClause,
-                isCreateTable, generatedPartitionId, writeEditLog, 
batchPartitions);
+                isCreateTable, generatedPartitionId, writeEditLog, 
batchPartitions, enforceAutoPartitionLimit);
     }
 
     public void addMultiPartitions(Database db, String tableName, 
AlterMultiPartitionClause multiPartitionClause)
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 4ebfb9f95f5..adda42ad5c1 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
@@ -1468,6 +1468,16 @@ public class InternalCatalog implements 
CatalogIf<Database> {
                                              boolean writeEditLog,
                                              List<Pair<PartitionPersistInfo, 
Partition>> batchPartitions)
             throws DdlException {
+        addPartition(db, tableName, addPartitionClause, isCreateTable, 
generatedPartitionId,
+                writeEditLog, batchPartitions, false);
+    }
+
+    public void addPartition(Database db, String tableName, AddPartitionClause 
addPartitionClause,
+                                             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()
@@ -1747,6 +1757,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 f08becb81b0..222276ee773 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
@@ -4099,10 +4099,35 @@ 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 (AddPartitionClause addPartitionClause : 
addPartitionClauseMap.values()) {
             try {
-                // here maybe check and limit created partitions num
-                Env.getCurrentEnv().addPartition(db, olapTable.getName(), 
addPartitionClause, false, 0, true, null);
+                Env.getCurrentEnv().addPartition(db, olapTable.getName(), 
addPartitionClause, false, 0, true,
+                        null, true);
             } catch (DdlException e) {
                 LOG.warn(e);
                 errorStatus.setErrorMsgs(
@@ -4128,9 +4153,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 e45bed95a47..6a4071c08c6 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
@@ -412,6 +412,108 @@ public class FrontendServiceImplTest {
         Assert.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"));
+            Assert.assertEquals(TStatusCode.RUNTIME_ERROR, 
result.getStatus().getStatusCode());
+            
Assert.assertTrue(result.getStatus().getErrorMsgs().get(0).contains("max_auto_partition_num"));
+            Assert.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"));
+            Assert.assertEquals(TStatusCode.OK, 
first.getStatus().getStatusCode());
+            Assert.assertEquals(1, table.getPartitionNum());
+
+            TCreatePartitionResult existing = 
impl.createPartition(createPartitionRequest(db, table, "BEIJING"));
+            Assert.assertEquals(TStatusCode.OK, 
existing.getStatus().getStatusCode());
+            Assert.assertEquals(1, table.getPartitionNum());
+
+            TCreatePartitionResult overflow = impl.createPartition(
+                    createPartitionRequest(db, table, "BEIJING", "SHANGHAI"));
+            Assert.assertEquals(TStatusCode.RUNTIME_ERROR, 
overflow.getStatus().getStatusCode());
+            
Assert.assertTrue(overflow.getStatus().getErrorMsgs().get(0).contains("max_auto_partition_num"));
+            Assert.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"));
+            Assert.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"));
+                Assert.assertEquals(TStatusCode.RUNTIME_ERROR, 
result.getStatus().getStatusCode());
+                
Assert.assertTrue(result.getStatus().getErrorMsgs().get(0).contains("max_auto_partition_num"));
+                Assert.assertFalse(stalePrecheck.get());
+                Assert.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 
testCreatePartitionReturnsRetryErrorWhenResultPartitionIsMissing() throws 
Exception {
         String createOlapTblStmt = "CREATE TABLE 
test.partition_dropped_before_result_snapshot(\n"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to