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

zhangstar333 pushed a commit to branch doris_master_new
in repository https://gitbox.apache.org/repos/asf/doris.git

commit 7ab0e9b8447a024d22bcfe1d4efc8921d143bd42
Author: zhangstar333 <[email protected]>
AuthorDate: Wed Jun 3 23:16:35 2026 +0800

    change heart beat time and rpc retry with master
---
 be/src/util/thrift_rpc_helper.cpp                  | 121 ++++++---------------
 .../main/java/org/apache/doris/common/Config.java  |   2 +-
 .../main/java/org/apache/doris/catalog/Env.java    |   1 +
 .../java/org/apache/doris/qe/SessionVariable.java  |  14 ++-
 .../main/java/org/apache/doris/qe/VariableMgr.java |  29 +++++
 5 files changed, 79 insertions(+), 88 deletions(-)

diff --git a/be/src/util/thrift_rpc_helper.cpp 
b/be/src/util/thrift_rpc_helper.cpp
index 63b3cd54125..9f3831f94cf 100644
--- a/be/src/util/thrift_rpc_helper.cpp
+++ b/be/src/util/thrift_rpc_helper.cpp
@@ -269,104 +269,53 @@ Status ThriftRpcHelper::rpc_fe_with_master_refresh(
         LOG(INFO) << "fe RPC got NOT_MASTER from " << old_master
                   << ", refreshed master_fe_addr to " << new_master;
     } else {
-        // Scenario A: transport failure; or NOT_MASTER without master_address
-        // (old FE without thrift change).
-        //
-        //   * candidate RPC returns ok          -> candidate IS current 
master,
-        //                                          cache it and return OK
-        //                                          (the response was filled by
-        //                                          callback during the probe).
-        //   * candidate returns NOT_MASTER hint -> refresh master_fe_addr and
-        //                                          fall through to the single
-        //                                          retry against new master.
-        //   * candidate transport-failed        -> try the next one.
-        //
-        constexpr int kMaxProbes = 3;
-        auto running = _s_exec_env->get_running_frontends();
-        int probed = 0;
-        bool master_refreshed = false;
+        // Transport failure or NOT_MASTER without master_address hint.
+        // During graceful shutdown, the heartbeat interval is reduced to ~1s,
+        // so BE will quickly pick up the new master FE address. Retry with
+        // increasing backoff, relying on heartbeat to update master_fe_addr
+        // which is read fresh by address_provider on each attempt.
+        constexpr int kMaxRetries = 3;
+        for (int i = 0; i < kMaxRetries; i++) {
+            int backoff_ms = 1000 * (1 << i);
+            std::this_thread::sleep_for(std::chrono::milliseconds(backoff_ms));
 
-        for (const auto& kv : running) {
-            if (probed >= kMaxProbes) {
-                break;
-            }
-            const TNetworkAddress& candidate_addr = kv.first;
-            if (candidate_addr.hostname == old_master.hostname &&
-                candidate_addr.port == old_master.port) {
-                continue; // Skip the already-failed master.
-            }
-            if (candidate_addr.hostname.empty() || candidate_addr.port == 0) {
-                continue;
-            }
-            ++probed;
-
-            auto candidate_provider = [candidate_addr]() { return 
candidate_addr; };
-            Status probe_st = rpc<FrontendServiceClient>(
-                    candidate_provider, callback, timeout_ms);
-            if (!probe_st.ok()) {
-                LOG(INFO) << "fe RPC probe to " << candidate_addr
-                          << " transport-failed after master " << old_master
-                          << " was unreachable: " << probe_st;
-                if (probed < kMaxProbes) {
-                    std::this_thread::sleep_for(1000ms); // Back off a bit 
before the next probe.
-                }
-                continue;
-            }
-
-            TStatus probe_resp = status_extractor();
-            if (probe_resp.status_code == TStatusCode::NOT_MASTER) {
-                const TNetworkAddress* hinted = master_addr_extractor();
-                if (hinted != nullptr && !hinted->hostname.empty()
-                        && hinted->port != 0) {
-                    if (hinted->hostname == old_master.hostname &&
-                        hinted->port == old_master.port) {
-                        LOG(INFO) << "fe RPC probe to " << candidate_addr
-                                  << " returned NOT_MASTER with stale 
master_address "
-                                  << *hinted << ", same as failed master " << 
old_master
-                                  << "; trying next";
-                        if (probed < kMaxProbes) {
-                            std::this_thread::sleep_for(1000ms); // Back off a 
bit before the next probe.
-                        }
-                        continue;
+            Status retry_st = rpc<FrontendServiceClient>(address_provider, 
callback, timeout_ms);
+            if (retry_st.ok()) {
+                TStatus retry_resp = status_extractor();
+                if (retry_resp.status_code == TStatusCode::NOT_MASTER) {
+                    const TNetworkAddress* hinted = master_addr_extractor();
+                    if (hinted != nullptr && !hinted->hostname.empty()
+                            && hinted->port != 0) {
+                        _s_exec_env->cluster_info()->master_fe_addr = *hinted;
+                        new_master = *hinted;
+                        have_new_master = true;
+                        LOG(INFO) << "fe RPC retry " << (i + 1) << "/" << 
kMaxRetries
+                                  << " returned NOT_MASTER, refreshed 
master_fe_addr to "
+                                  << *hinted;
+                        break; // fall through to the single retry below
                     }
-                    _s_exec_env->cluster_info()->master_fe_addr = *hinted;
-                    LOG(INFO) << "fe RPC probe to " << candidate_addr
-                              << " returned NOT_MASTER, refreshed 
master_fe_addr to "
-                              << *hinted << " (after transport failure on " << 
old_master << ")";
-                    master_refreshed = true;
-                    break;
+                    LOG(INFO) << "fe RPC retry " << (i + 1) << "/" << 
kMaxRetries
+                              << " returned NOT_MASTER without master_address";
+                    continue;
                 }
-                // NOT_MASTER without master_address (old FE). Try next 
candidate.
-                LOG(INFO) << "fe RPC probe to " << candidate_addr
-                          << " returned NOT_MASTER without master_address, 
trying next";
-                if (probed < kMaxProbes) {
-                    std::this_thread::sleep_for(2000ms); // Back off a bit 
before the next probe.
-                }
-                continue;
+                // RPC succeeded: address_provider already yielded the correct 
master.
+                LOG(INFO) << "fe RPC succeeded on retry " << (i + 1) << "/" << 
kMaxRetries;
+                return Status::OK();
             }
-
-            // Probe succeeded on the candidate: it IS the current master.
-            // The callback has already populated the response on this probe
-            // (status_extractor returned non-NOT_MASTER OK), so the original
-            // operation has completed against the new master. Refresh the
-            // cache so subsequent RPCs go directly there.
-            _s_exec_env->cluster_info()->master_fe_addr = candidate_addr;
-            LOG(INFO) << "fe RPC succeeded on probe candidate " << 
candidate_addr
-                      << " after transport failure on " << old_master
-                      << ", refreshed master_fe_addr.";
-            return Status::OK();
+            LOG(INFO) << "fe RPC retry " << (i + 1) << "/" << kMaxRetries
+                      << " transport-failed: " << retry_st;
         }
 
-        if (!master_refreshed) {
+        if (!have_new_master) {
             LOG(WARNING) << "fe RPC failed against " << old_master
-                         << ", probed " << probed
-                         << " follower(s) without recovery; returning original 
error.";
+                         << " after " << kMaxRetries
+                         << " backoff retries, returning original error.";
             return st.ok() ? Status::Error<ErrorCode::NOT_MASTER>(
                                      "FE returned NOT_MASTER but no new master 
address available")
                            : st;
         }
         LOG(INFO) << "fe RPC failed against " << old_master
-                  << ", master refreshed via probe, retrying original 
request.";
+                  << ", master refreshed via backoff retry, retrying original 
request.";
     }
 
     // Second (and only) attempt against the new master address.
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java 
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index c7c4c65dae8..3fae58d6dd9 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2124,7 +2124,7 @@ public class Config extends ConfigBase {
      * Heartbeat interval in seconds.
      * Default is 10, which means every 10 seconds, the master will send a 
heartbeat to all backends.
      */
-    @ConfField(mutable = false, masterOnly = false)
+    @ConfField(mutable = true, masterOnly = false)
     public static int heartbeat_interval_second = 10;
 
     /**
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 7605a93639c..41c86f0d414 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
@@ -616,6 +616,7 @@ public class Env {
             .put("dynamic_partition_check_interval_seconds", 
this::getDynamicPartitionScheduler)
             .put("table_stream_partition_offset_cleanup_interval_second",
                     this::getTableStreamManager)
+            .put("heartbeat_interval_second", this::getHeartbeatMgr)
             .build();
 
     private TSOService tsoService;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 1dcb8562dc3..8638a95840b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -24,6 +24,8 @@ import org.apache.doris.catalog.Env;
 import org.apache.doris.cloud.qe.ComputeGroupException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.VariableAnnotation;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
@@ -2313,7 +2315,8 @@ public class SessionVariable implements Serializable, 
Writable {
                 "服务端 prepared statement 最大个数", "the maximum prepared 
statements server holds."})
     public int maxPreparedStmtCount = 100000;
 
-    @VarAttrDef.VarAttr(name = ENABLE_GRACEFUL_SHUTDOWN, flag = 
VarAttrDef.GLOBAL, needForward = true,
+    @VarAttrDef.VarAttr(name = ENABLE_GRACEFUL_SHUTDOWN, flag = 
VarAttrDef.GLOBAL,
+            setter = "setEnableGracefulShutdown", needForward = true,
             description = {
             "集群级别开关:当 FE 或 BE 准备做滚动重启 / 优雅退出时,由运维 SET GLOBAL 打开。"
                     + "打开后:(1) FE QueryCancelWorker / Coordinator.shouldCancel 
不再因 BE alive=false "
@@ -6864,3 +6867,12 @@ public class SessionVariable implements Serializable, 
Writable {
         }
     }
 }
+    public void setEnableGracefulShutdown(String value) throws DdlException {
+        if (value.equalsIgnoreCase("TRUE")) {
+            enableGracefulShutdown = true;
+        } else if (value.equalsIgnoreCase("FALSE")) {
+            enableGracefulShutdown = false;
+        } else {
+            ErrorReport.reportDdlException(ErrorCode.ERR_WRONG_VALUE_FOR_VAR, 
ENABLE_GRACEFUL_SHUTDOWN, value);
+        }
+    }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java
index 5f6a60a2d16..768de26cb9d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java
@@ -26,6 +26,7 @@ import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.Type;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
+import org.apache.doris.common.ConfigException;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
@@ -103,6 +104,9 @@ import javax.annotation.Nullable;
  */
 public class VariableMgr {
     private static final Logger LOG = LogManager.getLogger(VariableMgr.class);
+    private static final String HEARTBEAT_INTERVAL_SECOND = 
"heartbeat_interval_second";
+    private static final String FAST_HEARTBEAT_INTERVAL_SECONDS = "1";
+    private static final String DEFAULT_HEARTBEAT_INTERVAL_SECONDS = "10";
 
     // Map variable name to variable context which have enough information to 
change variable value.
     // This map contains info of all session and global variables.
@@ -448,6 +452,7 @@ public class VariableMgr {
         try {
 
             setValue(ctx.getObj(), new SessionVariableField(ctx.getField()), 
value);
+            applyGlobalVariableSideEffect(name, value);
             // write edit log
             GlobalVarPersistInfo info = new 
GlobalVarPersistInfo(defaultSessionVariable, Lists.newArrayList(name));
             Env.getCurrentEnv().getEditLog().logGlobalVariableV2(info);
@@ -456,6 +461,29 @@ public class VariableMgr {
         }
     }
 
+    private static void applyGlobalVariableSideEffect(String name, String 
value) throws DdlException {
+        if (!SessionVariable.ENABLE_GRACEFUL_SHUTDOWN.equals(name) || 
Env.isCheckpointThread()) {
+            return;
+        }
+        String heartbeatInterval = parseBooleanValue(name, value)
+                ? FAST_HEARTBEAT_INTERVAL_SECONDS : 
DEFAULT_HEARTBEAT_INTERVAL_SECONDS;
+        try {
+            
Env.getCurrentEnv().setMutableConfigWithCallback(HEARTBEAT_INTERVAL_SECOND, 
heartbeatInterval);
+        } catch (ConfigException e) {
+            throw new DdlException(e.getMessage());
+        }
+    }
+
+    private static boolean parseBooleanValue(String name, String value) throws 
DdlException {
+        if (value.equalsIgnoreCase("TRUE")) {
+            return true;
+        } else if (value.equalsIgnoreCase("FALSE")) {
+            return false;
+        }
+        ErrorReport.reportDdlException(ErrorCode.ERR_WRONG_VALUE_FOR_VAR, 
name, value);
+        return false;
+    }
+
     public static void refreshDefaultSessionVariables(String versionMsg, 
String sessionVar, String value) {
         wlock.lock();
         try {
@@ -540,6 +568,7 @@ public class VariableMgr {
                 try {
                     setValue(varContext.getObj(), new 
SessionVariableField(varContext.getField()),
                             root.get(varName).toString());
+                    applyGlobalVariableSideEffect((String) varName, 
root.get(varName).toString());
                 } catch (Exception exception) {
                     LOG.warn("Exception during replay global variabl {} oplog, 
{}, THIS EXCEPTION WILL BE IGNORED.",
                             (String) varName, exception.getMessage());


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

Reply via email to