【问题标题】:Neo4j: slow aggregation with large number of nodesNeo4j:具有大量节点的慢速聚合
【发布时间】:2017-02-08 12:34:20
【问题描述】:


我是 Neo4j 的新手。目前,我正在评估 Neo4j 在我们的一个用例中的使用情况。当我尝试与大量节点进行聚合时遇到问题。

我们正在使用 Neo4j 来存储针对不同类别投票的访问者。投票有一个位置和一个推荐人,我将其建模为单个节点,以便“重用”它们。
这大致是我的架构的样子(节点投票有其他关系,这里省略了):

(Visitor {id})-[VOTED]->(Vote)-[RELATES_TO]->(Category {name})

在 Vistor.id 和 Category.name 上有一个索引

我们的数据集非常大,所以我开始用 400 万票和 300 万访问者测试我的架构。投票涉及 18 个不同的类别。
我希望能够进行聚合。例如,我需要获得投票给“A 类”和“B 类”的不同访客的数量。

MATCH (c:Category)<-[:PARENT*0..3]-(child:Category)<-[:RELATES_TO]-(v:Vote)<-[:VOTED]-(visitor:Visitor)
WHERE c.name = 'Category A'
WITH visitor
MATCH (c:Category)<-[:RELATES_TO]-(v:Vote)<-[:VOTED]-(visitor)
WHERE c.name = 'Category B'
RETURN count(distinct(visitor.id)) as Cat_A_and_B_lovers

这需要将近 30 秒

我还希望能够获得所有类别和不同票数:

MATCH (n:Category)<-[:RELATES_TO]-(:Vote)<-[:VOTED]-(v:Visitor)
RETURN n.name as category, count(DISTINCT v) as count;

这需要 13 秒。

是否可以改进这些时间,或者 Neo4j 不适合这种类型的分析?

我们也在分析每个访问者级别的数据 - 但也希望能够进行这些聚合。

我的设置:
Linux,4 核 14GB
Noe4j 社区版
neo4j.conf:

dbms.memory.heap.initial_size = 2G
dbms.memory.heap.max_size = 10G
dbms.memory.pagecache.size = 10g

-- 2002 年 15 月更新

按照 Michael 的建议,我能够改进这两个查询的执行时间。我的第一个查询:

PROFILE MATCH (n:Category)<-[:RELATES_TO]-()<-[:VOTED]-(v:Visitor)
WITH n, count(DISTINCT v) as count
RETURN n.name as category, count

配置文件输出:

9964 毫秒

编译器 CYPHER 3.1

规划师费用

运行时解释

+-------------------+----------------+---------+---------+--------------------------------------+------------------------------------+
| Operator          | Estimated Rows | Rows    | DB Hits | Variables                            | Other                              |
+-------------------+----------------+---------+---------+--------------------------------------+------------------------------------+
| +ProduceResults   |           1545 |      18 |       0 | category, count                      | category, count                    |
| |                 +----------------+---------+---------+--------------------------------------+------------------------------------+
| +Projection       |           1545 |      18 |      18 | category -- count, n                 | {category : n.name, count : count} |
| |                 +----------------+---------+---------+--------------------------------------+------------------------------------+
| +EagerAggregation |           1545 |      18 |       0 | count -- n                           | n                                  |
| |                 +----------------+---------+---------+--------------------------------------+------------------------------------+
| +Filter           |        2386269 | 4114784 | 4114784 | anon[19], anon[35], anon[37], n, v   | v:Visitor                          |
| |                 +----------------+---------+---------+--------------------------------------+------------------------------------+
| +Expand(All)      |        2386269 | 4114784 | 8229568 | anon[37], v -- anon[19], anon[35], n | ()<-[:VOTED]-(v)                   |
| |                 +----------------+---------+---------+--------------------------------------+------------------------------------+
| +Expand(All)      |        4113784 | 4114784 | 4114807 | anon[19], anon[35] -- n              | (n)<-[:RELATES_TO]-()              |
| |                 +----------------+---------+---------+--------------------------------------+------------------------------------+
| +NodeByLabelScan  |             23 |      23 |      24 | n                                    | :Category                          |
+-------------------+----------------+---------+---------+--------------------------------------+------------------------------------+

现在大约需要 10 秒。我需要让它低于 1 秒。这是否可行(使用neo4j)?

【问题讨论】:

  • 这可能更适合代码审查。 Stack Overflow 是为了帮助解决实际问题,意思是“修复不起作用的东西”,而不是“改进有效的东西”。
  • 索引呢?
  • 我有一个关于 Vistor.id 和 Category.name 的索引。

标签: neo4j


【解决方案1】:

不要配置比你拥有的更多的内存。

将页面缓存设置为 4G,将 min/max-heap 设置为 8

对于查询,最好查看带有EXPLAINPROFILE 的查询计划。

我推荐这样的东西:

MATCH (ca:Category)<-[:PARENT*0..3]-(child)<-[:RELATES_TO]-()<-[:VOTED]-(visitor)
WHERE ca.name = 'Category A'
WITH distinct visitor
MATCH (cb:Category)<-[:RELATES_TO]-()<-[:VOTED]-(visitor)
USING INDEX cb:Category(name)
WHERE cb.name = 'Category B'
RETURN count(distinct(visitor)) as Cat_A_and_B_lovers

您的原始查询每个类别有一个访问者重复并投票,因此可能有很多重复,对于每个重复,都会执行第二次匹配。

每个类别树 A 有多少访问者? 您也可以将两者翻转,即先做'B'然后做'A'。

MATCH (cb:Category)<-[:RELATES_TO]-()<-[:VOTED]-(visitor)
WHERE cb.name = 'Category B'
WITH distinct visitor
MATCH (ca:Category)<-[:PARENT*0..3]-(child)<-[:RELATES_TO]-()<-[:VOTED]-(visitor)
USING INDEX ca:Category(name)
WHERE ca.name = 'Category A'
RETURN count(distinct(visitor)) as Cat_A_and_B_lovers

此外,当节点本身代表相同的概念时,计算属性 (visitor.id) 的成本仅高且无益。

【讨论】:

  • 谢谢迈克尔。按照您的建议,我能够显着改善查询的运行时间。我仍然离我的目标很远,即每个查询的执行时间都低于一秒。就我的数据量而言,这个目标是否可行?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多