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

jiajunwang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/helix.git


The following commit(s) were added to refs/heads/master by this push:
     new a1aba60  Adjust the auto rebalancer state assignment logic to reduce 
top state transition. (#986)
a1aba60 is described below

commit a1aba60a6701692f42ed650cae88827a26e93183
Author: Jiajun Wang <[email protected]>
AuthorDate: Wed May 6 15:48:15 2020 -0700

    Adjust the auto rebalancer state assignment logic to reduce top state 
transition. (#986)
    
    The old state assignment logic assign the states to selected nodes 
according to the priority of the current replica state that is on the instance. 
Moreover, the sorting algorithm is designed to prioritize both current topstate 
and current secondary states equally. The result is that we will have premature 
mastership handoff to a current seconardy state host before the real desired 
master host is ready.
    For example,
    1. The current states are: [N1:M, N2:S, N3,S]
    2. The desired states are: [N4:M, N2:S, N1:S]
    3. Due to the sorting logic based on current states, we will have a 
transient preference list ordered like: [N2, N1, N4]. In which case, the 
controller will assign master to N2 before N4 has a slave state replica.
    4. When N4 finishes the Offline to Slave transition, the same sorting logic 
will sort the preference list to be: [N4, N2, N1]. Then we have another 
mastership handoff.
    To be clear, we don't want step 3. But only the state transition in step 4.
    
    In this PR, we refactor the sorting logic so that it will only move the 
master whenever the candidate has a "ready" state replica, in which case, only 
one mastership handoff happens.
---
 .../controller/rebalancer/AbstractRebalancer.java  | 78 +++++++++++-----------
 .../rebalancer/DelayedAutoRebalancer.java          |  2 +-
 .../rebalancer/TestAbstractRebalancer.java         |  6 +-
 ...bstractRebalancer.ComputeBestPossibleState.json | 27 ++++++++
 4 files changed, 71 insertions(+), 42 deletions(-)

diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/AbstractRebalancer.java
 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/AbstractRebalancer.java
index 9416c7e..e85a2df 100644
--- 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/AbstractRebalancer.java
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/AbstractRebalancer.java
@@ -305,45 +305,60 @@ public abstract class AbstractRebalancer<T extends 
BaseControllerDataProvider> i
     Set<String> liveAndEnabled = new HashSet<>(liveInstances);
     liveAndEnabled.removeAll(disabledInstancesForPartition);
 
-    for (String state : statesPriorityList) {
-      // Use the the specially ordered preferenceList for choosing instance 
for top state.
-      if (state.equals(statesPriorityList.get(0))) {
-        List<String> preferenceListForTopState = new 
ArrayList<>(preferenceList);
-        Collections.sort(preferenceListForTopState,
-            new TopStatePreferenceListComparator(currentStateMap, 
stateModelDef));
-        preferenceList = preferenceListForTopState;
-      }
+    // Sort the instances based on replicas' state priority in the current 
state
+    List<String> sortedPreferenceList = new ArrayList<>(preferenceList);
+    sortedPreferenceList.sort(new StatePriorityComparator(currentStateMap, 
stateModelDef));
 
+    // Assign the state to the instances that appear in the preference list.
+    for (String state : statesPriorityList) {
       int stateCount =
           getStateCount(state, stateModelDef, liveAndEnabled.size(), 
preferenceList.size());
       for (String instance : preferenceList) {
         if (stateCount <= 0) {
-          break;
+          break; // continue assigning for the next state
+        }
+        if (assigned.contains(instance) || !liveAndEnabled.contains(instance)) 
{
+          continue; // continue checking for the next available instance
         }
-        if (!assigned.contains(instance) && liveAndEnabled.contains(instance)) 
{
-          if 
(HelixDefinedState.ERROR.toString().equals(currentStateMap.get(instance))) {
-            bestPossibleStateMap.put(instance, 
HelixDefinedState.ERROR.toString());
-          } else {
-            bestPossibleStateMap.put(instance, state);
-            stateCount--;
+        String proposedInstance = instance;
+        // Additional check and alternate the assignment for reducing top 
state handoff.
+        if (state.equals(stateModelDef.getTopState()) && 
!stateModelDef.getSecondTopStates()
+            .contains(currentStateMap.getOrDefault(instance, 
stateModelDef.getInitialState()))) {
+          // If the desired state is the top state, but the instance cannot be 
transited to the
+          // top state in one hop, try to keep the top state on current host 
or a host with a closer
+          // state.
+          for (String currentStatePrioritizedInstance : sortedPreferenceList) {
+            if (!assigned.contains(currentStatePrioritizedInstance) && 
liveAndEnabled
+                .contains(currentStatePrioritizedInstance)) {
+              proposedInstance = currentStatePrioritizedInstance;
+              break;
+            }
           }
-          assigned.add(instance);
+          // Note that if all the current top state instances are not 
assignable, then we fallback
+          // to the default logic that assigning the state according to 
preference list order.
         }
+        // Assign the desired state to the proposed instance
+        if 
(HelixDefinedState.ERROR.toString().equals(currentStateMap.get(proposedInstance)))
 {
+          bestPossibleStateMap.put(proposedInstance, 
HelixDefinedState.ERROR.toString());
+        } else {
+          bestPossibleStateMap.put(proposedInstance, state);
+          stateCount--;
+        }
+        assigned.add(proposedInstance);
       }
     }
-
     return bestPossibleStateMap;
   }
 
   /**
-   * Sorter for nodes that sorts according to the CurrentState of the 
partition. There are only two priorities:
-   * (1) Top-state and second states have priority 0. (2) Other states(or no 
state) have priority 1.
+   * Sorter for nodes that sorts according to the CurrentState of the 
partition.
    */
-  protected static class TopStatePreferenceListComparator implements 
Comparator<String> {
-    protected final Map<String, String> _currentStateMap;
-    protected final StateModelDefinition _stateModelDef;
+  protected static class StatePriorityComparator implements Comparator<String> 
{
+    private final Map<String, String> _currentStateMap;
+    private final StateModelDefinition _stateModelDef;
 
-    public TopStatePreferenceListComparator(Map<String, String> 
currentStateMap, StateModelDefinition stateModelDef) {
+    public StatePriorityComparator(Map<String, String> currentStateMap,
+        StateModelDefinition stateModelDef) {
       _currentStateMap = currentStateMap;
       _stateModelDef = stateModelDef;
     }
@@ -352,21 +367,8 @@ public abstract class AbstractRebalancer<T extends 
BaseControllerDataProvider> i
     public int compare(String ins1, String ins2) {
       String state1 = _currentStateMap.get(ins1);
       String state2 = _currentStateMap.get(ins2);
-
-      String topState = _stateModelDef.getStatesPriorityList().get(0);
-      Set<String> preferredStates = new 
HashSet<String>(_stateModelDef.getSecondTopStates());
-      preferredStates.add(topState);
-
-      int p1 = 1;
-      int p2 = 1;
-
-      if (state1 != null && preferredStates.contains(state1)) {
-        p1 = 0;
-      }
-      if (state2 != null && preferredStates.contains(state2)) {
-        p2 = 0;
-      }
-
+      int p1 = state1 == null ? Integer.MAX_VALUE : 
_stateModelDef.getStatePriorityMap().get(state1);
+      int p2 = state2 == null ? Integer.MAX_VALUE : 
_stateModelDef.getStatePriorityMap().get(state2);
       return p1 - p2;
     }
   }
diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/DelayedAutoRebalancer.java
 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/DelayedAutoRebalancer.java
index 53c1225..f4c95a6 100644
--- 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/DelayedAutoRebalancer.java
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/DelayedAutoRebalancer.java
@@ -357,7 +357,7 @@ public class DelayedAutoRebalancer extends 
AbstractRebalancer<ResourceController
           .subList(0, subListSize <= instanceToAdd.size() ? subListSize : 
instanceToAdd.size()));
     }
 
-    // Make all intial state instance not in preference list to be dropped.
+    // Make all initial state instance not in preference list to be dropped.
     Map<String, String> currentMapWithPreferenceList = new 
HashMap<>(currentStateMap);
     currentMapWithPreferenceList.keySet().retainAll(preferenceList);
 
diff --git 
a/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestAbstractRebalancer.java
 
b/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestAbstractRebalancer.java
index a3c2d3f..0886768 100644
--- 
a/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestAbstractRebalancer.java
+++ 
b/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestAbstractRebalancer.java
@@ -49,12 +49,12 @@ public class TestAbstractRebalancer {
           .setCurrentState("test", partition, instance, 
currentStateMap.get(instance));
     }
     Map<String, String> bestPossibleMap = rebalancer
-        .computeBestPossibleStateForPartition(new 
HashSet<String>(liveInstances),
+        .computeBestPossibleStateForPartition(new HashSet<>(liveInstances),
             
BuiltInStateModelDefinitions.valueOf(stateModelName).getStateModelDefinition(),
-            preferenceList, currentStateOutput, new 
HashSet<String>(disabledInstancesForPartition),
+            preferenceList, currentStateOutput, new 
HashSet<>(disabledInstancesForPartition),
             new IdealState("test"), new ClusterConfig("TestCluster"), 
partition);
 
-    Assert.assertTrue(bestPossibleMap.equals(expectedBestPossibleMap));
+    Assert.assertEquals(bestPossibleMap, expectedBestPossibleMap);
   }
 
   @DataProvider(name = "TestComputeBestPossibleStateInput")
diff --git 
a/helix-core/src/test/resources/TestAbstractRebalancer.ComputeBestPossibleState.json
 
b/helix-core/src/test/resources/TestAbstractRebalancer.ComputeBestPossibleState.json
index 16d7cf0..8aac5c8 100644
--- 
a/helix-core/src/test/resources/TestAbstractRebalancer.ComputeBestPossibleState.json
+++ 
b/helix-core/src/test/resources/TestAbstractRebalancer.ComputeBestPossibleState.json
@@ -171,5 +171,32 @@
       "node_2": "SLAVE",
       "node_3": "SLAVE"
     }
+  },
+  {
+    "comment": "Rebalancer switched master to a new instance. Before the new 
instance ready, keep the master at the original node to avoid extra mastership 
handoff.",
+    "stateModel": "MasterSlave",
+    "liveInstances": [
+      "node_1",
+      "node_2",
+      "node_3",
+      "node_4"
+    ],
+    "preferenceList": [
+      "node_4",
+      "node_2",
+      "node_1"
+    ],
+    "currentStateMap": {
+      "node_1": "MASTER",
+      "node_2": "SLAVE",
+      "node_3": "SLAVE"
+    },
+    "disabledInstancesForPartition": [],
+    "expectedBestPossibleStateMap": {
+      "node_1": "MASTER",
+      "node_2": "SLAVE",
+      "node_3": "DROPPED",
+      "node_4": "SLAVE"
+    }
   }
 ]

Reply via email to