// 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.Lists; import org.janusgraph.diskstorage.configuration.ConfigOption; import org.janusgraph.diskstorage.configuration.Configuration; import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; import org.janusgraph.graphdb.configuration.PreInitializeConfigOptions; import org.janusgraph.graphdb.database.idassigner.IDPoolExhaustedException; import org.janusgraph.graphdb.idmanagement.IDManager; import org.janusgraph.graphdb.internal.InternalElement; import org.janusgraph.graphdb.internal.InternalVertex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * A id placement strategy that assigns all vertices created in a transaction * to the same partition id. The partition id is selected randomly from a set * of partition ids that are retrieved upon initialization. * * The number of partition ids to choose from is configurable. * * @author Matthias Broecheler (me@matthiasb.com) */ @PreInitializeConfigOptions public class SimpleBulkPlacementStrategy implements IDPlacementStrategy { private static final Logger log = LoggerFactory.getLogger(SimpleBulkPlacementStrategy.class); public static final ConfigOption<Integer> CONCURRENT_PARTITIONS = new ConfigOption<Integer>(GraphDatabaseConfiguration.IDS_NS, "num-partitions","Number of partition block to allocate for placement of vertices", ConfigOption.Type.MASKABLE,10); public static final int PARTITION_FINDING_ATTEMPTS = 1000; private final Random random = new Random(); private final int[] currentPartitions; private List<PartitionIDRange> localPartitionIdRanges; private final Set<Integer> exhaustedPartitions; public SimpleBulkPlacementStrategy(int concurrentPartitions) { Preconditions.checkArgument(concurrentPartitions > 0); currentPartitions = new int[concurrentPartitions]; exhaustedPartitions = Collections.newSetFromMap(new ConcurrentHashMap<Integer,Boolean>()); } public SimpleBulkPlacementStrategy(Configuration config) { this(config.get(CONCURRENT_PARTITIONS)); } private final int nextPartitionID() { return currentPartitions[random.nextInt(currentPartitions.length)]; } private final void updateElement(int index) { Preconditions.checkArgument(localPartitionIdRanges!=null && !localPartitionIdRanges.isEmpty(),"Local partition id ranges have not been initialized"); int newPartition; int attempts = 0; do { attempts++; newPartition = localPartitionIdRanges.get(random.nextInt(localPartitionIdRanges.size())).getRandomID(); if (attempts>PARTITION_FINDING_ATTEMPTS) throw new IDPoolExhaustedException("Could not find non-exhausted partition"); } while (exhaustedPartitions.contains(newPartition)); currentPartitions[index] = newPartition; log.debug("Setting partition at index [{}] to: {}",index,newPartition); } @Override public void injectIDManager(IDManager idManager) {} //We don't need the IDManager here @Override public int getPartition(InternalElement element) { return nextPartitionID(); } @Override public void getPartitions(Map<InternalVertex, PartitionAssignment> vertices) { int partitionID = nextPartitionID(); for (Map.Entry<InternalVertex, PartitionAssignment> entry : vertices.entrySet()) { entry.setValue(new SimplePartitionAssignment(partitionID)); } } @Override public boolean supportsBulkPlacement() { return true; } @Override public void setLocalPartitionBounds(List<PartitionIDRange> localPartitionIdRanges) { Preconditions.checkArgument(localPartitionIdRanges!=null && !localPartitionIdRanges.isEmpty()); this.localPartitionIdRanges = Lists.newArrayList(localPartitionIdRanges); //copy for (int i = 0; i < currentPartitions.length; i++) { updateElement(i); } } public boolean isExhaustedPartition(int partitionID) { return exhaustedPartitions.contains(partitionID); } @Override public void exhaustedPartition(int partitionID) { exhaustedPartitions.add(partitionID); for (int i = 0; i < currentPartitions.length; i++) { if (currentPartitions[i] == partitionID) { updateElement(i); } } } }