
com.bigdata.resources.SplitUtility Maven / Gradle / Ivy
Show all versions of bigdata-core Show documentation
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Feb 6, 2009
*/
package com.bigdata.resources;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Future;
import org.apache.log4j.Logger;
import com.bigdata.btree.BTree;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.ILocalBTreeView;
import com.bigdata.btree.ISimpleSplitHandler;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.btree.IndexSegment;
import com.bigdata.btree.Leaf;
import com.bigdata.btree.Node;
import com.bigdata.journal.TimestampUtility;
import com.bigdata.mdi.LocalPartitionMetadata;
import com.bigdata.service.Event;
import com.bigdata.service.Split;
import com.bigdata.util.BytesUtil;
import com.bigdata.util.concurrent.ExecutionExceptions;
/**
* Utility methods for {@link ISimpleSplitHandler}s and friends.
*
* @author Bryan Thompson
* @version $Id$
*/
public class SplitUtility {
private static final Logger log = Logger.getLogger(SplitUtility.class);
/**
* Dynamic sharding operations are not currently supported for indices with
* more than MAX_INT tuples. It is not really a use case for scale-out, and
* that is what the SplitUtility is designed to support. Various methods in
* this class therefore will cast to an int32 value.
*
* @param entryCount
* The entry count of some index.
*
* @return The int32 entry count.
*
* @throws UnsupportedOperationException
* if entryCount is GT MAX_INT.
*/
static private int assertEntryCount(final long entryCount) {
if (entryCount >= Integer.MAX_VALUE)
throw new UnsupportedOperationException(
"More than GT MAX_INT tuples: n=" + entryCount);
return (int) entryCount;
}
/**
* Validate splits, including: that the separator keys are strictly
* ascending, that the separator keys perfectly cover the source key range
* without overlap, that the rightSeparator for each split is the
* leftSeparator for the prior split, that the fromIndex offsets are
* strictly ascending, etc.
*
* @param src
* The source index.
* @param splits
* The recommended split points.
*
* @throws IllegalArgumentException
* if either argument is null
.
* @throws IllegalArgumentException
* if the source index is not an index partition (if
* {@link IndexMetadata#getPartitionMetadata() returns null
).
*/
static public void validateSplits(final IIndex src, final Split[] splits) {
if (src == null)
throw new IllegalArgumentException();
if (splits == null)
throw new IllegalArgumentException();
final LocalPartitionMetadata pmd = src.getIndexMetadata()
.getPartitionMetadata();
if (pmd == null)
throw new IllegalArgumentException();
validateSplits(pmd, splits, true/* checkFromToIndex */);
}
/**
* Validate splits, including: that the separator keys are strictly
* ascending, that the separator keys perfectly cover the source key range
* without overlap, that the rightSeparator for each split is the
* leftSeparator for the prior split, that the fromIndex offsets are
* strictly ascending, etc.
*
* @param originalPartitionMetadata
* The description of the key range of the index partition.
* @param splits
* The recommended split points.
* @param checkFromToIndex
* If the {@link Split#fromIndex}, {@link Split#toIndex} and
* {@link Split#ntuples} fields should be validated.
*
* @throws IllegalArgumentException
* if any argument is null
.
*/
static public void validateSplits(
final LocalPartitionMetadata originalPartitionMetadata,
final Split[] splits,
final boolean checkFromToIndex) {
if (originalPartitionMetadata == null)
throw new IllegalArgumentException();
if (splits == null)
throw new IllegalArgumentException("splits[] is null.");
final int nsplits = splits.length;
if (nsplits <= 1)
throw new AssertionError(
"Expecting at least two splits, but found " + nsplits);
// verify splits obey index order constraints.
int lastToIndex = -1;
// Note: the first leftSeparator must be this value.
byte[] fromKey = originalPartitionMetadata.getLeftSeparatorKey();
for (int i = 0; i < nsplits; i++) {
final Split split = splits[i];
if (split == null)
throw new AssertionError();
if(split.pmd == null)
throw new AssertionError();
if(!(split.pmd instanceof LocalPartitionMetadata))
throw new AssertionError();
final LocalPartitionMetadata pmd = (LocalPartitionMetadata) split.pmd;
// check the leftSeparator key.
if(pmd.getLeftSeparatorKey() == null)
throw new AssertionError();
if(!BytesUtil.bytesEqual(fromKey, pmd.getLeftSeparatorKey()))
throw new AssertionError();
// verify rightSeparator is ordered after the left
// separator.
if(pmd.getRightSeparatorKey() != null) {
if(BytesUtil.compareBytes(fromKey, pmd
.getRightSeparatorKey()) >= 0)
throw new AssertionError();
}
// next expected leftSeparatorKey.
fromKey = pmd.getRightSeparatorKey();
if (checkFromToIndex) {
if (i == 0) {
if (split.fromIndex != 0)
throw new AssertionError();
if (split.toIndex <= split.fromIndex)
throw new AssertionError();
} else {
if (split.fromIndex != lastToIndex)
throw new AssertionError();
}
if (i + 1 == nsplits && split.toIndex == 0) {
/*
* Note: This is allowed in case the index partition has
* more than int32 entries in which case the toIndex of the
* last split can not be defined and will be zero.
*/
if (split.ntuples != 0)
throw new AssertionError();
log.warn("Last split has no definate tuple count");
} else {
if (split.toIndex - split.fromIndex != split.ntuples)
throw new AssertionError();
}
}
lastToIndex = split.toIndex;
}
/*
* verify left separator key for 1st partition is equal to the left
* separator key of the source (this condition is also checked
* above).
*/
if (!BytesUtil.bytesEqual(originalPartitionMetadata
.getLeftSeparatorKey(), splits[0].pmd.getLeftSeparatorKey())) {
throw new AssertionError("leftSeparator[0]"
+ //
": expected="
+ BytesUtil.toString(originalPartitionMetadata
.getLeftSeparatorKey())
+ //
", actual="
+ BytesUtil.toString(splits[0].pmd.getLeftSeparatorKey()));
}
/*
* verify right separator key for last partition is equal to the
* right separator key of the source.
*/
{
// right separator for the last split.
final byte[] rightSeparator = ((LocalPartitionMetadata) splits[splits.length - 1].pmd)
.getRightSeparatorKey();
if(rightSeparator == null ) {
// if null then the source right separator must have been null.
if (originalPartitionMetadata.getRightSeparatorKey() != null)
throw new AssertionError(
"rightSeparator for lastSplit: expected="
+ BytesUtil
.toString(originalPartitionMetadata
.getRightSeparatorKey())
+ ", actual=null");
} else {
// otherwise must compare as equals byte-by-byte.
if (!rightSeparator.equals(originalPartitionMetadata
.getRightSeparatorKey()))
throw new AssertionError(
"rightSeparator for lastSplit: expected="
+ BytesUtil
.toString(originalPartitionMetadata
.getRightSeparatorKey())
+ ", actual="
+ BytesUtil.toString(rightSeparator));
}
}
}
/**
* Identifies the splits for an index with heavy write append behavior.
*
* The split point is chosen by locating the right-most non-leaf node. The
* key range which would enter that node is placed within the new
* right-sibling index partition (the tail). The rest of the key range is
* placed within the new left-sibling index partition (the head).
*
*
* @param btree
* The {@link BTree}.
*
* @return The splits (split[0] is the head split, split[1] is the tail
* split).
*/
public static Split[] tailSplit(final ResourceManager resourceManager,
final BTree btree) {
if (resourceManager == null)
throw new IllegalArgumentException();
if (btree == null)
throw new IllegalArgumentException();
if (btree.getHeight() == 0) {
throw new IllegalArgumentException("B+Tree is only a root leaf.");
}
// The name of the scale-out index.
final String name = btree.getIndexMetadata().getName();
// The metadata for the index partition that is being split.
final LocalPartitionMetadata oldpmd = btree.getIndexMetadata().getPartitionMetadata();
if (oldpmd == null) {
throw new RuntimeException("Not an index partition?");
}
/*
* We need to choose a key that will separate the head and the tail and
* also identify the index of the last key that will enter into the
* head. We do this using the right-most node (not a leaf) in the
* mutable BTree loaded from the last commit time on the old journal.
*
* First we choose a Leaf which is a child of that node. The one in the
* middle is choosen as a decent guess at where we might split the index
* in order to leave some activity on the head but most activity on the
* tail.
*
* Then we choose a specific key in the leaf which will be the first key
* NOT copied into the head. For simplicity, we choose the first key in
* this leaf since it is always defined.
*/
final Node node = (Node) btree.getRightMostNode(true/* nodesOnly */);
// choose the leaf that is in the center of the nodes children.
// final int childIndex = (node.getChildCount() + 1) / 2;
// choose the first leaf that is a child of this node.
final int childIndex = 0;
// leaf from the middle of the leaves of the node.
final Leaf leaf = (Leaf) node.getChild(childIndex);
// separator key is the first key in the leaf.
final byte[] separatorKey = leaf == null ? null : leaf.getKeys()
.get(0/* index */);
if (leaf == null || separatorKey == null) {
/*
* Note: I have never seen a problem here.
*/
throw new RuntimeException("Could not locate separator key? Node="
+ node
+ ", nchildren="
+ node.getChildCount()
+ ", childIndex="
+ childIndex
+ ", leaf="
+ leaf
+ (leaf == null ? "" : ("nkeys=" + leaf.getKeyCount()
+ ", keys=" + leaf.getKeys())));
}
// The index within the btree of the tuple associated with that key.
final int separatorIndex = assertEntryCount(btree.indexOf(separatorKey));
/*
* The key must exist since we just discovered it. even if the tuple is
* deleted, indexOf should return its index and not its insertion point.
*/
assert separatorIndex >= 0;
/*
* Ready to define the splits.
*/
final Split[] splits = new Split[2];
{
/*
* Head split.
*/
// New partition identifier.
final int partitionId = resourceManager.nextPartitionId(name);
// Note: always assign the leftSeparator to the head split.
final byte[] fromKey = oldpmd.getLeftSeparatorKey();
final LocalPartitionMetadata pmd = new LocalPartitionMetadata(
partitionId, //
-1, // Note: split not allowed during move.
fromKey,//
separatorKey,//
/*
* Note: no resources for an index segment
*/
null,//
/*
* Note: cause will be set by the atomic update task.
*/
null//
// , oldpmd.getHistory()
// + "chooseTailSplitPoint(oldPartitionId="
// + oldpmd.getPartitionId() + ",nsplits=" + 2
// + ",newPartitionId=" + partitionId + ") "
);
final int fromIndex = 0;
splits[0] = new Split(pmd, fromIndex, separatorIndex);
}
{
/*
* Tail split.
*/
// New partition identifier.
final int partitionId = resourceManager.nextPartitionId(name);
// Note: always assign the rightSeparator to the tail split.
final byte[] toKey = oldpmd.getRightSeparatorKey();
final LocalPartitionMetadata pmd = new LocalPartitionMetadata(
partitionId,//
-1, // Note: split not allowed during move.
separatorKey,//
toKey,//
/*
* Note: no resources for an index segment
*/
null,//
/*
* Note: Cause will be set by the atomic update for the
* split task.
*/
null//
// , oldpmd.getHistory()
// + "chooseTailSplitPoint(oldPartitionId="
// + oldpmd.getPartitionId() + ",nsplits=" + 2
// + ",newPartitionId=" + partitionId + ") "
);
/*
* Note: The index of the last tuple in the btree will be the
* entryCount of the B+Tree. We want one beyond that last tuple
* since this is the index of the first tuple NOT to be included in
* the tail split.
*/
splits[1] = new Split(pmd, separatorIndex, (int)btree.getEntryCount());
}
return splits;
}
/**
* Build N index segments based on those split points.
*
* Note: This is done in parallel to minimize latency.
*
* Note: The generated {@link IndexSegment}s are on the retentionSet and
* MUST be removed from that set once it has been incorporated in a restart
* safe manner into an index partition view or once the task fails.
*
* @see StoreManager#retentionSetAdd(java.util.UUID)
*
* @throws InterruptedException
* @throws ExecutionExceptions
*
* @todo The operation could be serialized (or run with limited parallelism)
* in order to minimize the RAM burden for buffers during index
* segment creation. You can also limit the parallelism to some upper
* bound. During normal operations, the #of splits generated should be
* fairly small, e.g., N >= 2 and N ~ 2. This requires a thread pool
* (or delegate for a thread pool) that can impose a limit on the
* actual parallelism.
*/
public static SplitResult buildSplits(final ViewMetadata vmd,
final Split[] splits, final Event parentEvent)
throws InterruptedException, ExecutionExceptions {
if (vmd == null)
throw new IllegalArgumentException();
if (splits == null)
throw new IllegalArgumentException();
final int nsplits = splits.length;
final List tasks = new ArrayList(
nsplits);
for (int i = 0; i < splits.length; i++) {
final Split split = splits[i];
/*
* Create task to build an index segment from the key-range
* for the split.
*/
final BuildIndexSegmentSplitTask task = new BuildIndexSegmentSplitTask(
vmd, split, parentEvent);
// add to set of tasks to be run.
tasks.add(task);
}
// submit and await completion.
final List> futures = vmd.resourceManager
.getConcurrencyManager().invokeAll(tasks);
// copy the individual build results into an array.
final BuildResult[] buildResults = new BuildResult[nsplits];
final List causes = new LinkedList();
{
int i = 0;
for (Future f : futures) {
try {
buildResults[i] = f.get();
} catch (Throwable t) {
causes.add(t);
log.error(t.getLocalizedMessage());
}
// increment regardless of the task outcome.
i++;
}
}
if (!causes.isEmpty()) {
/*
* Error handling - remove all generated files.
*/
for (BuildResult result : buildResults) {
if (result == null)
continue;
// make it releasable.
vmd.resourceManager.retentionSetRemove(result.segmentMetadata
.getUUID());
// delete it.
vmd.resourceManager.deleteResource(result.segmentMetadata
.getUUID(), false/* isJournal */);
}
// throw wrapped set of exceptions.
throw new ExecutionExceptions(causes);
}
if (log.isInfoEnabled())
log.info("Generated " + splits.length
+ " index segments: name=" + vmd.name);
// form the split result.
final SplitResult result = new SplitResult(vmd.name,
vmd.indexMetadata, splits, buildResults);
return result;
}
/**
* Task used to build an {@link IndexSegment} from a restricted key-range of
* an index during a {@link SplitIndexPartitionTask}. This is a compacting
* merge since we want as much of the data for the index as possible in a
* single {@link IndexSegment}.
*
* @author Bryan Thompson
* @version $Id$
*/
static protected class BuildIndexSegmentSplitTask extends
AbstractResourceManagerTask {
private final ViewMetadata vmd;
private final Split split;
private final Event parentEvent;
/**
* Builds an {@link IndexSegment} from the lastCommitTime of the old
* journal.
*
* @param vmd
* @param split
*/
public BuildIndexSegmentSplitTask(final ViewMetadata vmd,
final Split split, final Event parentEvent) {
super(vmd.resourceManager, TimestampUtility
.asHistoricalRead(vmd.commitTime), vmd.name);
if (split == null)
throw new IllegalArgumentException();
this.vmd = vmd;
this.split = split;
this.parentEvent = parentEvent;
}
/**
* Note: The generated {@link IndexSegment} is on the retentionSet and
* MUST be removed from that set once it has been incorporated in a
* restart safe manner into an index partition view or once the task
* fails.
*
* @see StoreManager#retentionSetAdd(java.util.UUID)
*/
@Override
protected BuildResult doTask() throws Exception {
if (resourceManager.isOverflowAllowed())
throw new IllegalStateException();
final String name = getOnlyResource();
final ILocalBTreeView src = (ILocalBTreeView)getIndex(name);
if (log.isInfoEnabled()) {
// note: the mutable btree - accessed here for debugging only.
final BTree btree = src.getMutableBTree();
log.info("src=" + name + ",counter=" + src.getCounter().get()
+ ",checkpoint=" + btree.getCheckpoint());
}
final LocalPartitionMetadata pmd = (LocalPartitionMetadata) split.pmd;
final byte[] fromKey = pmd.getLeftSeparatorKey();
final byte[] toKey = pmd.getRightSeparatorKey();
if (fromKey == null && toKey == null) {
/*
* Note: This is not legal because it implies that we are
* building the index segment from the entire source key range -
* hence not a split at all!
*/
throw new RuntimeException("Not a key-range?");
}
/*
* Build the index segment from the key range.
*
* Note: The generated index segment is on the retentionSet and MUST
* be removed from that set once it has been incorporated in a
* restart safe manner into an index partition view or once the task
* fails.
*/
final BuildResult result = resourceManager.buildIndexSegment(name,
src, true/* compactingMerge */, vmd.commitTime,
fromKey, toKey, parentEvent);
return result;
}
}
/**
* Choose a set of splits which may be reasonably expected to divide the
* {@link IndexSegment} into extents each of which is approximately 50%
* full. The first split MUST use the leftSeparator of the index view as its
* leftSeparator. The last split MUST use the rightSeparator of the index
* view as its rightSeparator. The #of splits SHOULD be chosen such that the
* resulting index partitions are approximately 50% full.
*
* @param keyRange
* The left and right separator keys for the view.
* @param seg
* The {@link IndexSegment} containing most of the data for the
* view.
* @param nominalShardSize
* The nominal size of an index partition (typically 200MB).
* @param splitHandler
* Applies an application constraint to the choice of the
* separator key (optional).
*
* @return A {@link Split}[] array contains everything that we need to
* define the new index partitions -or- null
if a more
* detailed examination reveals that the index SHOULD NOT be split
* at this time. The returned array MUST containing at least two
* elements. If the {@link IndexSegment} CAN NOT be split, this MUST
* return null
rather than array with a single element.
*
* @see src/architecture/SplitMath.xls
*/
public static Split[] getSplits(
final IPartitionIdFactory partitionIdFactory,
final LocalPartitionMetadata oldpmd, final IndexSegment seg,
final long nominalShardSize,
final ISimpleSplitHandler splitHandler) {
if (partitionIdFactory == null)
throw new IllegalArgumentException();
if (oldpmd == null)
throw new IllegalArgumentException();
if (seg == null)
throw new IllegalArgumentException();
if (nominalShardSize <= 0)
throw new IllegalArgumentException();
if(!seg.getStore().getCheckpoint().compactingMerge) {
/*
* Note: You can only do this after a compacting merge since that is
* the only time we have perfect information about the size on disk
* of the shard's segments and a guarantee that there are no deleted
* tuples in the index segment. Both of those assumptions greatly
* simplify the logic here and the logic surrounding dynamic
* sharding in general.
*/
throw new IllegalArgumentException();
}
/*
* Compute the target #of splits based on the size on disk without
* regard to the #of tuples present in the index segment.
*
* @see src/architecture/SplitMath.xls for this formula.
*/
final int N1 = (int)(seg.getStore().size() / (nominalShardSize / 2.));
if (N1 < 2) {
/*
* There is not enough data on the disk to split this index segment.
*/
return null;
}
/*
* This is the actual number of tuples in the index segment.
*
* Note: These will all be non-deleted tuples since we verified that
* this is a compact segment above.
*/
final int entryCount = assertEntryCount(seg.getEntryCount());
/*
* This adjusts the #of splits if there are not enough tuples to
* generate that many splits.
*
* Note: This should only happen if you really, really dial down the
* [nominalShardSize]. That case is exercised by the test suite.
*/
final int N = (entryCount < N1) ? entryCount : N1;
final String scaleOutIndexName = seg.getIndexMetadata().getName();
if (log.isInfoEnabled()) {
log.info("segSize="
+ seg.getStore().size()
+ ", nominalShardSize="
+ nominalShardSize
+ ", N1="
+ N1
+ ", entryCount="
+ entryCount
+ ", N="
+ N
+ (N != N1 ? " [#splits adjusted down to the entryCount]."
: ""));
}
// The splits (may be fewer than N).
final List splits = new ArrayList(N);
// the index of the inclusive lower bound.
int low = 0;
// the index of the _inclusive_ upper bound.
final int high = entryCount - 1;
// the next key to use as the left separator key.
byte[] lastSeparatorKey = oldpmd.getLeftSeparatorKey();
// false until we use the rightSeparatorKey in the last split.
boolean didLastSplit = false;
/*
* do until done.
*
* @see src/architecture/SplitMath.xls for the math in this loop.
*/
while (low < high) {
// The #of tuples in [low:high].
final int rangeCount = high - low + 1;
// The #of splits already decided.
final int splitCount = splits.size();
// The #of splits that we will still create.
final int remainingSplits = N - splitCount;
// inclusive lower bound of the split.
final int fromIndex = low;
final byte[] fromKey = lastSeparatorKey;
// exclusive upper bound of the split.
final int toIndex;
final byte[] toKey;
/*
* Figure out the last tuple to copy into this split and the
* separatorKey which is the exclusive upper bound for this split.
* Note that the separatorKey does not need to correspond to the
* last tuple copied into the split. It can be a successor of that
* tuple in the unsigned byte[] ordering which is less than the next
* tuple actually present in the index.
*/
if (remainingSplits == 1) {
/*
* This is the last split: always use the rightSeparator.
*/
toKey = oldpmd.getRightSeparatorKey();
toIndex = high;
didLastSplit = true;
} else {
// The index of recommended separatorKey.
final int splitAt = (rangeCount / remainingSplits) + low;
assert splitAt > low && splitAt <= high : "low=" + low
+ ", high=" + high + ", splitAt=" + splitAt;
;
if (splitHandler != null) {
/*
* Allow override of the recommended separator.
*
* Note: we receive a separatorKey from the override so we
* can create a separation not represented by any key
* actually present in the index. To handle this case, we
* lookup the desired separatorKey in the index. If it is
* not found then we convert the insertion point to the
* index of the key where it would be inserted and use that
* as the toIndex.
*/
// Allow override of the separatorKey.
final byte[] chosenKey = splitHandler.getSeparatorKey(seg,
fromIndex, (high + 1)/* toIndex */, splitAt);
if (chosenKey == null) {
/*
* No separator key could be identified. The rest of the
* data in the segment will all go into this split.
*/
final double overextension = ((double) seg.getStore()
.size())
/ nominalShardSize;
if (splits.isEmpty() && overextension > 2d) {
/*
* Log, but keep going.
*
* Note: An application with poorly written override
* logic could cause an index segment to fail to
* split. This has an extreme negative impact on
* performance once the segment grows to 1G or more.
* The DataService SHOULD refuse writes for shards
* which refuse splits, which pushes the problem
* back to the application where it belongs.
*/
log.error("Segment overextended: "
+ overextension
+ "x : application refuses to split shard: "
+ scaleOutIndexName + "#"
+ oldpmd.getPartitionId());
}
if(splits.isEmpty()) {
/*
* Since no splits have been chosen we can not
* return everything in a single split. Instead we
* return null and the source WILL NOT be split.
*/
return null;
}
/*
* Everything remaining goes into this split.
*/
toKey = oldpmd.getRightSeparatorKey();
toIndex = high;
didLastSplit = true;
} else {
// Lookup key in the index.
final int pos = assertEntryCount(seg.indexOf(chosenKey));
// If key not found, convert pos to the insertion point.
toIndex = pos < 0 ? -(pos + 1) : pos;
if (toIndex < low || toIndex > high) {
/*
* The override did not return a separator key
* within the key range which we instructed it to
* use. This is a bug in the application's override
* code.
*/
throw new RuntimeException(
"bad split override: name="
+ scaleOutIndexName
+ ", fromIndex=" + fromIndex
+ ", toIndex=" + (high + 1)
+ ", recommendedSplitAt=" + splitAt
+ ", but split choose at "
+ toIndex);
}
if (toIndex == high) {
/*
* If they choose the last allowed index then all
* tuples have been consumed and we are done. In
* this case we use the rightSeparator rather than
* the chosen key in order to enforce the constraint
* that the last split has the same rightSeparator
* as the source index segment.
*/
toKey = oldpmd.getRightSeparatorKey();
didLastSplit = true;
} else {
// We will use the chosen key.
toKey = chosenKey;
}
}
} else {
if (splitAt == high) {
/*
* Enforce the constraint that the last split has the
* same rightSeparator as the source index segment.
*/
toKey = oldpmd.getRightSeparatorKey();
didLastSplit = true;
} else {
/*
* Take whatever key is at the recommended split index and
* use that as the separator key for this split.
*/
toKey = seg.keyAt(splitAt);
}
toIndex = splitAt;
}
}
if (log.isDebugEnabled()) {
log.debug("splitCount=" + splitCount + ", remainingSplits="
+ remainingSplits + ", low=" + low + ", high=" + high
+ ", rangeCount=" + rangeCount + ", fromIndex="
+ fromIndex + ", toIndex=" + toIndex + ", ntuples="
+ (toIndex - fromIndex) + ", separatorKey="
+ BytesUtil.toString(toKey));
}
// Create Split description.
{
/*
* Get the next partition identifier for the named scale-out
* index.
*
* Note: This is a RMI.
*/
final int partitionId = partitionIdFactory
.nextPartitionId(scaleOutIndexName);
/*
* Describe the partition metadata for the new split.
*/
final LocalPartitionMetadata newpmd = new LocalPartitionMetadata(
partitionId,//
-1, // Note: split not allowed during move.
fromKey, // leftSeparatorKey
toKey, // rightSeparatorKey
/*
* Note: no resources for an index segment.
*/
null,//
/*
* Note: cause will be set by the atomic update task.
*/
null //
// , oldpmd.getHistory()
// + "chooseSplitPoint(oldPartitionId="
// + oldpmd.getPartitionId() + ",nsplits=" + N
// + ",newPartitionId=" + partitionId + ") "
);
final Split split = new Split(newpmd, fromIndex, toIndex);
// add to list of splits.
splits.add(split);
}
// prepare for the next round.
low = toIndex;
lastSeparatorKey = toKey;
}
if(!didLastSplit) {
/*
* This catches logic errors where we have failed to assign the
* rightSeparatorKey explicitly to the last split.
*/
throw new AssertionError();
}
return splits.toArray(new Split[splits.size()]);
}
}