Получение данных для d3 из ArangoDB с помощью AQL (или arangojs)

Я создаю приложение, основанное на силовом графике d3 с ArangoDB на бэкэнде, и я хочу иметь возможность загружать узел и динамически связывать данные из Arango как можно эффективнее.

Я не эксперт в d3, но в целом макет force, похоже, хочет, чтобы его данные были массивом узлов и массивом ссылок, которые имеют фактические объекты узла в качестве своих источников и целей, например:

var nodes = [
        {id: 0, reflexive: false},
        {id: 1, reflexive: true },
        {id: 2, reflexive: false}
    ],
    links = [
        {source: nodes[0], target: nodes[1], left: false, right: true },
        {source: nodes[1], target: nodes[2], left: false, right: true }
    ];

В настоящее время я использую следующий запрос AQL для получения соседние узлы, но довольно громоздкие. Часть сложности заключается в том, что я хочу включить информацию о ребрах для узлов, даже если эти ребра не пересекаются (чтобы отобразить количество ссылок, которые узел имеет перед загрузкой этих ссылок из базы данных).

LET docId = "ExampleDocClass/1234567"

 // get data for all the edges
LET es = GRAPH_EDGES('EdgeClass',docId,{direction:'any',maxDepth:1,includeData:true})

// create an array of all the neighbor nodes
LET vArray = ( 
    FOR v IN GRAPH_TRAVERSAL('EdgeClass',docId[0],'any',{ maxDepth:1})
        FOR v1 IN v RETURN v1.vertex
    )

// using node array, return inbound and outbound for each node 
LET vs = (
    FOR v IN vArray
        // inbound and outbound are separate queries because I couldn't figure out
        // how to get Arango to differentiate inbout and outbound in the query results
        LET oe = (FOR oe1 IN GRAPH_EDGES('EdgeClass',v,{direction:'outbound',maxDepth:1,includeData:true}) RETURN oe1._to)
        LET ie = (FOR ie1 IN GRAPH_EDGES('EdgeClass',v,{direction:'inbound',maxDepth:1,includeData:true}) RETURN ie1._from)
        RETURN {'vertexData': v, 'outEdges': oe, 'inEdges': ie}
    )
RETURN {'edges':es,'vertices':vs}

конечный вывод выглядит следующим образом: http://pastebin.com/raw.php?i=B7uzaWxs ...который можно прочитать почти непосредственно в d3 (мне просто нужно немного дедуплицировать).

мои узлы графа имеют большое количество ссылок, поэтому важна производительность (как с точки зрения нагрузки на сервер и клиент, так и размера файла для связи между ними). Я также планирую создать различные команды для взаимодействия с графом, помимо простого расширения соседних узлов. Есть ли способ лучше структурировать этот запрос AQL (например, избегая четырех отдельных графических запросов) или вообще избегать AQL, используя функции arangojs или приложение FOXX, все еще структурируя ответ в формате, который мне нужен для d3 (включая данные связи с каждым узлом)?

1 ответов


извините за поздний ответ, мы были заняты строительством v2.8 ;) Я бы предложил сделать как можно больше вещей на стороне базы данных, так как копирование и сериализация/десериализация JSON по сети обычно дороги, поэтому передача как можно меньше данных должна быть хорошей целью.

прежде всего, я использовал ваш запрос и выполнил его на образце набора данных, который я создал (~ 800 вершин и 800 ребер попали в мой набор данных) Качестве основы я использовал время выполнения вашего запроса что в моем случае было ~5.0 s

поэтому я попытался создать тот же результат, что и в AQL. Я нашел некоторые улучшения в вашем запросе: 1. GRAPH_NEIGHBORS немного быстрее, чем GRAPH_EDGES. 2. По возможности избегайте {includeData: true} если вам не нужны данные Особенно если вам надо от вершины.Параметр _id только GRAPH_NEIGHBORS С {includeData: false} превосходит GRAPH_EDGES на порядок. 3. GRAPH_NEIGHBORS дедуплицируется, GRAPH_EDGES-нет. Что в вашем случае кажется желательным. 3. Там вы можете избавиться от нескольких подзапросов.

Итак, вот чистый запрос AQL, который я мог бы придумать:

LET docId = "ExampleDocClass/1234567"
LET edges = GRAPH_EDGES('EdgeClass',docId,{direction:'any',maxDepth:1,includeData:true})
LET verticesTmp = (FOR v IN GRAPH_NEIGHBORS('EdgeClass', docId, {direction: 'any', maxDepth: 1, includeData: true})
  RETURN {
    vertexData: v,
    outEdges: GRAPH_NEIGHBORS('EdgeClass', v, {direction: 'outbound', maxDepth: 1, includeData: false}),
    inEdges: GRAPH_NEIGHBORS('EdgeClass', v, {direction: 'inbound', maxDepth: 1, includeData: false})
  })
LET vertices = PUSH(verticesTmp, {
  vertexData: DOCUMENT(docId),
  outEdges: GRAPH_NEIGHBORS('EdgeClass', docId, {direction: 'outbound', maxDepth: 1, includeData: false}),
  inEdges: GRAPH_NEIGHBORS('EdgeClass', docId, {direction: 'inbound', maxDepth: 1, includeData: false})
})
RETURN { edges, vertices }

это дает тот же формат результата, что и ваш запрос, и имеет то преимущество, что каждая вершина, связанная с docId, хранится ровно один раз в вершинах. Также сам docId хранится ровно один раз в вершинах. На стороне клиента дедупликация не требуется. Но, в outEdges / inEdges каждой вершины все связанные вершины также точно один раз, я не знаю, если вы нужно знать, есть ли несколько ребер между вершинами в этом списке.

этот запрос использует ~0.06 s на моем наборе данных.

однако, если вы приложите больше усилий, вы также можете использовать ручной обход внутри приложения Foxx. Это немного сложнее, но может быть быстрее в вашем случае, как вы делаете меньше подзапросов. Код для этого может выглядеть следующим образом:

var traversal = require("org/arangodb/graph/traversal");
var result = {
  edges: [],
  vertices: {}
}
var myVisitor = function (config, result, vertex, path, connected) {
  switch (path.edges.length) {
    case 0:
      if (! result.vertices.hasOwnProperty(vertex._id)) {
        // If we visit a vertex, we store it's data and prepare out/in
        result.vertices[vertex._id] = {
          vertexData: vertex,
          outEdges: [],
          inEdges: []
        };
      }

      // No further action
      break;
    case 1:
      if (! result.vertices.hasOwnProperty(vertex._id)) {
        // If we visit a vertex, we store it's data and prepare out/in
        result.vertices[vertex._id] = {
          vertexData: vertex,
          outEdges: [],
          inEdges: []
        };
      }
      // First Depth, we need EdgeData
      var e = path.edges[0];
      result.edges.push(e);
      // We fill from / to for both vertices
      result.vertices[e._from].outEdges.push(e._to);
      result.vertices[e._to].inEdges.push(e._from);
      break;
    case 2:
      // Second Depth, we do not need EdgeData
      var e = path.edges[1];
      // We fill from / to for all vertices that exist
      if (result.vertices.hasOwnProperty(e._from)) {
        result.vertices[e._from].outEdges.push(e._to);
      }
      if (result.vertices.hasOwnProperty(e._to)) {
        result.vertices[e._to].inEdges.push(e._from);
      }
      break;
  }
};
var config = {
  datasource: traversal.generalGraphDatasourceFactory("EdgeClass"),
  strategy: "depthfirst",
  order: "preorder",
  visitor: myVisitor,
  expander: traversal.anyExpander,
  minDepth: 0,
  maxDepth: 2
};
var traverser = new traversal.Traverser(config);
traverser.traverse(result, {_id: "ExampleDocClass/1234567"});
return {
  edges: result.edges,
  vertices: Object.keys(result.vertices).map(function (key) {
              return result.vertices[key];
            })
};

идея этого обхода чтобы посетить все вершины от начальной вершины до двух ребер. Все вершины в глубине 0-1 будут добавлены с данными в объект вершин. Все ребра, исходящие из начальной вершины будут добавлены данные в список ребер. Все вершины в глубине 2 будут устанавливать только outEdges / inEdges в результате.

это имеет то преимущество, что vertices дедуплицированы. и outEdges / inEdges содержат все связанные вершины несколько раз, если есть несколько ребер между их.

этот обход выполняется в моем наборе данных в ~0.025 s таким образом, это в два раза быстрее, чем только решение AQL.

надеюсь, это все еще помогает;)