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

airborne12 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 5964f9f2635 [fix](fe) Run an ALTER that also needs heavy work as a 
heavy schema change instead of a light index change (#69444)
5964f9f2635 is described below

commit 5964f9f2635bf37337b1514367cd530962fde2b1
Author: Jack <[email protected]>
AuthorDate: Tue Oct 27 10:54:04 2026 +0800

    [fix](fe) Run an ALTER that also needs heavy work as a heavy schema change 
instead of a light index change (#69444)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #68674
    
    Problem Summary:
    
    When an ALTER TABLE statement combines an index change that can run on
    the light index change path with a clause that needs a heavy schema
    change, the light path wins and the heavy work is never done:
    
    ```sql
    -- v was INT, idx is an INVERTED index: the shadow column __doris_shadow_v 
BIGINT is published and the
    -- schema change job is recorded as FINISHED, but no BE conversion job runs
    ALTER TABLE t MODIFY COLUMN v BIGINT NULL, DROP INDEX idx;
    ALTER TABLE t MODIFY COLUMN v BIGINT NULL, ADD INDEX i2 (x) USING INVERTED;
    -- enable_add_index_for_new_data = false: bf_v2 must be built for the 
existing data, but it is only
    -- published for new data
    ALTER TABLE t ADD INDEX bf_v2 (v2) USING BLOOMFILTER, DROP INDEX idx;
    ```
    
    The same holds for every column or property clause that cannot run as a
    light schema change (dropping a key column, reordering columns, sequence
    mapping tables, table properties). On a changelog table such a statement
    also bypassed the check that only light changes are allowed, because
    that check looked at the candidate flag `lightIndexChange`.
    
    Root cause: in `SchemaChangeHandler#process` every clause that is not
    light sets `lightSchemaChange = false`, index clauses included, so
    nothing records that a clause needs a heavy schema change. The dispatch
    then takes the light index change branch whenever an index clause can be
    light.
    
    Fix: the clause loop also records `requiresHeavyChange` for column and
    property clauses that cannot be light and for an `ADD INDEX` that cannot
    be light. After the loop, the light index change path is only used when
    no clause needs heavy work; otherwise the statement runs as a heavy
    schema change job (`createJob`), which applies the index changes as
    well, exactly as it does when `enable_light_index_change` is off. The
    changelog check uses the path that is actually taken. No statement is
    newly rejected (other than on changelog tables, where the heavy path was
    never allowed).
    
    Notes:
    - A `DROP INDEX` without light support still needs no heavy work: in a
    light index change only its metadata is dropped (same-statement NGRAM_BF
    to BLOOMFILTER replacement in local mode keeps working).
    - The light DROP INDEX pre-check from #68674 still runs before the
    clause loop, because whether a clause needs heavy work is only known
    after it is processed. In cloud mode it therefore still requires a
    compute group for such a statement, as before this change.
---
 .../apache/doris/alter/SchemaChangeHandler.java    | 24 ++++++++-
 .../doris/alter/SchemaChangeHandlerTest.java       | 62 ++++++++++++++++++++++
 2 files changed, 84 insertions(+), 2 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java 
b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java
index 0bc39904026..51cd7ae55bd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java
@@ -2325,6 +2325,9 @@ public class SchemaChangeHandler extends AlterHandler {
             //alterClauses can or cannot light schema change
             boolean lightSchemaChange = true;
             boolean lightIndexChange = false;
+            // A clause needs a heavy schema change job (a column or property 
change that can not be light, or
+            // an index that has to be built for the existing data). The light 
index change path can not run it.
+            boolean requiresHeavyChange = false;
             boolean buildIndexChange = false;
 
             // index id -> index schema
@@ -2508,10 +2511,12 @@ public class SchemaChangeHandler extends AlterHandler {
                             indexSchemaMap, colUniqueIdSupplierMap);
                     if (!clauseCanLightSchemaChange) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                     // TODO sequence mapping do not support light schema yet
                     if (olapTable.hasColumnSeqMapping() || 
!sequenceMapping.isEmpty()) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                 } else if (alterOp instanceof AddColumnsOp) {
                     // add columns
@@ -2521,10 +2526,12 @@ public class SchemaChangeHandler extends AlterHandler {
                             indexSchemaMap, false, colUniqueIdSupplierMap);
                     if (!clauseCanLightSchemaChange) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                     // TODO sequence mapping do not support light schema yet
                     if (olapTable.hasColumnSeqMapping() || 
!sequenceMapping.isEmpty()) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                 } else if (alterOp instanceof DropColumnOp) {
                     // drop column and drop indexes on this column
@@ -2533,10 +2540,12 @@ public class SchemaChangeHandler extends AlterHandler {
                             indexSchemaMap, newIndexes);
                     if (!clauseCanLightSchemaChange) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                     // TODO sequence mapping do not support light schema yet
                     if (olapTable.hasColumnSeqMapping() || 
!sequenceMapping.isEmpty()) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                 } else if (alterOp instanceof ModifyColumnOp) {
                     // modify column
@@ -2544,15 +2553,18 @@ public class SchemaChangeHandler extends AlterHandler {
                             olapTable, indexSchemaMap);
                     if (!clauseCanLightSchemaChange) {
                         lightSchemaChange = false;
+                        requiresHeavyChange = true;
                     }
                 } else if (alterOp instanceof ReorderColumnsOp) {
                     // reorder column
                     processReorderColumn((ReorderColumnsOp) alterOp, 
olapTable, indexSchemaMap);
                     lightSchemaChange = false;
+                    requiresHeavyChange = true;
                 } else if (alterOp instanceof ModifyTablePropertiesOp) {
                     // modify table properties
                     // do nothing, properties are already in propertyMap
                     lightSchemaChange = false;
+                    requiresHeavyChange = true;
                 } else if (alterOp instanceof CreateIndexOp) {
                     CreateIndexOp createIndexOp = (CreateIndexOp) alterOp;
                     Index index = createIndexOp.getIndex();
@@ -2567,6 +2579,9 @@ public class SchemaChangeHandler extends AlterHandler {
                         alterIndexes.add(index);
                         isDropIndex = false;
                         lightIndexChange = true;
+                    } else {
+                        // The index has to be built for the existing data as 
well.
+                        requiresHeavyChange = true;
                     }
                 } else if (alterOp instanceof BuildIndexOp) {
                     BuildIndexOp buildIndexOp = (BuildIndexOp) alterOp;
@@ -2672,7 +2687,12 @@ public class SchemaChangeHandler extends AlterHandler {
                         lightIndexChange, buildIndexChange, indexSchemaMap);
             }
 
-            if (olapTable.hasChangelog() && !(lightSchemaChange || 
lightIndexChange)) {
+            // The light index change path only publishes the new schema and 
submits index delete jobs. When a
+            // clause also needs a heavy schema change, the whole statement 
runs as a heavy schema change job,
+            // which applies the index changes as well.
+            boolean useLightIndexChange = Config.enable_light_index_change && 
lightIndexChange
+                    && !requiresHeavyChange;
+            if (olapTable.hasChangelog() && !(lightSchemaChange || 
useLightIndexChange)) {
                 throw new DdlException("only support light schema change 
operator when use table with changelog");
             }
 
@@ -2681,7 +2701,7 @@ public class SchemaChangeHandler extends AlterHandler {
                 //for schema change add/drop value column optimize, direct 
modify table meta.
                 modifyTableLightSchemaChange(rawSql, db, olapTable, 
indexSchemaMap, newIndexes,
                         null, isDropIndex, jobId, false, propertyMap);
-            } else if (Config.enable_light_index_change && lightIndexChange) {
+            } else if (useLightIndexChange) {
                 Index.checkConflict(newIndexes, 
olapTable.getCopiedBfColumns());
                 long jobId = Env.getCurrentEnv().getNextId();
                 // For light index changes, directly modify table metadata 
first.
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java
index eea5c4111c6..fccf8447720 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java
@@ -1485,6 +1485,68 @@ public class SchemaChangeHandlerTest extends 
TestWithFeService {
                 
tbl.getIndexes().stream().map(Index::getIndexName).collect(Collectors.toList()));
     }
 
+    @Test
+    public void testLightIndexChangeWithHeavyWorkRunsHeavySchemaChange() 
throws Exception {
+        String tableName = "sc_light_index_with_heavy_work";
+        dropTable("test." + tableName, false);
+        createTable("CREATE TABLE test." + tableName + " (\n"
+                + "k1 INT,\n"
+                + "vi INT,\n"
+                + "v1 STRING,\n"
+                + "v2 STRING,\n"
+                + "v3 STRING,\n"
+                + "INDEX idx_v1 (v1) USING INVERTED,\n"
+                + "INDEX idx_v3 (v3) USING INVERTED\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(k1)\n"
+                + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1', 'light_schema_change' 
= 'true');");
+
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrMetaException("test");
+        OlapTable tbl = (OlapTable) db.getTableOrMetaException(tableName, 
Table.TableType.OLAP);
+        SchemaChangeHandler handler = 
Env.getCurrentEnv().getSchemaChangeHandler();
+        String alterPrefix = "ALTER TABLE test." + tableName + " ";
+
+        // A type change needs a BE conversion job, so the light DROP INDEX 
must not publish the shadow schema.
+        assertRunsHeavySchemaChange(handler, tbl, alterPrefix + "MODIFY COLUMN 
vi BIGINT NULL, DROP INDEX idx_v1");
+        Assertions.assertEquals(PrimitiveType.BIGINT, 
tbl.getColumn("vi").getType().getPrimitiveType());
+        Assertions.assertTrue(tbl.getBaseSchema(true).stream()
+                .noneMatch(column -> 
column.getName().startsWith(SchemaChangeHandler.SHADOW_NAME_PREFIX)));
+        Assertions.assertTrue(tbl.getIndexes().stream().noneMatch(idx -> 
idx.getIndexName().equals("idx_v1")));
+
+        // Without new-data-only indexing, the BLOOMFILTER index has to be 
built for the existing data too.
+        boolean enableAddIndexForNewData = 
connectContext.getSessionVariable().isEnableAddIndexForNewData();
+        connectContext.getSessionVariable().setEnableAddIndexForNewData(false);
+        try {
+            assertRunsHeavySchemaChange(handler, tbl,
+                    alterPrefix + "ADD INDEX bf_v2 (v2) USING BLOOMFILTER, 
DROP INDEX idx_v3");
+        } finally {
+            
connectContext.getSessionVariable().setEnableAddIndexForNewData(enableAddIndexForNewData);
+        }
+        Assertions.assertEquals(Lists.newArrayList("bf_v2"),
+                
tbl.getIndexes().stream().map(Index::getIndexName).collect(Collectors.toList()));
+    }
+
+    // Runs the statement and checks it ran as a heavy schema change job, not 
as a light index change that
+    // publishes the schema at once and submits index delete jobs.
+    private void assertRunsHeavySchemaChange(SchemaChangeHandler handler, 
OlapTable tbl, String sql)
+            throws Exception {
+        Set<Long> alterJobIdsBefore = 
Sets.newHashSet(handler.getAlterJobsV2().keySet());
+        long indexChangeJobsOfTable = 
handler.getIndexChangeJobs().values().stream()
+                .filter(job -> job.getTableId() == tbl.getId()).count();
+
+        alterTable(sql, connectContext);
+        jobSize++;
+
+        List<AlterJobV2> newJobs = handler.getAlterJobsV2().values().stream()
+                .filter(job -> job.getTableId() == tbl.getId() && 
!alterJobIdsBefore.contains(job.getJobId()))
+                .collect(Collectors.toList());
+        Assertions.assertEquals(1, newJobs.size());
+        Assertions.assertEquals(indexChangeJobsOfTable, 
handler.getIndexChangeJobs().values().stream()
+                .filter(job -> job.getTableId() == tbl.getId()).count());
+        waitAlterJobDone(handler.getAlterJobsV2());
+    }
+
     @Test
     public void testAddInvertedIndexStoresCanonicalBuiltinAnalyzer() throws 
Exception {
         createAnalyzerAliasTable("sc_ik_alias");


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

Reply via email to