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

morningman 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 966a917a561 [fix](row-binlog) Keep row binlog tablets on the binlog 
compaction policy (#69240)
966a917a561 is described below

commit 966a917a5617114ba340a5b01f1b239f2d85b224
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Oct 19 07:15:36 2026 +0100

    [fix](row-binlog) Keep row binlog tablets on the binlog compaction policy 
(#69240)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #68033, #65810
    
    Problem Summary:
    
    **In short.** In local (non-cloud) mode, altering a compaction setting
    of a table with `binlog.format = 'ROW'` also reaches its hidden row
    binlog tablets. A new `compaction_policy` moves them off the `binlog`
    policy they must use. A `time_series_compaction_*` change on a
    `time_series` table fails halfway. #68033 fixed the same thing for
    cloud. This PR applies the same fix to the local path: the FE no longer
    sends compaction settings to row binlog tablets. As a second guard, the
    BE now refuses to change a row binlog tablet's policy.
    
    **Background.**
    - A table with `binlog.format = 'ROW'` has a hidden row binlog index.
    Each data tablet has a row binlog tablet next to it, which stores the
    change events.
    - Row binlog tablets are created with the `binlog` compaction policy
    (`BinlogCumulativeCompactionPolicy`). It is a tiered policy written for
    change events; for example, its quick merge links the segments of
    several rowsets into one `OVERLAPPING` rowset instead of rewriting them.
    `CreateReplicaTask` sets it when it creates a row binlog tablet.
    - `ALTER TABLE ... SET (<compaction property>)` sends the new value to
    the tablets through `UpdateTabletMetaInfoTask`. #68033 made the cloud
    handler (`CloudSchemaChangeHandler`) leave out row binlog tablets for
    `compaction_policy`. The local handler
    (`SchemaChangeHandler.updatePartitionProperties`) still uses
    `getMaterializedIndices(VISIBLE, true)`, so the row binlog index is
    included.
    
    **The problem, and what it cost.** Both cases are reproduced on master
    in local mode:
    
    | Statement on a table with `binlog.format = 'ROW'` | What happens on
    master |
    |---|---|
    | `ALTER TABLE t SET ("compaction_policy" = "time_series")` | Succeeds,
    and every row binlog tablet meta now says `time_series`. From the next
    compaction on, those tablets are compacted by the time series policy
    instead of the binlog policy. |
    | `ALTER TABLE t SET ("time_series_compaction_goal_size_mbytes" =
    "2048")` on a `time_series` table | Fails: `Failed to update
    partition[t]. tablet meta. Error: ...[INVALID_ARGUMENT]only time series
    compaction policy support time series config]. This operation maybe
    partial successfully, You should retry until success.` The BE rejects
    time series settings for tablets that are not on the time series policy,
    and the row binlog tablets are on `binlog`. The data tablets are already
    updated by then. Retrying fails the same way. |
    
    **How this PR fixes it.**
    1. FE, `SchemaChangeHandler.updatePartitionProperties`: when the update
    carries a compaction policy or a time series setting, the row binlog
    index is left out. This is the same rule #68033 uses for cloud.
    - `ALTER TABLE ... SET` changes one table property at a time
    (`ModifyTablePropertiesOp.validate`), so such an update has nothing else
    for the row binlog tablets.
    - Every other tablet meta update (in memory, storage policy, binlog
    config, `disable_auto_compaction`, ...) still reaches them, as before.
    2. BE, `update_tablet_meta_callback`:
    - It refuses to change the compaction policy of a row binlog tablet
    (`can not change compaction policy of row binlog tablet <id>`).
    - It no longer accepts `binlog` as a new policy for any tablet. #65810
    added it to the accepted values, but no FE path sends it: the policy is
    only set when a row binlog tablet is created.
    - With these checks, an older FE, or a future caller, gets an error
    instead of silently moving a row binlog tablet off its policy.
    
    **Results.**
    
    | Statement (local mode, table with `binlog.format = 'ROW'`) | Before |
    After |
    |---|---|---|
    | `SET ("compaction_policy" = "time_series")` | Succeeds; row binlog
    tablets switch to `time_series` | Succeeds; data tablets switch, row
    binlog tablets stay on `binlog` |
    | `SET ("time_series_compaction_goal_size_mbytes" = "2048")` on a
    `time_series` table | Fails after updating the data tablets | Succeeds;
    data tablets get 2048, row binlog tablets are untouched |
    | The first statement from an FE without this fix, against a BE with it
    | Succeeds; row binlog tablets switch to `time_series` | Fails with `can
    not change compaction policy of row binlog tablet <id>`; row binlog
    tablets stay on `binlog` |
---
 be/src/agent/task_worker_pool.cpp                  |  13 ++-
 .../apache/doris/alter/SchemaChangeHandler.java    |   7 +-
 .../doris/alter/SchemaChangeHandlerTest.java       |  60 ++++++++++
 .../test_row_binlog_alter_compaction_policy.out    |  13 +++
 .../test_row_binlog_alter_compaction_policy.groovy | 124 +++++++++++++++++++++
 5 files changed, 212 insertions(+), 5 deletions(-)

diff --git a/be/src/agent/task_worker_pool.cpp 
b/be/src/agent/task_worker_pool.cpp
index 96ad3390b30..2e584918976 100644
--- a/be/src/agent/task_worker_pool.cpp
+++ b/be/src/agent/task_worker_pool.cpp
@@ -73,7 +73,6 @@
 #include "runtime/snapshot_loader.h"
 #include "runtime/user_function_cache.h"
 #include "service/backend_options.h"
-#include "storage/compaction/cumulative_compaction_binlog_policy.h"
 #include "storage/compaction/cumulative_compaction_policy.h"
 #include "storage/compaction/cumulative_compaction_time_series_policy.h"
 #include "storage/data_dir.h"
@@ -910,12 +909,18 @@ void update_tablet_meta_callback(StorageEngine& engine, 
const TAgentTaskRequest&
             need_to_save = true;
         }
         if (tablet_meta_info.__isset.compaction_policy) {
+            // The binlog policy is set when a row binlog tablet is created 
and never changes.
+            if (tablet->is_row_binlog_tablet()) {
+                status = Status::InvalidArgument(
+                        "can not change compaction policy of row binlog tablet 
{}",
+                        tablet_meta_info.tablet_id);
+                continue;
+            }
             if (tablet_meta_info.compaction_policy != 
CUMULATIVE_SIZE_BASED_POLICY &&
-                tablet_meta_info.compaction_policy != 
CUMULATIVE_TIME_SERIES_POLICY &&
-                tablet_meta_info.compaction_policy != 
CUMULATIVE_BINLOG_POLICY) {
+                tablet_meta_info.compaction_policy != 
CUMULATIVE_TIME_SERIES_POLICY) {
                 status = Status::InvalidArgument(
                         "invalid compaction policy, only support for 
size_based or "
-                        "time_series or binlog");
+                        "time_series");
                 continue;
             }
             
tablet->tablet_meta()->set_compaction_policy(tablet_meta_info.compaction_policy);
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 11129f63e1b..b17fcdb172e 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
@@ -3101,9 +3101,14 @@ public class SchemaChangeHandler extends AlterHandler {
         String partitionName = partition.getName();
         // Use the captured partition identity, including temporary partitions 
with the same name.
         Map<Long, Set<Pair<Long, Integer>>> beIdToTabletIdWithHash = 
Maps.newHashMap();
+        // Row binlog tablets keep the binlog compaction policy they are 
created with, so a new compaction
+        // policy or time series setting must not reach them. ALTER sets one 
table property at a time,
+        // so such an update has nothing else for them.
+        boolean includeRowBinlog = compactionPolicy == null
+                && (timeSeriesCompactionConfig == null || 
timeSeriesCompactionConfig.isEmpty());
         olapTable.readLock();
         try {
-            for (MaterializedIndex index : 
partition.getMaterializedIndices(IndexExtState.VISIBLE, true)) {
+            for (MaterializedIndex index : 
partition.getMaterializedIndices(IndexExtState.VISIBLE, includeRowBinlog)) {
                 int schemaHash = 
olapTable.getSchemaHashByIndexId(index.getId());
                 for (Tablet tablet : index.getTablets()) {
                     for (Replica replica : tablet.getReplicas()) {
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 4f0c2015663..15e1e648213 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
@@ -23,8 +23,11 @@ import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.Index;
 import org.apache.doris.catalog.KeysType;
+import org.apache.doris.catalog.MaterializedIndex;
+import org.apache.doris.catalog.MaterializedIndex.IndexExtState;
 import org.apache.doris.catalog.MaterializedIndexMeta;
 import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Partition;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.catalog.Table;
@@ -32,6 +35,7 @@ import org.apache.doris.catalog.info.ColumnPosition;
 import org.apache.doris.catalog.info.IndexType;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.Pair;
 import org.apache.doris.common.jmockit.Deencapsulation;
 import org.apache.doris.indexpolicy.DropIndexPolicyLog;
 import org.apache.doris.indexpolicy.IndexPolicy;
@@ -43,6 +47,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.AlterTableCommand;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.StmtExecutor;
+import org.apache.doris.task.UpdateTabletMetaInfoTask;
 import org.apache.doris.utframe.TestWithFeService;
 
 import com.google.common.collect.Lists;
@@ -52,12 +57,14 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
 import org.mockito.Mockito;
 
 import java.lang.reflect.Method;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.IntSupplier;
 import java.util.stream.Collectors;
@@ -331,6 +338,59 @@ public class SchemaChangeHandlerTest extends 
TestWithFeService {
         
Assertions.assertFalse(cols.contains(Column.generateBeforeColName(Column.SEQUENCE_COL)));
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAlterCompactionSettingSkipsRowBinlogTablets() throws 
Exception {
+        String tableName = "binlog_compaction_setting";
+        String create = "CREATE TABLE IF NOT EXISTS test." + tableName + " (\n"
+                + "k1 INT NOT NULL,\n"
+                + "v1 INT\n"
+                + ")\n"
+                + "DUPLICATE KEY(k1)\n"
+                + "DISTRIBUTED BY HASH(k1) BUCKETS 1\n"
+                + 
"PROPERTIES('replication_num'='1','binlog.enable'='true','binlog.format'='ROW');";
+        createTableWithRowBinlog(create);
+
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrMetaException("test");
+        OlapTable tbl = (OlapTable) db.getTableOrMetaException(tableName, 
Table.TableType.OLAP);
+        Set<Long> dataTabletIds = Sets.newHashSet();
+        Set<Long> rowBinlogTabletIds = Sets.newHashSet();
+        for (Partition partition : tbl.getPartitions()) {
+            for (MaterializedIndex index : 
partition.getMaterializedIndices(IndexExtState.VISIBLE, true)) {
+                (index.isRowBinlog() ? rowBinlogTabletIds : 
dataTabletIds).addAll(index.getTabletIdsInOrder());
+            }
+        }
+        Assertions.assertFalse(rowBinlogTabletIds.isEmpty());
+
+        // Build the update tasks without sending them, and record the tablets 
they are for.
+        Set<Long> updatedTabletIds = Sets.newHashSet();
+        boolean originalRunningUnitTest = FeConstants.runningUnitTest;
+        FeConstants.runningUnitTest = true;
+        try (MockedConstruction<UpdateTabletMetaInfoTask> ignored = 
Mockito.mockConstruction(
+                UpdateTabletMetaInfoTask.class, (task, context) -> {
+                    for (Pair<Long, Integer> tablet : (Set<Pair<Long, 
Integer>>) context.arguments().get(1)) {
+                        updatedTabletIds.add(tablet.first);
+                    }
+                })) {
+            alterTable("ALTER TABLE test." + tableName + " SET 
('compaction_policy' = 'time_series')",
+                    connectContext);
+            Assertions.assertEquals(dataTabletIds, updatedTabletIds);
+
+            updatedTabletIds.clear();
+            alterTable("ALTER TABLE test." + tableName + " SET 
('time_series_compaction_goal_size_mbytes' = '2048')",
+                    connectContext);
+            Assertions.assertEquals(dataTabletIds, updatedTabletIds);
+
+            // Other tablet meta still goes to the row binlog tablets.
+            updatedTabletIds.clear();
+            alterTable("ALTER TABLE test." + tableName + " SET 
('disable_auto_compaction' = 'true')",
+                    connectContext);
+            Assertions.assertEquals(Sets.union(dataTabletIds, 
rowBinlogTabletIds), updatedTabletIds);
+        } finally {
+            FeConstants.runningUnitTest = originalRunningUnitTest;
+        }
+    }
+
     @Test
     public void testModifyTableLightSchemaChangeReplayNormalizesBfColumns() 
throws Exception {
         String tableName = "sc_replay_bf_case";
diff --git 
a/regression-test/data/row_binlog_p0/test_row_binlog_alter_compaction_policy.out
 
b/regression-test/data/row_binlog_p0/test_row_binlog_alter_compaction_policy.out
new file mode 100644
index 00000000000..65b699664b2
--- /dev/null
+++ 
b/regression-test/data/row_binlog_p0/test_row_binlog_alter_compaction_policy.out
@@ -0,0 +1,13 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !alter_policy --
+0      1       10
+0      1       11
+0      2       20
+0      3       30
+
+-- !alter_time_series_setting --
+0      1       10
+0      1       11
+0      2       20
+0      3       30
+
diff --git 
a/regression-test/suites/row_binlog_p0/test_row_binlog_alter_compaction_policy.groovy
 
b/regression-test/suites/row_binlog_p0/test_row_binlog_alter_compaction_policy.groovy
new file mode 100644
index 00000000000..8e174e68d47
--- /dev/null
+++ 
b/regression-test/suites/row_binlog_p0/test_row_binlog_alter_compaction_policy.groovy
@@ -0,0 +1,124 @@
+// 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.
+
+// Altering a compaction setting of a row binlog table must not reach its 
hidden row binlog
+// tablets: they keep the binlog compaction policy they were created with.
+suite("test_row_binlog_alter_compaction_policy") {
+    if (isCloudMode()) {
+        return
+    }
+
+    def backendIdToIp = [:]
+    def backendIdToHttpPort = [:]
+    getBackendIpHttpPort(backendIdToIp, backendIdToHttpPort)
+
+    def getTabletMeta = { tablet ->
+        def meta = null
+        httpTest {
+            endpoint 
"${backendIdToIp["${tablet.BackendId}"]}:${backendIdToHttpPort["${tablet.BackendId}"]}"
+            uri "/api/meta/header/${tablet.TabletId}"
+            op "get"
+            check { respCode, body ->
+                assertEquals(200, respCode as int)
+                meta = parseJson(body.toString())
+            }
+        }
+        return meta
+    }
+
+    // A manual compaction makes a tablet switch to the policy in its tablet 
meta, so after it
+    // the tablet status shows the policy the tablet really compacts with.
+    def checkCompactionPolicy = { String tableName, String dataTabletPolicy ->
+        trigger_and_wait_compaction(tableName, "cumulative")
+        def rowBinlogTabletNum = 0
+        for (def tablet : sql_return_maparray("SHOW TABLETS FROM 
${tableName}")) {
+            def expectedPolicy = dataTabletPolicy
+            if (tablet.IsRowBinlog == "true") {
+                expectedPolicy = "binlog"
+                rowBinlogTabletNum++
+            }
+            assertEquals(expectedPolicy, 
getTabletMeta(tablet).compaction_policy)
+            def (code, out, err) = 
be_show_tablet_status(backendIdToIp["${tablet.BackendId}"],
+                    backendIdToHttpPort["${tablet.BackendId}"], 
tablet.TabletId)
+            assertEquals(0, code)
+            assertEquals(expectedPolicy, parseJson(out.trim())["cumulative 
policy type"])
+        }
+        assertTrue(rowBinlogTabletNum > 0)
+    }
+
+    // Case 1: change the compaction policy.
+    sql "DROP TABLE IF EXISTS test_row_binlog_alter_compaction_policy_dup 
FORCE"
+    sql """
+        CREATE TABLE test_row_binlog_alter_compaction_policy_dup (
+            k1 INT,
+            v1 INT
+        )
+        DUPLICATE KEY(k1)
+        DISTRIBUTED BY HASH(k1) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "disable_auto_compaction" = "true",
+            "binlog.enable" = "true",
+            "binlog.format" = "ROW"
+        )
+    """
+    sql "INSERT INTO test_row_binlog_alter_compaction_policy_dup VALUES (1, 
10), (2, 20)"
+    sql "INSERT INTO test_row_binlog_alter_compaction_policy_dup VALUES (1, 
11), (3, 30)"
+
+    sql """ALTER TABLE test_row_binlog_alter_compaction_policy_dup SET 
("compaction_policy" = "time_series")"""
+    checkCompactionPolicy("test_row_binlog_alter_compaction_policy_dup", 
"time_series")
+    order_qt_alter_policy """
+        SELECT __DORIS_BINLOG_OP__ AS op, k1, v1
+        FROM binlog("table" = "test_row_binlog_alter_compaction_policy_dup")
+    """
+
+    // Case 2: change a time series setting. The row binlog tablets are not on 
the time_series
+    // policy, so the setting must not be sent to them.
+    sql "DROP TABLE IF EXISTS test_row_binlog_alter_compaction_policy_ts FORCE"
+    sql """
+        CREATE TABLE test_row_binlog_alter_compaction_policy_ts (
+            k1 INT,
+            v1 INT
+        )
+        DUPLICATE KEY(k1)
+        DISTRIBUTED BY HASH(k1) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "disable_auto_compaction" = "true",
+            "compaction_policy" = "time_series",
+            "binlog.enable" = "true",
+            "binlog.format" = "ROW"
+        )
+    """
+    sql "INSERT INTO test_row_binlog_alter_compaction_policy_ts VALUES (1, 
10), (2, 20)"
+    sql "INSERT INTO test_row_binlog_alter_compaction_policy_ts VALUES (1, 
11), (3, 30)"
+
+    sql """
+        ALTER TABLE test_row_binlog_alter_compaction_policy_ts
+        SET ("time_series_compaction_goal_size_mbytes" = "2048")
+    """
+    for (def tablet : sql_return_maparray("SHOW TABLETS FROM 
test_row_binlog_alter_compaction_policy_ts")) {
+        if (tablet.IsRowBinlog != "true") {
+            assertEquals(2048L, 
getTabletMeta(tablet).time_series_compaction_goal_size_mbytes as Long)
+        }
+    }
+    checkCompactionPolicy("test_row_binlog_alter_compaction_policy_ts", 
"time_series")
+    order_qt_alter_time_series_setting """
+        SELECT __DORIS_BINLOG_OP__ AS op, k1, v1
+        FROM binlog("table" = "test_row_binlog_alter_compaction_policy_ts")
+    """
+}


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

Reply via email to