Skip to content

Conversation

dgolitsyn
Copy link
Contributor

@dgolitsyn dgolitsyn commented Aug 30, 2017

CachingCostBalancerStrategy is fast implementation of CostBalancerStrategy. Computation algorithm slightly differs from the original version and allows pre-compute and cache cost function values to reduce complexity of the algorithm.

Motivation:

  1. Current implementation is very complex computational task and calculation time grows linearly with number of segments in the cluster. For rather big cluster one druid balancer iteration could take tens of minutes
  2. If large number of segments need to be rebalanced, cost function calculation could take hours or even days (if number of historical nodes changed for instance)
  3. Total server cost could not be calculated due to high computational complexity and thus could not be included in balancing algorithm

Implementation details:

  • Result numbers are not equal for CachingCostBalancerStrategy and CostBalancerStrategy, but decisions made are the same, meaning that the same server has been chosen for particular segment by two strategies (has been tested for ~100k segments from our cluster)
  • CachingCostBalancerStrategy is less memory efficient and requires ~100mb heap for each million of segments
  • Computation time is ~10000x faster (depends on segment distribution and total number of servers and segments)

Benchmark

cachingCostStrategy  avgt   10      1.766 ±    0.035  us/op
costStrategy         avgt   10  21987.245 ± 3593.512  us/op

@leventov leventov changed the title Add CachingCostBalancerStrategy; Rename ServerView.ServerCallback to … Add CachingCostBalancerStrategy Aug 30, 2017
}
}

protected void runServerCallbacks(final DruidServer server)
Copy link
Contributor

Choose a reason for hiding this comment

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

runServerRemovedCallbacks

Copy link
Member

Choose a reason for hiding this comment

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

Renamed

if (!lifecycleLock.canStop()) {
throw new ISE("CachingCostBalancerStrategyFactory can not be stopped");
}
executor.shutdownNow();
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be in a try block whose finally calls exitStop I think?

Copy link
Member

Choose a reason for hiding this comment

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

exitStop() should be called only if lifecycled object is recycleable

Copy link
Member

Choose a reason for hiding this comment

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

Added a comment

* LIFE_THRESHOLD is used to avoid calculations for segments that are "far"
* from each other and thus cost(X,Y) ~ 0 for these segments
*/
private static final long LIFE_THRESHOLD = TimeUnit.DAYS.toMillis(30);
Copy link
Contributor

Choose a reason for hiding this comment

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

outside scope of this PR, but this would be nice to have configurable some day.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed

Interval interval = getBucketInterval(segment);
buckets.computeIfPresent(
interval,
(i, builder) -> builder.removeSegment(segment).isEmpty() ? null : builder
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this remove the key or just set the value to null?

Copy link
Member

Choose a reason for hiding this comment

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

Remove the key, added a comment

Copy link
Contributor

Choose a reason for hiding this comment

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

Is that guaranteed anywhere, or just specific to some implementations? Here's where my java8 knowledge falls flat.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yes, I missed it on my other read through. thanks!

double leftCost = 0.0;
// add to cost all left-overlapping segments
int leftIndex = index - 1;
while (leftIndex >= 0
Copy link
Contributor

Choose a reason for hiding this comment

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

This loop looks pretty odd, can it simply be

for (int leftIndex = index - 1; leftIndex >=0; --leftIndex) {
final Segment segment = sortedSegments.get(leftIndex);
if (!segment.getInterval().overlaps(dataSegment.getInterval()))
{
  break;
}
final double start = convertStart(sortedSegments.get(leftIndex), interval);
final double end = convertEnd(sortedSegments.get(leftIndex), interval);
leftCost += CostBalancerStrategy.intervalCost(end - start, t0 - start, t1 - start);
}

or similar?

Copy link
Member

Choose a reason for hiding this comment

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

It uses leftCost variable outside of the loop

double rightCost = 0.0;
// add all right-overlapping segments
int rightIndex = index;
while (rightIndex < sortedSegments.size() &&
Copy link
Contributor

Choose a reason for hiding this comment

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

same thing about using a for loop for readability.


public static class Builder
{
private final Map<String, ServerCostCache.Builder> serversCostCache = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

This exec is not used to populate the builder. Another one, stored in a field in CachingCostBalancerStrategyFactory is always single-threaded, added a comment about that.

@leventov
Copy link
Member

leventov commented Sep 6, 2017

@drcrallen thanks for review, addressed comments

@drcrallen
Copy link
Contributor

If this is the same as the prior cost balancer strategy, can it reuse the tests from io.druid.server.coordinator.CostBalancerStrategyTest?

@leventov
Copy link
Member

leventov commented Sep 8, 2017

It doesn't produce the same numbers, but (supposedly) makes the same decisions. That's what CachingCostBalancerStrategyTest verifies.

@leventov leventov merged commit 752151f into apache:master Sep 8, 2017
@leventov leventov deleted the caching-cost-balancer-on-upstream branch September 8, 2017 17:23
@jon-wei jon-wei added this to the 0.11.0 milestone Oct 18, 2017
gianm pushed a commit to implydata/druid-public that referenced this pull request Oct 20, 2017
* Add CachingCostBalancerStrategy; Rename ServerView.ServerCallback to ServerRemovedCallback

* Fix benchmark units

* Style, forbidden-api, review, bug fixes

* Add docs

* Address comments
seoeun25 pushed a commit to seoeun25/incubator-druid that referenced this pull request Jan 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants