// Copyright 2017 JanusGraph Authors // // Licensed 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.janusgraph.graphdb.database.idassigner.placement; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.janusgraph.diskstorage.StaticBuffer; import org.janusgraph.diskstorage.keycolumnvalue.KeyRange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Random; /** * An instance of this class describes a range of partition ids. This range if defined by a lower id (inclusive) * and upper id (exclusive). When lowerId < upperId this partition range is called a proper range since it describes the * contiguous block of ids from lowerId until upperId. When lowerId >= upperID then the partition block "wraps around" the * specified idUpperBound. In other words, it describes the ids from [lowerId,idUpperBound) AND [0,upperId). * * It is always true that lowerID and upperID are smaller or equal than idUpperBound. * * @author Matthias Broecheler (me@matthiasb.com) */ public class PartitionIDRange { private static final Logger log = LoggerFactory.getLogger(PartitionIDRange.class); private static final Random random = new Random(); private final int lowerID; private final int upperID; private final int idUpperBound; public PartitionIDRange(int lowerID, int upperID, int idUpperBound) { Preconditions.checkArgument(idUpperBound>0, "Partition limit " + idUpperBound + " must be positive"); Preconditions.checkArgument(idUpperBound<=Integer.MAX_VALUE, "Partition limit cannot exceed representable range of an integer"); Preconditions.checkArgument(lowerID>=0, "Negative partition lower bound " + lowerID); Preconditions.checkArgument(lowerID< idUpperBound, "Partition lower bound " + lowerID + " exceeds limit " + idUpperBound); Preconditions.checkArgument(upperID>=0, "Negative partition upper bound " + upperID); Preconditions.checkArgument(upperID<=idUpperBound, "Partition upper bound " + upperID + " exceeds limit " + idUpperBound); this.lowerID = lowerID; this.upperID = upperID; this.idUpperBound = idUpperBound; } public int getLowerID() { return lowerID; } public int getUpperID() { return upperID; } public int getIdUpperBound() { return idUpperBound; } public int[] getAllContainedIDs() { int[] result; if (lowerID < upperID) { //"Proper" id range result = new int[upperID-lowerID]; int pos=0; for (int id=lowerID;id<upperID;id++) { result[pos++]=id; } } else { //Id range "wraps around" result = new int[(idUpperBound-lowerID)+(upperID)]; int pos=0; for (int id=0;id<upperID;id++) { result[pos++]=id; } for (int id=lowerID;id<idUpperBound;id++) { result[pos++]=id; } } return result; } /** * Returns true of the given partitionId lies within this partition id range, else false. * * @param partitionId * @return */ public boolean contains(int partitionId) { if (lowerID < upperID) { //"Proper" id range return lowerID <= partitionId && upperID > partitionId; } else { //Id range "wraps around" return (lowerID <= partitionId && partitionId < idUpperBound) || (upperID > partitionId && partitionId >= 0); } } @Override public String toString() { return "["+lowerID+","+upperID+")%"+idUpperBound; } /** * Returns a random partition id that lies within this partition id range. * * @return */ public int getRandomID() { //Compute the width of the partition... int partitionWidth; if (lowerID < upperID) partitionWidth = upperID - lowerID; //... for "proper" ranges else partitionWidth = (idUpperBound - lowerID) + upperID; //... and those that "wrap around" Preconditions.checkArgument(partitionWidth > 0, partitionWidth); return (random.nextInt(partitionWidth) + lowerID) % idUpperBound; } /* =========== Helper methods to generate PartitionIDRanges ============ */ public static List<PartitionIDRange> getGlobalRange(final int partitionBits) { Preconditions.checkArgument(partitionBits>=0 && partitionBits<(Integer.SIZE-1),"Invalid partition bits: %s",partitionBits); final int partitionIdBound = (1 << (partitionBits)); return ImmutableList.of(new PartitionIDRange(0, partitionIdBound, partitionIdBound)); } public static List<PartitionIDRange> getIDRanges(final int partitionBits, final List<KeyRange> locals) { Preconditions.checkArgument(partitionBits>0 && partitionBits<(Integer.SIZE-1)); Preconditions.checkArgument(locals!=null && !locals.isEmpty(),"KeyRanges are empty"); final int partitionIdBound = (1 << (partitionBits)); final int backShift = Integer.SIZE-partitionBits; List<PartitionIDRange> partitionRanges = Lists.newArrayList(); for (KeyRange local : locals) { Preconditions.checkArgument(local.getStart().length() >= 4); Preconditions.checkArgument(local.getEnd().length() >= 4); if (local.getStart().equals(local.getEnd())) { //Start=End => Partition spans entire range partitionRanges.add(new PartitionIDRange(0, partitionIdBound, partitionIdBound)); continue; } int startInt = local.getStart().getInt(0); int lowerID = startInt >>> backShift; assert lowerID>=0 && lowerID<partitionIdBound; //Lower id must be inclusive, so check that we did not truncate anything! boolean truncatedBits = (lowerID<<backShift)!=startInt; StaticBuffer start = local.getAt(0); for (int i=4;i<start.length() && !truncatedBits;i++) { if (start.getByte(i)!=0) truncatedBits=true; } if (truncatedBits) lowerID+=1; //adjust to make sure we are inclusive int upperID = local.getEnd().getInt(0) >>> backShift; //upper id is exclusive //Check that we haven't jumped order indicating that the interval was too small if ((local.getStart().compareTo(local.getEnd())<0 && lowerID>=upperID)) { discardRange(local); continue; } lowerID = lowerID%partitionIdBound; //ensure that lowerID remains within range if (lowerID==upperID) { //After re-normalizing, check for interval colision discardRange(local); continue; } partitionRanges.add(new PartitionIDRange(lowerID, upperID, partitionIdBound)); } return partitionRanges; } private static void discardRange(KeyRange local) { log.warn("Individual key range is too small for partition block - result would be empty; hence ignored: {}",local); } }