Skip to content

Commit 566ff5f

Browse files
committed
Add limited-traversal query on a binary tree with one supernode
1 parent 04c3084 commit 566ff5f

File tree

2 files changed

+103
-52
lines changed

2 files changed

+103
-52
lines changed
Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,3 @@
1-
// This script can create a binary tree in ArangoDB with relatively large
2-
// vertex documents (approx. 1 MB each). You can give the depth and you can
3-
// choose what type of graph to create.
4-
//
5-
// Usage:
6-
//
7-
// makeGraph("G", "V", "E") - creates a general graph with name G, vertex
8-
// collection V and edge collection E
9-
// makeTree(6, "V", "E") - creates the actual tree after the graph was created
10-
// 6 is the depth and one has to name the vertex and
11-
// edge collections
12-
//
13-
// The following AQL query and its performance could be of interest:
14-
//
15-
// FOR v IN 0..6 OUTBOUND "V/S1:K1" GRAPH "G"
16-
// RETURN v.data
17-
//
18-
// This traverses the whole graph starting from the root but retrieves only
19-
// a tiny part of the vertex data. This tests the 3.10 feature of
20-
// traversal projections. You can see that it does this from this explain
21-
// output for the above query:
22-
//
23-
// Query String (58 chars, cacheable: true):
24-
// FOR v IN 0..6 OUTBOUND "V/S1:K1" GRAPH "G"
25-
// RETURN v.data
26-
//
27-
// Execution plan:
28-
// Id NodeType Site Est. Comment
29-
// 1 SingletonNode COOR 1 * ROOT
30-
// 2 TraversalNode COOR 64 - FOR v /* vertex (projections: `data`) */ IN 0..6 /* min..maxPathDepth */ OUTBOUND 'V/S1:K1' /* startnode */ GRAPH 'G'
31-
// 3 CalculationNode COOR 64 - LET #3 = v.`data` /* attribute expression */
32-
// 4 ReturnNode COOR 64 - RETURN #3
33-
//
34-
// In the line with Id 2 you can see that the TraversalNode uses a projection to the field `data`.
35-
//
36-
371
rand = require("internal").rand;
382
time = require("internal").time;
393

@@ -48,7 +12,7 @@ function makeRandomString(l) {
4812
return s;
4913
}
5014

51-
function makeGraph(graphName, vertexCollName, edgeCollName) {
15+
function createGraph(graphName, vertexCollName, edgeCollName) {
5216
let graph = require("@arangodb/general-graph");
5317
try {
5418
graph._drop(graphName, true);
@@ -62,25 +26,50 @@ function makeKey(i) {
6226
return "S" + (i % 3) + ":K" + i;
6327
}
6428

65-
function makeTree(depth, vertexCollName, edgeCollName) {
29+
// creates a binary tree where every vertex includes one megabyte of data
30+
//
31+
// The following AQL query and its performance could be of interest:
32+
//
33+
// FOR v IN 0..6 OUTBOUND "V/S1:K1" GRAPH "G"
34+
// RETURN v.data
35+
//
36+
// This traverses the whole graph starting from the root but retrieves only
37+
// a tiny part of the vertex data. This tests the 3.10 feature of
38+
// traversal projections. You can see that it does this from this explain
39+
// output for the above query:
40+
//
41+
// Query String (58 chars, cacheable: true):
42+
// FOR v IN 0..6 OUTBOUND "V/S1:K1" GRAPH "G"
43+
// RETURN v.smallData
44+
//
45+
// Execution plan:
46+
// Id NodeType Site Est. Comment
47+
// 1 SingletonNode COOR 1 * ROOT
48+
// 2 TraversalNode COOR 64 - FOR v /* vertex (projections: `data`) */ IN 0..6 /* min..maxPathDepth */ OUTBOUND 'V/S1:K1' /* startnode */ GRAPH 'G'
49+
// 3 CalculationNode COOR 64 - LET #3 = v.`smallData` /* attribute expression */
50+
// 4 ReturnNode COOR 64 - RETURN #3
51+
//
52+
// In the line with Id 2 you can see that the TraversalNode uses a projection to the field `smallData`.
53+
function makeTreeWithLargeData(graphName, vertexCollName, edgeCollName, depth) {
54+
createGraph(graphName, vertexCollName, edgeCollName);
6655
let V = db._collection(vertexCollName);
6756
let E = db._collection(edgeCollName);
57+
58+
// create vertices
6859
let klumpen = {};
6960
for (let i = 0; i < 1000; ++i) {
7061
klumpen["K"+i] = makeRandomString(1024);
7162
}
7263
for (let i = 1; i <= 2 ** depth - 1; ++i) {
7364
let v = klumpen;
74-
v.data = "D"+i;
65+
v.smallData = "D"+i;
7566
v.smart = "S"+(i % 3);
7667
v._key = makeKey(i);
7768
V.insert(v);
7869
print("Have created", i, "vertices out of", 2 ** depth - 1);
7970
}
8071

81-
// This is now a gigabyte of data, one megabyte per vertex.
82-
83-
// Make a binary tree:
72+
// make a binary tree from these vertices
8473
for (let i = 1; i <= 2 ** (depth - 1) - 1; ++i) {
8574
let e = { _from: vertexCollName + "/" + makeKey(i),
8675
_to: vertexCollName + "/" + makeKey(2 * i)};
@@ -89,5 +78,45 @@ function makeTree(depth, vertexCollName, edgeCollName) {
8978
_to: vertexCollName + "/" + makeKey(2 * i + 1)};
9079
E.insert(e);
9180
}
81+
}
82+
83+
// creates a binary tree with vertex 2 beeing a supernode
84+
// 1
85+
// / \
86+
// 3 2 with additional superNodeSize neighbours
87+
// / \ / \
88+
// 7 6 5 4
89+
// ...
90+
function makeTreeWithSupernode(graphName, vertexCollName, edgeCollName, depth, superNodeSize) {
91+
createGraph(graphName, vertexCollName, edgeCollName);
92+
let V = db._collection(vertexCollName);
93+
let E = db._collection(edgeCollName);
94+
95+
// Add 2^depth - 1 vertices for tree and additionally superNodeSize vertices
96+
let docs = []
97+
for (let i = 1; i <= 2**depth-1+superNodeSize; ++i) {
98+
docs.push({data: "D"+i, smart: "S"+(i%3), _key: makeKey(i)});
99+
}
100+
V.insert(docs);
101+
102+
// make a binary tree from the first 2^depth - 1 vertices
103+
docs = [];
104+
for (let i = 1; i <= 2 ** (depth - 1) - 1; ++i) {
105+
docs.push({ _from: vertexCollName + "/" + makeKey(i),
106+
_to: vertexCollName + "/" + makeKey(2 * i)});
107+
docs.push({ _from: vertexCollName + "/" + makeKey(i),
108+
_to: vertexCollName + "/" + makeKey(2 * i + 1)});
92109

110+
}
111+
E.insert(docs);
112+
113+
// make vertex 2 a supernode
114+
if (depth > 1) {
115+
docs = [];
116+
let key = makeKey(2);
117+
for (let j=1; j <= superNodeSize; j++) {
118+
docs.push({_from: vertexCollName + "/" + key, _to: vertexCollName + "/" + (2**depth - 1+j)});
119+
}
120+
E.insert(docs);
121+
}
93122
}

simple/test.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22
/* jshint globalstrict:false, strict:false, maxlen: 500 */
3-
/* global GLOBAL, makeGraph, makeTree */
3+
/* global GLOBAL, makeTreeWithLargeData, makeTreeWithSupernode */
44

55
const internal = require("internal");
66
const arango = internal.arango;
@@ -9,10 +9,10 @@ const fs = require("fs");
99
const semver = require("semver");
1010
const _ = require("lodash");
1111
const db = require("org/arangodb").db;
12-
require("internal").load("simple/BIGvertices.js");// makeGraph, makeTree
12+
require("internal").load("simple/binaryTrees.js");// makeTreeWithLargeData, makeTreeWithSupernode
1313

1414
GLOBAL.returnValue = 0;
15-
15+
var supernodeTreeDepth = 0; // required for supernode_limit test
1616

1717
function sum (values) {
1818
if (values.length > 1) {
@@ -513,16 +513,19 @@ exports.test = function (testParams) {
513513
createEdges(1000);
514514
} else if (testParams.small) {
515515
createEdges(10000);
516-
makeGraph("Tree", "TreeV", "TreeE");
517-
makeTree(6, "TreeV", "TreeE");
516+
makeTreeWithLargeData("Tree", "TreeV", "TreeE", 6);
517+
supernodeTreeDepth = 4;
518+
makeTreeWithSupernode("Supernode", "SupernodeV", "SupernodeE", supernodeTreeDepth, 5000);
518519
} else if (testParams.medium) {
519520
createEdges(100000);
520-
makeGraph("Tree", "TreeV", "TreeE");
521-
makeTree(7, "TreeV", "TreeE");
521+
makeTreeWithLargeData("Tree", "TreeV", "TreeE", 7);
522+
supernodeTreeDepth = 5;
523+
makeTreeWithSupernode("Supernode", "SupernodeV", "SupernodeE", supernodeTreeDepth, 10000);
522524
} else if (testParams.big) {
523525
createEdges(1000000);
524-
makeGraph("Tree", "TreeV", "TreeE");
525-
makeTree(8, "TreeV", "TreeE");
526+
makeTreeWithLargeData("Tree", "TreeV", "TreeE", 8);
527+
supernodeTreeDepth = 6;
528+
makeTreeWithSupernode("Supernode", "SupernodeV", "SupernodeE", supernodeTreeDepth, 100000);
526529
}
527530

528531
internal.wal.flush(true, true);
@@ -1034,7 +1037,7 @@ exports.test = function (testParams) {
10341037
traversalProjections = function (params) {
10351038
// Note that depth 8 is good for all three sizes small (6), medium (7)
10361039
// and big (8). Depending on the size, we create a different tree.
1037-
db._query(`FOR v IN 0..8 OUTBOUND "TreeV/S1:K1" GRAPH "Tree" RETURN v.data`, {}, {}, {silent});
1040+
db._query(`FOR v IN 0..8 OUTBOUND "TreeV/S1:K1" GRAPH "Tree" RETURN v.smallData`, {}, {}, {silent});
10381041
},
10391042

10401043
outbound = function (params) {
@@ -1153,6 +1156,21 @@ exports.test = function (testParams) {
11531156
{ silent }
11541157
);
11551158
},
1159+
supernode_limit = function (params) {
1160+
// limit output vertices and make sure that at least one of the supernodes's neighbours is in the result but not all of the supernode's neighbours
1161+
// dfs first enumerates all vertices in one half-tree, then the other all vertices in the other half-tree
1162+
// if the supernode is in the first half-tree, limit should be smaller than the number of supernode neighbours (is already assured by tree creation)
1163+
// if the supernode is in the second half-tree, limit should be at least the size of the first half-tree (2**(depth-1)) plus 2 to additionally enumerate the supernode and one of its neighbours
1164+
let limit = 2**(supernodeTreeDepth-1)+2;
1165+
db._query(`FOR v IN 0..8 OUTBOUND "SupernodeV/S1:K1" GRAPH "Supernode" LIMIT @limit RETURN v.data`,
1166+
{
1167+
limit: limit
1168+
},
1169+
{},
1170+
{silent}
1171+
);
1172+
},
1173+
11561174

11571175
// /////////////////////////////////////////////////////////////////////////////
11581176
// documentTests
@@ -2619,6 +2637,10 @@ exports.test = function (testParams) {
26192637
name: "k-shortest-any",
26202638
params: { func: kShortestAny }
26212639
},
2640+
{
2641+
name: "supernode-traversal-limit",
2642+
params: { func: supernode_limit }
2643+
},
26222644
{
26232645
name: "subquery-exists-path",
26242646
params: { func: subqueryExistsPath }

0 commit comments

Comments
 (0)