什么是最快的ArangoDB朋友的朋友查询(带计数)?

19
我正在尝试使用ArangoDB获取朋友的朋友列表。不仅是基本的朋友的朋友列表,我还想知道用户和朋友之间有多少共同的朋友,并对结果进行排序。 经过多次尝试重写性能最佳的AQL查询,我最终得到了以下内容:
LET friends = (
  FOR f IN GRAPH_NEIGHBORS('graph', @user, {"direction": "any", "includeData": true, "edgeExamples": { name: "FRIENDS_WITH"}})
  RETURN f._id
)

LET foafs = (FOR friend IN friends
  FOR foaf in GRAPH_NEIGHBORS('graph', friend, {"direction": "any", "includeData": true, "edgeExamples": { name: "FRIENDS_WITH"}})
    FILTER foaf._id != @user AND foaf._id NOT IN friends
    COLLECT foaf_result = foaf WITH COUNT INTO common_friend_count
    RETURN {
      user: foaf_result,
      common_friend_count: common_friend_count
    }
)
FOR foaf IN foafs
  SORT foaf.common_friend_count DESC
  RETURN foaf

不幸的是,性能不如我所希望的那样好。与相同查询(和数据)的 Neo4j 版本相比,AQL 似乎要慢得多(5-10倍)。

我想知道的是... 我们该如何改进查询以使其性能更好?

1个回答

22

我是ArangoDB的核心开发人员之一,尝试优化了您的查询。由于没有您的数据集,所以我只能谈论我的测试数据集,如果您可以验证我的结果,我会很高兴。

首先,我正在运行ArangoDB 2.7,但在这种特殊情况下,我不希望与2.6相比有太大的性能差异。

在我的数据集中,我可以按原样执行您的查询,大约需要7秒。建议采用以下修复方案:
在您的friends语句中,您使用includeData: true并且仅返回_id。通过使用includeData: falseGRAPH_NEIGHBORS直接返回_id,我们也可以在此处摆脱子查询。

LET friends = GRAPH_NEIGHBORS('graph', 
                              @user,
                              {"direction": "any",
                               "edgeExamples": { 
                                   name: "FRIENDS_WITH"
               }})

在我的机器上,这样做可以将时间缩短到约1.1秒。因此,我预计这将接近Neo4J的性能。

为什么会有如此高的影响?我们内部首先查找_id值,而不实际加载文档JSON。在您的查询中,您不需要任何此类数据,因此我们可以安全地继续不打开它。

但现在是真正的改进时间

您的查询按“逻辑”方式进行,首先获取用户邻居,然后找到他们的邻居,计算foaf出现的次数并对其进行排序。这必须在内存中构建完整的foaf网络并将其作为一个整体进行排序。

你也可以用另一种方式做到:
1. 查找用户的所有friends(只需_ids
2. 查找所有foaf(完整文档)
3. 对于每个foaf,查找所有foaf_friends(只需_ids
4. 找到friendsfoaf_friends的交集并统计它们的数量

这个查询应该是这样的:

LET fids = GRAPH_NEIGHBORS("graph",
                           @user,
                           {
                             "direction":"any",
                             "edgeExamples": {
                               "name": "FRIENDS_WITH"
                              }
                           }
                          )
FOR foaf IN GRAPH_NEIGHBORS("graph",
                            @user,
                            {
                              "minDepth": 2,
                              "maxDepth": 2,
                              "direction": "any",
                              "includeData": true,
                              "edgeExamples": {
                                "name": "FRIENDS_WITH"
                              }
                            }
                           )
  LET commonIds = GRAPH_NEIGHBORS("graph",
                                  foaf._id, {
                                    "direction": "any",
                                    "edgeExamples": {
                                      "name": "FRIENDS_WITH"
                                     }
                                  }
                                 )
  LET common_friend_count = LENGTH(INTERSECTION(fids, commonIds))
  SORT common_friend_count DESC
  RETURN {user: foaf, common_friend_count: common_friend_count}

在我的测试图表中,它的执行时间约为0.024秒。

这使得执行速度比您当前在Neo4j中查询的速度快了 250倍 ,但由于我没有您的数据集,因此无法验证它。如果您可以这样做并告诉我那就太好了。

最后一件事

对于edgeExamples:{name:“FRIENDS_WITH”}includeData相同,在这种情况下,我们必须找到实际的边缘并查看它。如果根据名称将边缘存储在单独的集合中,然后删除edgeExamples,则可以避免这种情况。这将进一步提高性能(特别是如果有很多边缘)。

未来

敬请期待我们的下一个版本,我们正在为AQL添加更多功能,这将使您的查询更加容易,并应该提供另一个性能提升。


谢谢!我会在周一检查、验证并接受你的答案!我们非常感激你抽出时间回答我们的问题 ;) - Terry Seidler
3
在我们的情况下,你所做的第一个改进比我们的版本快得多。特别是我们最慢的查询从你的改进中受益匪浅。它确实使得AQL结果非常接近Neo4j的版本。至于第二个查询,它使我们最坏情况下的foaf-查询更快了,但最好情况下的查询却有点变慢:(。无论如何,第一个改进都对我们有很大帮助;)。 - Terry Seidler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接