blob: 4433f97290c07aa5147e60c4ff23cd416a8b76bc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.dht;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Partition splitter.
*/
public abstract class Splitter
{
private final IPartitioner partitioner;
protected Splitter(IPartitioner partitioner)
{
this.partitioner = partitioner;
}
protected abstract Token tokenForValue(BigInteger value);
protected abstract BigInteger valueForToken(Token token);
public List<Token> splitOwnedRanges(int parts, List<Range<Token>> localRanges, boolean dontSplitRanges)
{
if (localRanges.isEmpty() || parts == 1)
return Collections.singletonList(partitioner.getMaximumToken());
BigInteger totalTokens = BigInteger.ZERO;
for (Range<Token> r : localRanges)
{
BigInteger right = valueForToken(token(r.right));
totalTokens = totalTokens.add(right.subtract(valueForToken(r.left)));
}
BigInteger perPart = totalTokens.divide(BigInteger.valueOf(parts));
// the range owned is so tiny we can't split it:
if (perPart.equals(BigInteger.ZERO))
return Collections.singletonList(partitioner.getMaximumToken());
if (dontSplitRanges)
return splitOwnedRangesNoPartialRanges(localRanges, perPart, parts);
List<Token> boundaries = new ArrayList<>();
BigInteger sum = BigInteger.ZERO;
for (Range<Token> r : localRanges)
{
Token right = token(r.right);
BigInteger currentRangeWidth = valueForToken(right).subtract(valueForToken(r.left)).abs();
BigInteger left = valueForToken(r.left);
while (sum.add(currentRangeWidth).compareTo(perPart) >= 0)
{
BigInteger withinRangeBoundary = perPart.subtract(sum);
left = left.add(withinRangeBoundary);
boundaries.add(tokenForValue(left));
currentRangeWidth = currentRangeWidth.subtract(withinRangeBoundary);
sum = BigInteger.ZERO;
}
sum = sum.add(currentRangeWidth);
}
boundaries.set(boundaries.size() - 1, partitioner.getMaximumToken());
assert boundaries.size() == parts : boundaries.size() + "!=" + parts + " " + boundaries + ":" + localRanges;
return boundaries;
}
private List<Token> splitOwnedRangesNoPartialRanges(List<Range<Token>> localRanges, BigInteger perPart, int parts)
{
List<Token> boundaries = new ArrayList<>(parts);
BigInteger sum = BigInteger.ZERO;
int i = 0;
final int rangesCount = localRanges.size();
while (boundaries.size() < parts - 1 && i < rangesCount - 1)
{
Range<Token> r = localRanges.get(i);
Range<Token> nextRange = localRanges.get(i + 1);
Token right = token(r.right);
Token nextRight = token(nextRange.right);
BigInteger currentRangeWidth = valueForToken(right).subtract(valueForToken(r.left));
BigInteger nextRangeWidth = valueForToken(nextRight).subtract(valueForToken(nextRange.left));
sum = sum.add(currentRangeWidth);
// does this or next range take us beyond the per part limit?
if (sum.compareTo(perPart) > 0 || sum.add(nextRangeWidth).compareTo(perPart) > 0)
{
// Either this or the next range will take us beyond the perPart limit. Will stopping now or
// adding the next range create the smallest difference to perPart?
BigInteger diffCurrent = sum.subtract(perPart).abs();
BigInteger diffNext = sum.add(nextRangeWidth).subtract(perPart).abs();
if (diffNext.compareTo(diffCurrent) >= 0)
{
sum = BigInteger.ZERO;
boundaries.add(right);
}
}
i++;
}
boundaries.add(partitioner.getMaximumToken());
return boundaries;
}
/**
* We avoid calculating for wrap around ranges, instead we use the actual max token, and then, when translating
* to PartitionPositions, we include tokens from .minKeyBound to .maxKeyBound to make sure we include all tokens.
*/
private Token token(Token t)
{
return t.equals(partitioner.getMinimumToken()) ? partitioner.getMaximumToken() : t;
}
}