/* * 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; } }