8000 Bug fix/improve visualizer performance by mchacki · Pull Request #21819 · arangodb/arangodb · GitHub
[go: up one dir, main page]

Skip to content

Bug fix/improve visualizer performance #21819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5026212
Added a test stuie for the graph visualizer backend
mchacki Jun 23, 2025
0e7a6ce
Refactored graphs-v2
mchacki Jun 23, 2025
28a533f
Added a performance test, and started refactoring the backend code fo…
mchacki Jun 23, 2025
b0b924d
Further optimization by using 'KEEP' on required attributes only
mchacki Jun 23, 2025
f331f1a
Applied more complex optimization to make use of projections
mchacki Jun 24, 2025
d06b6d7
Allowed for edge Projections
mchacki Jun 24, 2025
c77b6c8
Removed debug output
mchacki Jun 24, 2025
6d79542
Added CHANGELOG entry
mchacki Jun 24, 2025
dd78cd4
Fixed behaviour for multi-labels
mchacki Jun 24, 2025
44cc13d
disable test, its not working for coverage on all of its parts (#21805)
dothebart Jun 23, 2025
0ab78de
Add inner product metric to vector index (#21814)
mchacki Jun 25, 2025
3279c21
Stop using a streaming transaction after an operation timeouts (#21796)
goedderz Jun 24, 2025
3df4b42
Update README.md for v3.12.5+ (#21804)
Simran-B Jun 24, 2025
0f46ad5
Docs: Reword arangimport startup option description for collection na…
Simran-B Jun 24, 2025
29d1f6b
properly handle connection and make sure we don't forget it (#21822)
dothebart Jun 24, 2025
a67be6c
Fix load memory order (#21818)
jvolmer Jun 25, 2025
5021b04
FE-611 | Add Vector Index Feature (#21793)
bluepal-nadeem-abdun Jun 25, 2025
ed243d0
Added CHANGELOG entry
8000 mchacki Jun 24, 2025
70777db
Merge remote-tracking branch 'origin' into bug-fix/improve-visualizer…
mchacki Jun 25, 2025
6b2e822
Added tests and bugfix for attribute not found behaviour
mchacki Jul 2, 2025
b2213df
Merge remote-tracking branch 'origin' into bug-fix/improve-visualizer…
mchacki Jul 8, 2025
072e309
Merge remote-tracking branch 'origin' into bug-fix/improve-visualizer…
mchacki Jul 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Applied more complex optimization to make use of projections
  • Loading branch information
mchacki committed Jun 24, 2025
commit f331f1a46bed6cd113ea5ac5c86a5650ebaa1602
140 changes: 83 additions & 57 deletions js/apps/system/_admin/aardvark/APP/aardvark.js
Original file line number Diff line number Diff line change
Expand Up @@ -1098,41 +1098,73 @@ var buildAQLQueries = function (config, name, startVertex, multipleIds) {
* NOTE: Dynamic attributes use getAttributeByKey() for nested access (e.g. "attr.subattr")
*/

// Build dynamic attribute selection based on config
const vertexAttrs = ['_id', '_key', '_rev']; // Always include system attributes
const edgeAttrs = ['_id', '_from', '_to']; // Always include system attributes

// Add dynamic vertex attributes based on config
if (config.nodeLabel) {
const labelAttrs = config.nodeLabel.trim().split(' ');
labelAttrs.forEach(attr => {
if (!vertexAttrs.includes(attr) && attr.indexOf('.') === -1) {
vertexAttrs.push(attr);
}
});
}
if (config.nodeSize && !vertexAttrs.includes(config.nodeSize)) {
vertexAttrs.push(config.nodeSize);
}
if (config.nodeColorAttribute && !vertexAttrs.includes(config.nodeColorAttribute)) {
vertexAttrs.push(config.nodeColorAttribute);
}

// Add dynamic edge attributes based on config
if (config.edgeLabel && !edgeAttrs.includes(config.edgeLabel) && config.edgeLabel.indexOf('.') === -1) {
edgeAttrs.push(config.edgeLabel);
}
if (config.edgeColorAttribute && !edgeAttrs.includes(config.edgeColorAttribute)) {
edgeAttrs.push(config.edgeColorAttribute);
}


const buildQuery = (id, depth, limit) => {
// Build vertex data with pre-computed attributes
let vertexDataQuery = `{
_id: v._id,
_key: v._key,
_rev: v._rev`;

// Add node label attributes
if (config.nodeLabel) {
const labelAttrs = config.nodeLabel.trim().split(' ');
labelAttrs.forEach((attr, index) => {
const attrPath = attr.indexOf('.') > -1 ? `v.${attr}` : `v["${attr}"]`;
vertexDataQuery += `,
nodeLabel_${index}: ${attrPath}`;
});
}

// Add node size attribute
if (config.nodeSize) {
const sizePath = config.nodeSize.indexOf('.') > -1 ? `v.${config.nodeSize}` : `v["${config.nodeSize}"]`;
vertexDataQuery += `,
nodeSize: ${sizePath}`;
}

// Add node color attribute
if (config.nodeColorAttribute) {
const colorPath = config.nodeColorAttribute.indexOf('.') > -1 ? `v.${config.nodeColorAttribute}` : `v["${config.nodeColorAttribute}"]`;
vertexDataQuery += `,
nodeColorAttribute: ${colorPath}`;
}

vertexDataQuery += `
}`;

// Build edge data with pre-computed attributes
let edgeDataQuery = `{
_id: e._id,
_from: e._from,
_to: e._to`;

// Add edge label attribute
if (config.edgeLabel && config.edgeLabel.indexOf('.') === -1) {
edgeDataQuery += `,
edgeLabel: e["${config.edgeLabel}"]`;
} else if (config.edgeLabel && config.edgeLabel.indexOf('.') > -1) {
edgeDataQuery += `,
edgeLabel: e.${config.edgeLabel}`;
}

// Add edge color attribute
if (config.edgeColorAttribute) {
const edgeColorPath = config.edgeColorAttribute.indexOf('.') > -1 ? `e.${config.edgeColorAttribute}` : `e["${config.edgeColorAttribute}"]`;
edgeDataQuery += `,
edgeColorAttribute: ${edgeColorPath}`;
}

edgeDataQuery += `
}`;

return `
FOR v, e IN 0..${depth} ANY "${id}" GRAPH "${name}"
${limit ? `LIMIT ${limit}` : ''}
/* Optimized query: return only needed attributes instead of full documents */
LET vertex_data = e == null ? KEEP(v, ${JSON.stringify(vertexAttrs)}) : KEEP(v, ${JSON.stringify(vertexAttrs)})
LET edge_data = e == null ? null : KEEP(e, ${JSON.stringify(edgeAttrs)})
/* Optimized query: pre-compute attribute values in AQL instead of JavaScript */
LET vertex_data = ${vertexDataQuery}
LET edge_data = e == null ? null : ${edgeDataQuery}
LET p = e == null ? {
vertices: [vertex_data],
edges: []
Expand Down Expand Up @@ -1231,8 +1263,8 @@ var generateNodeObject = function (node, config, nodeSize, sizeCategory, colors,
var nodeLabelArr = config.nodeLabel.trim().split(" ");
// in case multiple node labels are given
if (nodeLabelArr.length > 1) {
_.each(nodeLabelArr, function (attr) {
var attrVal = getAttributeByKey(node, attr);
_.each(nodeLabelArr, function (attr, index) {
var attrVal = node[`nodeLabel_${index}`]; // Use pre-computed value from AQL
if (attrVal !== undefined) {
if (typeof attrVal === 'string') {
tooltipText += attr + ": " + attrVal + "\n";
Expand All @@ -1245,7 +1277,7 @@ var generateNodeObject = function (node, config, nodeSize, sizeCategory, colors,
}
});
// in case of multiple node labels just display the first one in the graph
var firstAttrVal = getAttributeByKey(node, nodeLabelArr[0]);
var firstAttrVal = node.nodeLabel_0; // Use pre-computed value from AQL
if (firstAttrVal !== undefined) {
if (typeof firstAttrVal === 'string') {
label = nodeLabelArr[0] + ": " + truncate(firstAttrVal, 16) + " ...";
Expand All @@ -1257,7 +1289,7 @@ var generateNodeObject = function (node, config, nodeSize, sizeCategory, colors,
}
} else {
// in case of single node attribute given
var singleAttrVal = getAttributeByKey(node, nodeLabelArr[0]);
var singleAttrVal = node.nodeLabel_0; // Use pre-computed value from AQL
if (singleAttrVal !== undefined) {
if (typeof singleAttrVal === 'string') {
label = nodeLabelArr[0] + ": " + truncate(singleAttrVal, 16);
Expand Down Expand Up @@ -1286,15 +1318,15 @@ var generateNodeObject = function (node, config, nodeSize, sizeCategory, colors,
let sizeAttributeFound;
if (config.nodeSize && config.nodeSizeByEdges === 'false') {
nodeSize = 20;
if (Number.isInteger(node[config.nodeSize])) {
nodeSize = node[config.nodeSize];
if (Number.isInteger(node.nodeSize)) { // Use pre-computed value from AQL
nodeSize = node.nodeSize;
sizeAttributeFound = true;
} else {
sizeAttributeFound = false;
}

sizeCategory = node[config.nodeSize] || '';
nodesSizeValues.push(node[config.nodeSize]);
sizeCategory = node.nodeSize || ''; // Use pre-computed value from AQL
nodesSizeValues.push(node.nodeSize); // Use pre-computed value from AQL
}

var calculatedNodeColor = '#48BB78';
Expand Down Expand Up @@ -1329,7 +1361,7 @@ var generateNodeObject = function (node, config, nodeSize, sizeCategory, colors,
nodeObj.group = coll;
nodeObj.color = "";
} else if (config.nodeColorAttribute !== '') {
var attr = node[config.nodeColorAttribute]
var attr = node.nodeColorAttribute; // Use pre-computed value from AQL
if (attr) {
nodeObj.group = JSON.stringify(attr);
nodeObj.color = "";
Expand Down Expand Up @@ -1377,22 +1409,16 @@ var processGraphData = function (cursor, config, colors, startVertex, multipleId
_.each(obj.edges, function (edge) {
if (edge._to && edge._from) {
if (config.edgeLabel && config.edgeLabel.length > 0) {
// configure edge labels
if (config.edgeLabel.indexOf('.') > -1) {
edgeLabel = getAttributeByKey(edge, config.edgeLabel);
if (nodeLabel === undefined || nodeLabel === '') {
edgeLabel = edgeLabel._id;
}
} else {
if (edge[config.edgeLabel] !== undefined) {
if (typeof edge[config.edgeLabel] === 'string') {
edgeLabel = edge[config.edgeLabel];
} else {
edgeLabel = JSON.stringify(edge[config.edgeLabel]);
}
// Use pre-computed edge label from AQL
var edgeLabelValue = edge.edgeLabel;
if (edgeLabelValue !== undefined) {
if (typeof edgeLabelValue === 'string') {
edgeLabel = edgeLabelValue;
} else {
edgeLabel = notFoundString;
edgeLabel = JSON.stringify(edgeLabelValue);
}
} else {
edgeLabel = notFoundString;
}

if (typeof edgeLabel !== 'string') {
Expand Down Expand Up @@ -1476,12 +1502,12 @@ var processGraphData = function (cursor, config, colors, startVertex, multipleId
edgeObj.color = tmpObjEdges[coll];
}
} else if (config.edgeColorAttribute !== '') {
if(edge[config.edgeColorAttribute]) {
edgeObj.colorCategory = edge[config.edgeColorAttribute] || '';
if(edge.edgeColorAttribute) { // Use pre-computed value from AQL
edgeObj.colorCategory = edge.edgeColorAttribute || '';
const tempEdgeColor = Math.floor(Math.random()*16777215).toString(16).substring(1, 3) + Math.floor(Math.random()*16777215).toString(16).substring(1, 3) + Math.floor(Math.random()*16777215).toString(16).substring(1, 3);

const edgeColorObj = {
'name': edge[config.edgeColorAttribute] || '',
'name': edge.edgeColorAttribute || '',
'color': tempEdgeColor
};

Expand All @@ -1491,7 +1517,7 @@ var processGraphData = function (cursor, config, colors, startVertex, multipleId
}
}

var attr = edge[config.edgeColorAttribute];
var attr = edge.edgeColorAttribute; // Use pre-computed value from AQL
if (attr) {
if (tmpObjEdges.hasOwnProperty(attr)) {
edgeObj.color = '#' + edgesColorAttributes.find(obj => obj.name === attr).color;
Expand Down
24 changes: 12 additions & 12 deletions tests/js/client/shell/api/aardvark-graphvisualizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,9 @@ function performanceTestSuite() {
attr5: LARGE_STRING
});

// Add depth 1 vertices (10 children of root)
// Add depth 1 vertices (20 children of root)
const depth1Keys = [];
for (let i = 0; i < 10; i++) {
for (let i = 0; i < 20; i++) {
const key = `depth1_${i}`;
depth1Keys.push(key);
allVertices.push({
Expand All @@ -683,9 +683,9 @@ function performanceTestSuite() {
});
}

// Add depth 2 vertices (10 children for each depth 1 node)
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
// Add depth 2 vertices (20 children for each depth 1 node)
for (let i = 0; i < 20; i++) {
for (let j = 0; j < 20; j++) {
const key = `depth2_${i}_${j}`;
allVertices.push({
_key: key,
Expand All @@ -706,7 +706,7 @@ function performanceTestSuite() {
const allEdges = [];

// Add edges from root to depth 1 nodes
for (let i = 0; i < 50; i++) {
for (let i = 0; i < 20; i++) {
const key = depth1Keys[i];
allEdges.push({
_from: `perf_vertices/${rootKey}`,
Expand All @@ -721,9 +721,9 @@ function performanceTestSuite() {
}

// Add edges from depth 1 to depth 2 nodes
for (let i = 0; i < 50; i++) {
for (let i = 0; i < 20; i++) {
const parentKey = depth1Keys[i];
for (let j = 0; j < 10; j++) {
for (let j = 0; j < 20; j++) {
const key = `depth2_${i}_${j}`;
allEdges.push({
_from: `perf_vertices/${parentKey}`,
Expand Down Expand Up @@ -780,10 +780,10 @@ function performanceTestSuite() {
assertTrue(Array.isArray(doc.parsedBody.edges));

// Assert exact counts for the tree structure:
// 1 root + 10 depth1 + 100 depth2 = 111 vertices
// 10 root->depth1 + 100 depth1->depth2 = 110 edges
assertEqual(doc.parsedBody.nodes.length, 111, "Expected 111 nodes (1 root + 10 depth1 + 100 depth2)");
assertEqual(doc.parsedBody.edges.length, 110, "Expected 110 edges (10 root->depth1 + 100 depth1->depth2)");
// 1 root + 20 depth1 + 400 depth2 = 421 vertices
// 20 root->depth1 + 400 depth1->depth2 = 420 edges
assertEqual(doc.parsedBody.nodes.length, 421, "Expected 421 nodes (1 root + 20 depth1 + 400 depth2)");
assertEqual(doc.parsedBody.edges.length, 420, "Expected 420 edges (20 root->depth1 + 400 depth1->depth2)");

// Verify root node is properly marked as start vertex
let rootNode = doc.parsedBody.nodes.find(n => n.id === 'perf_vertices/root');
Expand Down
0