You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by al...@apache.org on 2026/08/06 02:06:10 UTC

(kudu) branch master updated: auto_leader_rebalancer: best-effort leader step-down

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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git


The following commit(s) were added to refs/heads/master by this push:
     new cb747ff0c auto_leader_rebalancer: best-effort leader step-down
cb747ff0c is described below

commit cb747ff0ca0209e3fa98be4167c67edd474acd67
Author: Gabriella Lotz <lo...@gmail.com>
AuthorDate: Thu Jul 16 14:50:56 2026 +0200

    auto_leader_rebalancer: best-effort leader step-down
    
    When rebalancing leaders for a table, RunLeaderRebalanceForTable
    resolved the current leader's address and sent a LeaderStepDown RPC
    with RETURN_NOT_OK. A single transient failure (an RPC timeout, the
    target briefly unavailable, or an election in flight) aborted the whole
    rebalancing pass and skipped every remaining tablet and table in that
    round.
    
    A leader transfer is best effort. The next round recomputes the plan
    and retries, so a single failed transfer should warn and move on to the
    next tablet rather than fail the pass. RunLoop already only logs a
    warning for the overall call, so this just stops one bad RPC from
    starving the rest of the round.
    
    Seen under heavy load, where one failed LeaderStepDown would otherwise
    end a round early and leave the leaders unbalanced for longer.
    
    Change-Id: I0fb2c7a8bf750520d34d9aba62274dbe7433d80d
    Reviewed-on: http://gerrit.cloudera.org:8080/24594
    Tested-by: Alexey Serbin <al...@apache.org>
    Reviewed-by: Alexey Serbin <al...@apache.org>
---
 src/kudu/master/auto_leader_rebalancer-test.cc | 43 ++++++++++++++++++++++++++
 src/kudu/master/auto_leader_rebalancer.cc      | 32 +++++++++++++++++--
 2 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/src/kudu/master/auto_leader_rebalancer-test.cc b/src/kudu/master/auto_leader_rebalancer-test.cc
index cfa4262cf..a2848e26e 100644
--- a/src/kudu/master/auto_leader_rebalancer-test.cc
+++ b/src/kudu/master/auto_leader_rebalancer-test.cc
@@ -87,6 +87,7 @@ DECLARE_uint32(auto_rebalancing_interval_seconds);
 DECLARE_uint32(auto_rebalancing_max_moves_per_server);
 DECLARE_uint32(auto_rebalancing_wait_for_replica_moves_seconds);
 DECLARE_uint32(leader_rebalancing_max_moves_per_round);
+DECLARE_bool(auto_leader_rebalancing_fail_moves_for_test);
 
 namespace kudu {
 namespace master {
@@ -117,11 +118,19 @@ class LeaderRebalancerTest : public KuduTest {
     if (cluster_) {
       cluster_->Shutdown();
     }
+    FLAGS_auto_leader_rebalancing_fail_moves_for_test = false;
     KuduTest::TearDown();
   }
 
   std::string table_name() { return workload_->table_name(); }
 
+  // Exposes the task's private test counter (the fixture is a friend of the
+  // task, individual TEST_F bodies are not).
+  int MovesScheduledThisRoundForTest() {
+    return cluster_->mini_master()->master()->catalog_manager()
+        ->auto_leader_rebalancer()->moves_scheduled_this_round_for_test_;
+  }
+
   Status RunLeaderRebalanceForTable(
       const scoped_refptr<TableInfo>& table_info,
       const vector<string>& tserver_uuids,
@@ -516,6 +525,40 @@ TEST_F(LeaderRebalancerTest, FunctionalTestForNotDivided) {
   }
 }
 
+// A single failed leader step-down RPC must not abort the whole rebalancing
+// pass. With every step-down forced to fail, the rebalancer should still
+// schedule the moves and return OK (before it was best-effort it returned the
+// RPC error and skipped the rest of the round).
+TEST_F(LeaderRebalancerTest, TestStepDownFailureDoesNotAbortPass) {
+  const int kNumTServers = 3;
+  const int kNumTablets = 6;
+  cluster_opts_.num_tablet_servers = kNumTServers;
+  ASSERT_OK(CreateAndStartCluster());
+  CreateWorkloadTable(kNumTablets, /*num_replicas*/ 3);
+
+  // Pile every leader onto ts0 so the rebalancer has transfers to schedule.
+  const string ts0_uuid = cluster_->mini_tablet_server(0)->uuid();
+  ASSERT_OK(MakeLeaderDistribution({kNumTablets, 0, 0}, table_name()));
+  std::map<string, int32_t> leader_map;
+  ASSERT_EVENTUALLY([&] {
+    ASSERT_OK(GetLeaderDistribution(&leader_map, table_name()));
+    // Over the per-tserver target, so moves will be scheduled.
+    ASSERT_GT(leader_map[ts0_uuid], kNumTablets / kNumTServers);
+  });
+
+  master::AutoLeaderRebalancerTask* leader_rebalancer =
+      cluster_->mini_master()->master()->catalog_manager()->auto_leader_rebalancer();
+
+  // Force every step-down RPC to fail. The pass must treat each failure as
+  // best-effort and still return OK instead of aborting the round.
+  FLAGS_auto_leader_rebalancing_fail_moves_for_test = true;
+  ASSERT_OK(leader_rebalancer->RunLeaderRebalancer());
+
+  // The failure path was actually exercised: moves were scheduled (and all
+  // failed), yet the pass still succeeded.
+  ASSERT_GT(MovesScheduledThisRoundForTest(), 0);
+}
+
 // Create a cluster, and create a table,
 // whether tablets is balanced, which decided by creating table process.
 // Bring up another tserver, the table is not balanced and leaders is also
diff --git a/src/kudu/master/auto_leader_rebalancer.cc b/src/kudu/master/auto_leader_rebalancer.cc
index b0224714c..52b51e096 100644
--- a/src/kudu/master/auto_leader_rebalancer.cc
+++ b/src/kudu/master/auto_leader_rebalancer.cc
@@ -96,6 +96,13 @@ DEFINE_bool(leader_rebalancing_ignore_soft_deleted_tables, false,
 TAG_FLAG(leader_rebalancing_ignore_soft_deleted_tables, advanced);
 TAG_FLAG(leader_rebalancing_ignore_soft_deleted_tables, runtime);
 
+DEFINE_bool(auto_leader_rebalancing_fail_moves_for_test, false,
+            "Force every leader step-down RPC issued by the per-table pass of "
+            "the leader rebalancer (RunLeaderRebalanceForTable) to fail. This "
+            "is only used for testing.");
+TAG_FLAG(auto_leader_rebalancing_fail_moves_for_test, unsafe);
+TAG_FLAG(auto_leader_rebalancing_fail_moves_for_test, hidden);
+
 DECLARE_bool(auto_leader_rebalancing_enabled);
 
 namespace kudu {
@@ -431,16 +438,35 @@ Status AutoLeaderRebalancerTask::RunLeaderRebalanceForTable(
     }
 
     vector<Sockaddr> resolved;
-    RETURN_NOT_OK(host_port->ResolveAddresses(&resolved));
+    if (Status s = host_port->ResolveAddresses(&resolved); !s.ok()) {
+      WARN_NOT_OK(s, Substitute("leader transfer for tablet $0: could not resolve $1",
+                                task.first, host_port->ToString()));
+      continue;
+    }
     ConsensusServiceProxy proxy(messenger_, resolved[0], host_port->host());
-    RETURN_NOT_OK(proxy.LeaderStepDown(request, &response, &rpc));
-    leader_transfer_count++;
+    // A single leader transfer is best effort. A transient failure (an RPC
+    // timeout, the target briefly unavailable, or an election in flight) should
+    // not abort the whole rebalancing pass and skip the remaining tablets and
+    // tables; the next round recomputes and retries.
+    Status s = PREDICT_FALSE(FLAGS_auto_leader_rebalancing_fail_moves_for_test)
+        ? Status::ServiceUnavailable("TEST: forced leader step-down failure")
+        : proxy.LeaderStepDown(request, &response, &rpc);
+    if (!s.ok()) {
+      WARN_NOT_OK(s, Substitute("leader transfer for tablet $0 from $1 to $2 failed",
+                                task.first, leader_uuid, task.second.second));
+      continue;
+    }
     if (!response.has_error()) {
+      leader_transfer_count++;
       VLOG(1) << Substitute("leader transfer table: $0, tablet_id: $1, from: $2 to: $3",
                             table_data.name(),
                             task.first,
                             leader_uuid,
                             task.second.second);
+    } else {
+      LOG(WARNING) << Substitute(
+          "leader transfer for tablet $0 (from $1 to $2) failed: $3",
+          task.first, leader_uuid, task.second.second, response.error().ShortDebugString());
     }
   }
   // @TODO(duyuqi)