Skip to content

Commit

Permalink
Enhance profiler results
Browse files Browse the repository at this point in the history
1. Add toString() method to SliceQuery to indicate which vertex-centric
index is used in edge queries.
2. Indicate "GraphCentricQuery" and "VertexCentricQuery" in profiler if applicable.
Previously they were incorrectly merged, causing corrupted annotations.
3. Indicate "constructGraphCentricQuery" phase in profiler rather than a vague
"optimization" annotation, if applicable.
4. Fix corrupted annotations caused by nested profile flattening issue. Now
AND/OR nesting is flattened only if it does not have siblings.

Fixes JanusGraph#898, JanusGraph#2283, JanusGraph#2285

Signed-off-by: Boxuan Li <liboxuan@connect.hku.hk>
  • Loading branch information
li-boxuan committed Jan 9, 2021
1 parent fb38d44 commit 4ba3352
Show file tree
Hide file tree
Showing 19 changed files with 399 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics;
import org.apache.tinkerpop.gremlin.structure.T;
import org.janusgraph.TestCategory;
Expand Down Expand Up @@ -989,6 +990,69 @@ public void testConditionalIndexing() {
numV / strings.length, new boolean[]{false, false}, weight, Order.ASC);
}

@Test
public void testGraphCentricQueryProfiling() {
final PropertyKey name = makeKey("name", String.class);
final PropertyKey prop = makeKey("prop", Integer.class);
mgmt.buildIndex("mixed", Vertex.class).addKey(name, Mapping.STRING.asParameter()).addKey(prop).buildMixedIndex(INDEX);
finishSchema();

tx.addVertex("name", "bob", "prop", 100);
tx.commit();

// satisfied by a single graph-centric query which is satisfied by a single mixed index query
if (indexFeatures.supportNotQueryNormalForm()) {
newTx();
Metrics mMixedOr = tx.traversal().V().or(__.has("name", "bob"), __.has("prop", 100))
.profile().next().getMetrics(0);
assertEquals("Or(JanusGraphStep([],[name.eq(bob)]),JanusGraphStep([],[prop.eq(100)]))", mMixedOr.getName());
assertEquals(2, mMixedOr.getNested().size());
Metrics nested = (Metrics) mMixedOr.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mMixedOr.getNested().toArray()[1];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
Map<String, String> annotations = new HashMap() {{
put("condition", "((name = bob) OR (prop = 100))");
put("orders", "[]");
put("isFitted", "false");
put("isOrdered", "true");
put("query", "[((name = bob) OR (prop = 100))](2000):mixed");
put("index", "mixed");
put("index_impl", "search");
}};
assertEquals(annotations, nested.getAnnotations());
}

// satisfied by a single graph-centric query which is satisfied by a single mixed index query
newTx();
Metrics mMixedAnd = tx.traversal().V().has("name", "bob").has("prop", 100)
.profile().next().getMetrics(0);
assertEquals("JanusGraphStep([],[name.eq(bob), prop.eq(100)])", mMixedAnd.getName());
assertEquals(3, mMixedAnd.getNested().size());
Metrics nested = (Metrics) mMixedAnd.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mMixedAnd.getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mMixedAnd.getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
String query;
if (readConfig.get(INDEX_BACKEND, INDEX).equals("solr")) {
query = "[(name_s = bob AND prop_i = 100)](1000):mixed";
} else {
query = "[(name = bob AND prop = 100)](1000):mixed";
}
Map<String, String> annotations = new HashMap() {{
put("condition", "(name = bob AND prop = 100)");
put("orders", "[]");
put("isFitted", "true");
put("isOrdered", "true");
put("query", query);
put("index", "mixed");
put("index_impl", "search");
}};
assertEquals(annotations, nested.getAnnotations());
}

@Test
public void testIndexSelectStrategy() {
final PropertyKey name = makeKey("name", String.class);
Expand All @@ -1002,33 +1066,52 @@ public void testIndexSelectStrategy() {
mixedIndex.name();
finishSchema();

final String backend = readConfig.get(INDEX_BACKEND, INDEX);
String nameKey;
String propKey;
if (backend.equals("solr")) {
nameKey = "name_s";
propKey = "prop_s";
} else {
nameKey = "name";
propKey = "prop";
}

// best combination is to pick up only 1 index (mixed index), however, greedy based approximate algorithm
// picks up 2 indexes (composite index + mixed index)
final String oneIndexResult = String.format("[(%s = value AND %s = value)](1000):mixed", nameKey, propKey);
final String twoIndexesResult = String.format("[composite:multiKSQ[1]@2147483647, mixed:[(%s = value AND %s = value)]:mixed]", nameKey, propKey);

// use default config
assertEquals(1, getIndexSelectResultNum());
assertEquals(oneIndexResult, getIndexSelectResult());

// use full class name
assertEquals(1, getIndexSelectResultNum(option(INDEX_SELECT_STRATEGY),
assertEquals(oneIndexResult, getIndexSelectResult(option(INDEX_SELECT_STRATEGY),
ThresholdBasedIndexSelectionStrategy.class.getName()));

assertEquals(1, getIndexSelectResultNum(option(INDEX_SELECT_BRUTE_FORCE_THRESHOLD), 10,
assertEquals(oneIndexResult, getIndexSelectResult(option(INDEX_SELECT_BRUTE_FORCE_THRESHOLD), 10,
option(INDEX_SELECT_STRATEGY), ThresholdBasedIndexSelectionStrategy.NAME));

assertEquals(2, getIndexSelectResultNum(option(INDEX_SELECT_BRUTE_FORCE_THRESHOLD), 0,
assertEquals(twoIndexesResult, getIndexSelectResult(option(INDEX_SELECT_BRUTE_FORCE_THRESHOLD), 0,
option(INDEX_SELECT_STRATEGY), ThresholdBasedIndexSelectionStrategy.NAME));

assertEquals(1, getIndexSelectResultNum(option(INDEX_SELECT_STRATEGY), BruteForceIndexSelectionStrategy.NAME));
assertEquals(oneIndexResult, getIndexSelectResult(option(INDEX_SELECT_STRATEGY), BruteForceIndexSelectionStrategy.NAME));

assertEquals(2, getIndexSelectResultNum(option(INDEX_SELECT_STRATEGY), ApproximateIndexSelectionStrategy.NAME));
assertEquals(twoIndexesResult, getIndexSelectResult(option(INDEX_SELECT_STRATEGY), ApproximateIndexSelectionStrategy.NAME));
}

private long getIndexSelectResultNum(Object... settings) {
private String getIndexSelectResult(Object... settings) {
clopen(settings);
GraphTraversalSource g = graph.traversal();
TraversalMetrics profile = g.V().has("name", "value")
.has("prop", "value").profile().next();
return profile.getMetrics().stream().findFirst().get().getNested().stream().filter(m -> m.getName().equals(QueryProfiler.BACKEND_QUERY)).count();
Metrics nested = (Metrics) profile.getMetrics(0).getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) profile.getMetrics(0).getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) profile.getMetrics(0).getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
return (String) nested.getAnnotation(QueryProfiler.QUERY_ANNOTATION);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -5685,6 +5697,183 @@ public void testNotHas() {
assertTrue(tx.traversal().V().not(__.has("p3")).hasNext());
}

@Test
public void testGraphCentricQueryProfiling() {
final PropertyKey name = makeKey("name", String.class);
final PropertyKey weight = makeKey("weight", Integer.class);
final JanusGraphIndex compositeNameIndex = mgmt.buildIndex("nameIdx", Vertex.class).addKey(name).buildCompositeIndex();
final JanusGraphIndex compositeWeightIndex = mgmt.buildIndex("weightIdx", Vertex.class).addKey(weight).buildCompositeIndex();
final PropertyKey prop = makeKey("prop", Integer.class);
finishSchema();

JanusGraphVertex v = tx.addVertex("name", "bob", "prop", 100, "weight", 100);
tx.commit();

// satisfied by a single composite index query
newTx();
Metrics mCompSingle = tx.traversal().V().has("name", "bob").profile().next().getMetrics(0);
assertEquals("JanusGraphStep([],[name.eq(bob)])", mCompSingle.getName());
assertEquals(3, mCompSingle.getNested().size());
Metrics nested = (Metrics) mCompSingle.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompSingle.getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompSingle.getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
Map<String, String> nameIdxAnnotations = new HashMap() {{
put("condition", "(name = bob)");
put("orders", "[]");
put("isFitted", "true");
put("isOrdered", "true");
put("query", "multiKSQ[1]@1000");
put("index", "nameIdx");
}};
assertEquals(nameIdxAnnotations, nested.getAnnotations());

// satisfied by unions of two separate graph-centric queries, each satisfied by a single composite index query
newTx();
Metrics mCompMultiOr = tx.traversal().V().or(__.has("name", "bob"), __.has("weight", 100))
.profile().next().getMetrics(0);
assertEquals("Or(JanusGraphStep([],[name.eq(bob)]),JanusGraphStep([],[weight.eq(100)]))", mCompMultiOr.getName());
assertEquals(5, mCompMultiOr.getNested().size());
nested = (Metrics) mCompMultiOr.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompMultiOr.getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompMultiOr.getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
assertEquals(nameIdxAnnotations, nested.getAnnotations());
nested = (Metrics) mCompMultiOr.getNested().toArray()[3];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompMultiOr.getNested().toArray()[4];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
Map<String, String> weightIdxAnnotations = new HashMap() {{
put("condition", "(weight = 100)");
put("orders", "[]");
put("isFitted", "true");
put("isOrdered", "true");
put("query", "multiKSQ[1]@1000");
put("index", "weightIdx");
}};
assertEquals(weightIdxAnnotations, nested.getAnnotations());

// satisfied by a single graph-centric query which satisfied by intersection of two composite index queries
newTx();
Metrics mCompMultiAnd = tx.traversal().V().and(__.has("name", "bob"), __.has("weight", 100))
.profile().next().getMetrics(0);
assertEquals("JanusGraphStep([],[name.eq(bob), weight.eq(100)])", mCompMultiAnd.getName());
assertEquals(3, mCompMultiAnd.getNested().size());
nested = (Metrics) mCompMultiAnd.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompMultiAnd.getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mCompMultiAnd.getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
assertEquals("(name = bob AND weight = 100)", nested.getAnnotation("condition"));
// this graph-centric query contains more than one backend query which are nested under this level
assertEquals(2, nested.getNested().size());
Metrics deeplyNested = (Metrics) nested.getNested().toArray()[0];
assertEquals("AND-query", deeplyNested.getName());
assertEquals("multiKSQ[1]@2147483647", deeplyNested.getAnnotation("query"));
deeplyNested = (Metrics) nested.getNested().toArray()[1];
assertEquals("AND-query", deeplyNested.getName());
assertEquals("multiKSQ[1]@2147483647", deeplyNested.getAnnotation("query"));

// satisfied by one graph-centric query, which satisfied by in-memory filtering after one composite index query
newTx();
Metrics mUnfittedMultiAnd = tx.traversal().V().and(__.has("name", "bob"), __.has("prop", 100))
.profile().next().getMetrics(0);
assertEquals("JanusGraphStep([],[name.eq(bob), prop.eq(100)])", mUnfittedMultiAnd.getName());
assertEquals(3, mUnfittedMultiAnd.getNested().size());
nested = (Metrics) mUnfittedMultiAnd.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mUnfittedMultiAnd.getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mUnfittedMultiAnd.getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
Map<String, String> annotations = new HashMap() {{
put("condition", "(name = bob AND prop = 100)");
put("orders", "[]");
put("isFitted", "false"); // not fitted because prop = 100 requires in-memory filtering
put("isOrdered", "true");
put("query", "multiKSQ[1]@2000");
put("index", "nameIdx");
}};
assertEquals(annotations, nested.getAnnotations());

// satisfied by union of two separate graph-centric queries, one satisfied by a composite index query and the other requires full scan
newTx();
Metrics mUnfittedMultiOr = tx.traversal().V().or(__.has("name", "bob"), __.has("prop", 100))
.profile().next().getMetrics(0);
assertEquals("Or(JanusGraphStep([],[name.eq(bob)]),JanusGraphStep([],[prop.eq(100)]))", mUnfittedMultiOr.getName());
assertEquals(5, mUnfittedMultiOr.getNested().size());
nested = (Metrics) mUnfittedMultiOr.getNested().toArray()[0];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mUnfittedMultiOr.getNested().toArray()[1];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mUnfittedMultiOr.getNested().toArray()[2];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
assertEquals(nameIdxAnnotations, nested.getAnnotations());
nested = (Metrics) mUnfittedMultiOr.getNested().toArray()[3];
assertEquals(QueryProfiler.CONSTRUCT_GRAPH_CENTRIC_QUERY, nested.getName());
nested = (Metrics) mUnfittedMultiOr.getNested().toArray()[4];
assertEquals(QueryProfiler.GRAPH_CENTRIC_QUERY, nested.getName());
Map<String, String> fullScanAnnotations = new HashMap() {{
put("condition", "(prop = 100)");
put("orders", "[]");
put("isFitted", "false");
put("isOrdered", "true");
put("query", "[]");
}};
assertEquals(fullScanAnnotations, nested.getAnnotations());
}

@Test
public void testVertexCentricQueryProfiling() {
final PropertyKey time = mgmt.makePropertyKey("time").dataType(Integer.class).cardinality(Cardinality.SINGLE).make();
final EdgeLabel friend = mgmt.makeEdgeLabel("friend").multiplicity(Multiplicity.SIMPLE).make();
mgmt.buildEdgeIndex(friend, "byTime", OUT, desc, time);
finishSchema();

JanusGraphVertex v = tx.addVertex();
JanusGraphVertex o = tx.addVertex();
v.addEdge("friend", o, "time", 100);
v.addEdge("friend-no-index", o, "time", 100);
tx.commit();

// satisfied by a single vertex-centric query which is satisfied by union of two edge queries
newTx();
Metrics mMultiLabels = tx.traversal().V(v).outE("friend", "friend-no-index").has("time", 100)
.profile().next().getMetrics(1);
assertEquals("JanusGraphVertexStep([time.eq(100)])", mMultiLabels.getName());
assertEquals(1, mMultiLabels.getNested().size());
Metrics nested = (Metrics) mMultiLabels.getNested().toArray()[0];
assertEquals(QueryProfiler.VERTEX_CENTRIC_QUERY, nested.getName());
Map<String, String> annotations = new HashMap() {{
put("condition", "(time = 100 AND (type[friend] OR type[friend-no-index]))");
put("orders", "[]");
put("vertices", 1);
}};
assertEquals(annotations, nested.getAnnotations());
assertEquals(3, nested.getNested().size());
Metrics friendMetrics = (Metrics) nested.getNested().toArray()[1];
assertEquals("OR-query", friendMetrics.getName());
annotations = new HashMap() {{
put("isFitted", "true");
put("isOrdered", "true");
put("query", "2069:byTime:SliceQuery[0xB0E0FF7FFFFF9B,0xB0E0FF7FFFFF9C)@2147483647"); // vertex-centric index utilized
}};
assertEquals(annotations, friendMetrics.getAnnotations());
Metrics friendNoIndexMetrics = (Metrics) nested.getNested().toArray()[2];
assertEquals("OR-query", friendNoIndexMetrics.getName());
annotations = new HashMap() {{
put("isFitted", "false");
put("isOrdered", "true");
put("query", "friend-no-index:SliceQuery[0x7180,0x7181)@2147483647"); // no vertex-centric index found
}};
assertEquals(annotations, friendNoIndexMetrics.getAnnotations());
}

@Test
public void testVertexCentricIndexWithNull() {
EdgeLabel bought = makeLabel("bought");
Expand Down
Loading

0 comments on commit 4ba3352

Please sign in to comment.