// 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.idmanagement;
import com.carrotsearch.hppc.LongHashSet;
import com.carrotsearch.hppc.LongSet;
import org.janusgraph.core.JanusGraphFactory;
import org.janusgraph.core.JanusGraph;
import org.janusgraph.core.JanusGraphVertex;
import org.janusgraph.diskstorage.configuration.ModifiableConfiguration;
import org.janusgraph.diskstorage.keycolumnvalue.StandardStoreFeatures;
import org.janusgraph.diskstorage.keycolumnvalue.StoreFeatures;
import org.janusgraph.diskstorage.keycolumnvalue.inmemory.InMemoryStoreManager;
import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration;
import org.janusgraph.graphdb.database.idassigner.IDPoolExhaustedException;
import org.janusgraph.graphdb.database.idassigner.VertexIDAssigner;
import org.janusgraph.graphdb.internal.InternalRelation;
import org.janusgraph.graphdb.internal.InternalVertex;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author Matthias Broecheler (me@matthiasb.com)
*/
@RunWith(Parameterized.class)
public class VertexIDAssignerTest {
final VertexIDAssigner idAssigner;
@Parameterized.Parameters
public static Collection<Object[]> configs() {
List<Object[]> configurations = new ArrayList<Object[]>();
for (int maxPerPartition : new int[]{Integer.MAX_VALUE, 100, 300}) {
for (int numPartitions : new int[]{2, 4, 10}) {
for (int[] local : new int[][]{null, {0,2, numPartitions}, {235,234,8}, {1,1,2}, {0,1<<(numPartitions-1),numPartitions}}) {
configurations.add(new Object[]{numPartitions, maxPerPartition, local});
}
}
}
return configurations;
}
private final long maxIDAssignments;
/**
*
* @param numPartitionsBits The number of partitions bits to use. This means there are exactly (1<<numPartitionBits) partitions.
* @param partitionMax The maxium number of ids that can be allocated per partition. This is artifically constraint by the MockIDAuthority
* @param localPartitionDef This array contains three integers: 1+2) lower and upper bounds for the local partition range, and
* 3) the bit width of the local bounds. The bounds will be bitshifted forward to consume the bit width
*/
public VertexIDAssignerTest(int numPartitionsBits, int partitionMax, int[] localPartitionDef) {
MockIDAuthority idAuthority = new MockIDAuthority(11, partitionMax);
StandardStoreFeatures.Builder fb = new StandardStoreFeatures.Builder();
if (null != localPartitionDef) {
fb.localKeyPartition(true);
idAuthority.setLocalPartition(PartitionIDRangeTest.convert(localPartitionDef[0],localPartitionDef[1],localPartitionDef[2]));
}
StoreFeatures features = fb.build();
ModifiableConfiguration config = GraphDatabaseConfiguration.buildGraphConfiguration();
config.set(GraphDatabaseConfiguration.CLUSTER_MAX_PARTITIONS,1<<numPartitionsBits);
idAssigner = new VertexIDAssigner(config, idAuthority, features);
System.out.println(String.format("Configuration [%s|%s|%s]",numPartitionsBits,partitionMax,Arrays.toString(localPartitionDef)));
if (localPartitionDef!=null && localPartitionDef[0]<localPartitionDef[1] && localPartitionDef[2]<=numPartitionsBits) {
this.maxIDAssignments = ((localPartitionDef[1]-localPartitionDef[0])<<(numPartitionsBits-localPartitionDef[2]))*((long)partitionMax);
} else {
this.maxIDAssignments = (1<<numPartitionsBits)*((long)partitionMax);
}
}
private static JanusGraph getInMemoryGraph() {
ModifiableConfiguration config = GraphDatabaseConfiguration.buildGraphConfiguration();
config.set(GraphDatabaseConfiguration.STORAGE_BACKEND, InMemoryStoreManager.class.getCanonicalName());
config.set(GraphDatabaseConfiguration.IDS_FLUSH, false);
config.set(GraphDatabaseConfiguration.IDAUTHORITY_WAIT, Duration.ofMillis(1L));
return JanusGraphFactory.open(config);
}
@Test
public void testIDAssignment() {
LongSet vertexIds = new LongHashSet();
LongSet relationIds = new LongHashSet();
int totalRelations = 0;
int totalVertices = 0;
for (int trial = 0; trial < 10; trial++) {
for (boolean flush : new boolean[]{true, false}) {
JanusGraph graph = getInMemoryGraph();
int numVertices = 1000;
List<JanusGraphVertex> vertices = new ArrayList<JanusGraphVertex>(numVertices);
List<InternalRelation> relations = new ArrayList<InternalRelation>();
JanusGraphVertex old = null;
totalRelations+=2*numVertices;
totalVertices+=numVertices;
try {
for (int i = 0; i < numVertices; i++) {
JanusGraphVertex next = graph.addVertex();
InternalRelation edge = null;
if (old != null) {
edge = (InternalRelation) old.addEdge("knows", next);
}
InternalRelation property = (InternalRelation) next.property("age", 25);
if (flush) {
idAssigner.assignID((InternalVertex) next, next.vertexLabel());
idAssigner.assignID(property);
if (edge != null) idAssigner.assignID(edge);
}
relations.add(property);
if (edge != null) relations.add(edge);
vertices.add(next);
old = next;
}
if (!flush) idAssigner.assignIDs(relations);
//Check if we should have exhausted the id pools
if (totalRelations>maxIDAssignments || totalVertices>maxIDAssignments) fail();
//Verify that ids are set and unique
for (JanusGraphVertex v : vertices) {
assertTrue(v.hasId());
long id = v.longId();
assertTrue(id>0 && id<Long.MAX_VALUE);
assertTrue(vertexIds.add(id));
}
for (InternalRelation r : relations) {
assertTrue(r.hasId());
long id = r.longId();
assertTrue(id>0 && id<Long.MAX_VALUE);
assertTrue(relationIds.add(id));
}
} catch (IDPoolExhaustedException e) {
//Since the id assignment process is randomized, we divide by 3/2 to account for minor variations
assertTrue("Max Avail: " + maxIDAssignments + " vs. ["+totalVertices+","+totalRelations+"]",
totalRelations>=maxIDAssignments/3*2 || totalVertices>=maxIDAssignments/3*2);
} finally {
graph.tx().rollback();
graph.close();
}
}
}
}
}