Skip to content

Per replica throttle#1532

Closed
kaisun2000 wants to merge 33 commits intoapache:masterfrom
kaisun2000:per_replica_throttle
Closed

Per replica throttle#1532
kaisun2000 wants to merge 33 commits intoapache:masterfrom
kaisun2000:per_replica_throttle

Conversation

@kaisun2000
Copy link
Contributor

@kaisun2000 kaisun2000 commented Nov 14, 2020

Issues

  • My PR addresses the following Helix issues and references them in the PR description:

resolve #343

Description

  • Here are some details about my PR, including screenshots of any UI changes:

    Per replica throttling replacing intermediate stage which is partition based. The finer granularity
    would skip boosting unnecessary replica in a recovery partition.

Tests

  • The following tests are written for this issue:
    TestPerReplicaThrottle
    TestPerReplicaThrottleStage

  • The following is the result of the "mvn test" command on the appropriate module:

(Before CI test pass, please copy & paste the result of "mvn test")

Documentation (Optional)

  • In case of new functionality, my PR adds documentation in the following wiki page:

(Link the GitHub wiki you added)

Commits

  • My commits all reference appropriate Apache Helix GitHub issues in their subject lines. In addition, my commits follow the guidelines from "How to write a good git commit message":
    1. Subject is separated from body by a blank line
    2. Subject is limited to 50 characters (not including Jira issue reference)
    3. Subject does not end with a period
    4. Subject uses the imperative mood ("add", not "adding")
    5. Body wraps at 72 characters
    6. Body explains "what" and "why", not "how"

Code Quality

  • My diff has been formatted using helix-style.xml
    (helix-style-intellij.xml if IntelliJ IDE is used)

@kaisun2000
Copy link
Contributor Author

@narendly, thx for the early review. Still in early stage for this feature.Ssome major refactoring and adding more tests are pending.

@kaisun2000 kaisun2000 changed the title Per replica throttle (WIP) Per replica throttle Dec 1, 2020
Copy link
Contributor

@junkaixue junkaixue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the code is too long.. I will sync up with you before next round of review...

Copy link
Contributor

@jiajunwang jiajunwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried the best to review files other than the gigantic PerReplicaThrottleStage.java. May I suggest 2 ways to make reviewing this file possible?

  1. Could you please comment on all the modifications that you made based on the previous class?
  2. [My preference] try to create the new class as a child class of the IntermediateStateCalcStage and then only modifying the necessary method. You can add a TODO here so we can do the cleanup work in another PR. In the later PR we can say that there is no logic change. So even the PR will still be large, there is no issue in reviewing it.

@@ -481,6 +482,7 @@ private static PipelineRegistry createDefaultRegistry(String pipelineName) {
rebalancePipeline.addStage(new MaintenanceRecoveryStage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the comment in the previous line, will the new logic generate different output with the IntermediateStateCalcStage result, so we entering the maintenance mode in the wrong condition?
Maybe we shall move the MaintenanceRecoveryStage after PerReplicaThrottleStage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, let me check the details.

Copy link
Contributor Author

@kaisun2000 kaisun2000 Dec 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved MaintenanceRecoveryStage after Per replica stages and also changed corresponding input change to MaintenanceRecoveryStage

failedResources.add(resourceName);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humm, where is the metrics part? At least we need a TODO here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought about it before. This worth another diff. This one is already very large.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add a to-do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to-do is added in public void process(ClusterEvent event) last line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO is totally fine, but we might need to check in this change into a branch first. Otherwise, it will break the already existing metrics in the master.

Copy link
Contributor Author

@kaisun2000 kaisun2000 Dec 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me add in the implementation of metrics here too. This should be a small change.

Copy link
Contributor Author

@kaisun2000 kaisun2000 Dec 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metrics added.

@kaisun2000
Copy link
Contributor Author

#1532 (comment)

This method seems to be added for debug log only.
For simplifying the business logic, I suggest don't add this method but relying on the existing get method to retrieve information

This is not for debug info only. Note the selectedMessage from previous stage is of this type. Before, we don't need to access it this way. Now per replica stage is using MessageOutput as input, this why we need this method.

@kaisun2000
Copy link
Contributor Author

@jiajunwang , thx for the review, address-ed current main logic feedback. Will add the comments about why test changed next.

Kai Sun added 13 commits December 15, 2020 00:12
the retraced currentstate is feed to per partition message recovery
testing. This should be revisited. Note, recovery message testing
should always based on current state, not retraced. 2/ haven't
finisih sorting of recovery messages and load messages.
current state and best possible only.
2/added comparator to sort recovery and load messages to give them
a priority order before throttling.
2/ enhance zero replica avoidance cluster expansion log; these logs
help to identify n->n+1 corner case that result in 2 replica.
1.1/ cleanupTest did not set DelayedPartition which effectively disabled all test except the first one.
1.2/ cleanupTest did not disableThrottleRecording which effectively fails all test except the first one.
1.3/ filter out transition toState dropped/error which is not subject to throttling
1.4/ enhance logging
2/ PerReplicaThrottleStage
2.1/ correct error shouldThrottleForInstance with wrongly resource name in
2.2/ enhance logging
3/ enhance log4j logging with log rotation
4/IntermediateStateCalcStage has serious bug. For pending message charging RebalanceType.NONE can
be charged which result in ERROR logging. Since we are going to deprecate this stage. Won't propose
more elegant fix.
and online/offline resource bringing ups.
Also add mock online-offline model and model factory to support the testing
of step by step online-offline model
1.1/ added recovery/loaded throttled messages to the event
2/ enhance TestPerReplicaThrottle with better logging upon assertion failure
…void github test failure due to straying awaying.
Copy link
Contributor

@jiajunwang jiajunwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me put some of my comments first. I'm still reviewing it.

My feeling is that the IntermediateStateCalcStage code has been patched too many times. There are some confusing logic and arbitrary designs. It might be easier to re-write it, or at least majorly refactor to simplify it. Otherwise, reviewing the code would cost weeks. And if the new change introduces some undesired behavior, it would be very hard to fix.

failedResources.add(resourceName);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO is totally fine, but we might need to check in this change into a branch first. Otherwise, it will break the already existing metrics in the master.

StateModelDefinition stateModelDef = cache.getStateModelDef(stateModelDefName);

// sort pendingMessages based on transition priority then timeStamp for state transition message
pendingMessages.sort(new PartitionMessageComparator(stateModelDef));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could potentially have problem. The sorting algorithm should be same as last round.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked. This is not a concern. Indeed both chargingPendingMessage and later classifyMessages all use the same sorting comparator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I commented, the logic is complicated. Please merge them first. Otherwise, I cannot determine what is the difference. Will be very hard for reader understanding it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is some confusion here. Let me first make sure we are on the same page about usage of two different comparator -- PartitionMessageComparator and MessageThrottleComparator.

MessageThrottleComparator is the counter part of PartitionPriorityComparator in IntermediateCalcStage.

1/ PartitionMessageComparator are used in two places, namely a) pendingMessages load/recovery classification and b) input message from previous stage load/recovery classification. -- note in the previous IntermediateCalcStage, the is not need for such a comparator as the load/recovery classification is done per partition (not per replica), thus there is no need to give a order.

2/ MessageThrottleComparator is basically the same as PartitionPriorityComparator in IntermediateCalcStage. The usage is to give an order when throttling is applied to load/recovery messages.
The logic is the following -- note this is basically the same as PartitionPriorityComparator in IntermediateCalcStage

  • Higher priority for topState
  • Higher priority for the partition with fewer active replicas
  • Higher priority for the partition with fewer replicas with states matching with IdealState

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically PartitionMessageComparator is only used to sort message (replica) with a partition. This logic is not need in previous IntermediateCalStage.
MessageThrottleComparator is use to determine the message order across partitions and this is the same logic as PartitionPriorityComparator in IntermediateCalcStage.

What is you take?

Comment on lines +547 to +555
if (isUpward && ((currentCount < expectedCount) || (currentCount == expectedCount && toState
.equals(secondTopState) && currentTopCount < expectedTopCount))) {
recoveryMessages.add(msg);
partitionsNeedRecovery.add(partition);
// update
currentStateCounts.put(toState, currentCount + 1);
} else {
loadMessages.add(msg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should think about the general case instead of only single top state and secondary state case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general way is we have 2 things:

  1. As you implemented the expected state map.
  2. Totally matched replica numbers. This number is counting from top priority of the state and accumulatively down to the number of replicas. But there is another rule of "R".

I would suggest you first try to understand the state model rule of how we counting different states of different replicas first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good suggestion to make the code more generic. The accumulation logic is added in getPartitionExpectedAndCurrentStateCountMap and the testing of load/recovery logic is simplified a lot here.

Copy link
Contributor

@junkaixue junkaixue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed to getExpected Replica numbers. Still in reviewing for other parts.

Comment on lines +97 to +111
if (logger.isDebugEnabled()) {
LogUtil.logDebug(logger, _eventId, String.format("output is"));
for (String resource : resourceToRebalance.keySet()) {
if (output.getResourceMessages(resource) != null) {
LogUtil.logDebug(logger, _eventId, String.format("resource: %s", resource));
Map<Partition, List<Message>> partitionListMap = output.getResourceMessages(resource);
for (Partition partition : partitionListMap.keySet()) {
for (Message msg : partitionListMap.get(partition)) {
LogUtil.logDebug(logger, _eventId, String
.format("\tresource: %s, partition: %s, msg: %s", resource, partition, msg));
}
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont need this. Usually we print out the message has been throttled. Next stage should have the message send out. It will be duplicated logs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

Comment on lines +116 to +119
if (isEmitThrottledMsg) {
event.addAttribute(AttributeName.PER_REPLICA_THROTTLED_RECOVERY_MESSAGES.name(), throttledRecoveryMsg);
event.addAttribute(AttributeName.PER_REPLICA_THROTTLED_LOAD_MESSAGES.name(), throttledLoadMsg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's minimize code structure change. We can let the print happening in the code instead of carrying them out at top level and print all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for testing purpose. The test would assert the messages got throttled as load or recovery. It is not for printing purpose. See TestPerReplicaThrottleStage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont believe have real production code for testing is a good idea.

Comment on lines +92 to +93
List<Message> throttledRecoveryMsg = new ArrayList<>();
List<Message> throttledLoadMsg = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to have these inputs and carrying them out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for testing purpose. The test would assert the messages got throttled as load or recovery. It is not for printing purpose. See TestPerReplicaThrottleStage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

* @param retracedResourceStateMap
* @param maxPartitionPerInstance
*/
private void validateMaxPartitionsPerInstance(ResourcesStateMap retracedResourceStateMap,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not change the order of the function. That's the reason diff can not be generated successfully.

Copy link
Contributor Author

@kaisun2000 kaisun2000 Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this one to immediately after compute, the same as Intermediate stage.

Comment on lines +411 to +429
Map<Partition, List<Message>> out = new HashMap<>();
for (Partition partition : resource.getPartitions()) {
List<Message> partitionMessages = selectedResourceMessages.get(partition);
if (partitionMessages == null) {
continue;
}
List<Message> finalPartitionMessages = new ArrayList<>();
for (Message message: partitionMessages) {
if (throttledRecoveryMessages.contains(message)) {
continue;
}
if (throttledLoadMessages.contains(message)) {
continue;
}
finalPartitionMessages.add(message);
}
out.put(partition, finalPartitionMessages);
output.addMessages(resourceName, partition, finalPartitionMessages);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont need to do some loops to construct. Why not we add all the message in new output. Then:

  1. If it is not FULL_AUTO, we directly return.
  2. If message has been throttled, we remove the message from output where it throttled.

So we will not see such a block to construct result.

Copy link
Contributor Author

@kaisun2000 kaisun2000 Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removing idea theoretically also works.

Just looked at implementing this idea. Noted that MessageOutput is the class holding the message. However, this class does not have any removing message helper functions. It is debatable whether we want to add message removing apis to this class.

Considering the benefit of using alternative approach is not really essential, maybe let us just keep the existing approach?

// new instance in best possible not in currentstate would not be in retracedStateMap yet.
String toInstance = message.getTgtName();
Map<String, String> retracedStateMap = retracedPartitionsStateMap.get(partition);
retracedStateMap.put(toInstance, toState);
Copy link
Contributor

@xyuanlu xyuanlu Dec 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

retracedStateMap.put(message.getTgtName(), message.getToState());

might be cleaner.

Copy link
Contributor

@junkaixue junkaixue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed to classifyMessage. Will continue for next round of review.


private void constructRetracedPartitionStateMap(Resource resource,
Map<Partition, Map<String, String>> retracedPartitionsStateMap,
Map<Partition, List<Message>> out) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name it as "out" could be confusing as naming... When I read code, I even looked back to understand what it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to outMessagesByPartition

Map<String, Integer> expectedStateCountMapOut,
Map<String, Integer> currentStateCountsOut
) {
List<String> preferenceList = preferenceLists.get(partition.getPartitionName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it is null? Original code has a check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PreferenceList is null seem to be in the case of dropping instances, let me test this a little bit more to make sure this part is addressed carefully. This can be potentially an issue. Will update a little bit later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested, this is a good point, we should have some check as original code.

}
}

private void getPartitionExpectedAndCurrentStateCountMap(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function only serves the get expected current state. But I expect this function does the job of counting how many expected state just once.

Because once we have it the expected state map, we dont need to compute again in "classifyMessage" function. It just need to do some derive, as the change already reflected from charged pending message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the gist is that you are concerned that we will spend unnecessary CPU cycles to re-count. Instead, we can do the trick of store the data and re-use them later, right? typical space and time trade off.

Changes done.

Comment on lines +640 to +641
getPartitionExpectedAndCurrentStateCountMap(partition, preferenceLists, idealState,
cache, currentStateMap, expectedStateCountMap, currentStateCounts);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not count this again. Since when we charged the pending message, we need to use the retrace map.

Also, expected state does not require to recompute again as I mentioned above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes done.

// 1) toState priority (toTop is higher than toSecond)
// 2) same toState, the message classification time, the less required toState meeting minActive requirement has higher priority
// 3) Higher priority for the partition of messages with fewer replicas with states matching with bestPossible ??? do we need this one
private static class MessageThrottleComparator implements Comparator<Message> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you said, if there is no diff from this comparator and another one. Please merge them. They are very complicated. It will be hard for reader to understand the both. I will review the logic in detail once you merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment before. Let us chat about this one.

Copy link
Contributor

@junkaixue junkaixue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through all the logics already. Please address the comments. And let me know if you have uncertainty of the comments. The major concerns from my comments:

  1. Understand how we compute the number of states and make the general counting algorithm as I suggested.
  2. Merge the two comparators algorithms.

I will review it again after you addressing all the comments. The comparator algorithm is complicated. I will review detailed logic after you finished merging.

expectedCount = expectedCount == null ? 0 : expectedCount;
currentCount = currentCount == null ? 0 : currentCount;

boolean isUpward = !isDownwardTransition(idealState, cache, msg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check this. I know message non-upwards are load but anyway we need to check expected state vs current state. This check is not necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkout upward seems to be essential. Otherwise, for downward message if it happens currentCount < expectedCount, it may classify the message as recovery.

Comment on lines +547 to +555
if (isUpward && ((currentCount < expectedCount) || (currentCount == expectedCount && toState
.equals(secondTopState) && currentTopCount < expectedTopCount))) {
recoveryMessages.add(msg);
partitionsNeedRecovery.add(partition);
// update
currentStateCounts.put(toState, currentCount + 1);
} else {
loadMessages.add(msg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general way is we have 2 things:

  1. As you implemented the expected state map.
  2. Totally matched replica numbers. This number is counting from top priority of the state and accumulatively down to the number of replicas. But there is another rule of "R".

I would suggest you first try to understand the state model rule of how we counting different states of different replicas first.

Comment on lines +584 to +616
for (Message recoveryMsg : recoveryMessages) {
String toState = recoveryMsg.getToState();
String toInstance = recoveryMsg.getTgtName();
// toInstance should be in currentStateMap
retracedStateMap.put(toInstance, toState);

throttleController
.chargeInstance(StateTransitionThrottleConfig.RebalanceType.RECOVERY_BALANCE,
toInstance);
throttleController
.chargeCluster(StateTransitionThrottleConfig.RebalanceType.RECOVERY_BALANCE);
throttleController
.chargeResource(StateTransitionThrottleConfig.RebalanceType.RECOVERY_BALANCE,
resourceName);
logger.trace("throttleControllerstate->{} after pending recovery charge msg:{}", throttleController, recoveryMsg);
}
// charge load message and retrace;
// note if M->S with relay message, we don't charge relay message now. We would charge relay
// message only when it shows in pending messages in the next cycle of controller run.
for (Message loadMsg : loadMessages) {
String toState = loadMsg.getToState();
String toInstance = loadMsg.getTgtName();
retracedStateMap.put(toInstance, toState);

throttleController
.chargeInstance(StateTransitionThrottleConfig.RebalanceType.LOAD_BALANCE, toInstance);
throttleController.chargeCluster(StateTransitionThrottleConfig.RebalanceType.LOAD_BALANCE);
throttleController
.chargeResource(StateTransitionThrottleConfig.RebalanceType.LOAD_BALANCE, resourceName);
logger.trace("throttleControllerstate->{} after pending load charge msg:{}", throttleController, loadMsg);
}
retracedPartitionsStateMap.put(partition, retracedStateMap);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code logic is very duplicated. We can refactor into previous message type decision piece. This would make the code much concise.

loop message:
RebalanceType type = xxxx;
throttleController
.chargeInstance(type,
toInstance);
throttleController
.chargeCluster(type);
throttleController
.chargeResource(type,
resourceName);
logger.trace("throttleControllerstate->{} after pending recovery charge msg:{}", throttleController, recoveryMsg);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed duplicated code.

Comment on lines +704 to +710
if (isUpward && ((currentCount < expectedCount) || (currentCount == expectedCount && toState
.equals(secondTopState) && currentTopCount < expectedTopCount))) {
recoveryMessages.add(msg);
currentStateCounts.put(toState, currentCount + 1);
} else {
loadMessages.add(msg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as I suggested above, let's loop the priority state with an accumulative number for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accumulation logic is added in getPartitionExpectedAndCurrentStateCountMap. thus the testing logic is simplified a lot here.

Kai Sun added 4 commits January 8, 2021 16:14
1/ save the expectedStateCount results for later use
2/ remove unnecessary log.
3/ change out varible name to ones with more context info.
@jiajunwang
Copy link
Contributor

Close due to inactive.

@jiajunwang jiajunwang closed this May 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fine-grained state transition throttling and respect MIN_ACTIVE replica

7 participants