【问题标题】:Neo4j crashes on 4th degree Cypher queryNeo4j 在 4 度 Cypher 查询中崩溃
【发布时间】:2013-12-12 21:41:35
【问题描述】:

这里是 Neo4j 新手,在 Neo4j 2.0.0 社区

我有一个包含 24,000 部电影和 2700 名用户的图形数据库,以及大约 60,000 个用户和电影之间的 LIKE 关系。

假设我有一部特定的电影 (movie1)。

START movie1=node:Movie("MovieId:88cacfca-3def-4b2c-acb2-8e7f4f28be04") 
MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)
RETURN usersLikingMovie1;

我可以通过上述查询快速轻松地找到喜欢这部电影的用户。我可以进一步沿着这条路径获得喜欢电影的用户和喜欢电影 1 的人一样的用户。我将这些称为第 2 代用户

START movie1=node:Movie("MovieId:88cacfca-3def-4b2c-acb2-8e7f4f28be04") 
MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)-[:LIKES]->(moviesGen1)<-[:LIKES]-(usersGen2)
RETURN usersGen2;

此查询大约需要 3 秒,并返回 1896 个用户。
现在我将这个查询更进一步,以获取上述用户喜欢的电影(第 2 代电影)

START movie1=node:Movie("MovieId:88cacfca-3def-4b2c-acb2-8e7f4f28be04") 
MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)-[:LIKES]->(moviesGen1)<-[:LIKES]-(usersGen2)-[:LIKES]->(moviesGen2)
RETURN moviesGen2;

此查询导致 neo4j 在 CPU 利用率为 100% 并使用 4GB 内存的情况下旋转几分钟。然后它发回一个异常“OutOfMemoryError: GC overhead limit exceeded”。

我希望有人可以帮助我并向我解释这个问题。 Neo4j 是否不打算以高性能的方式处理这种深度的查询? 我的 Cypher 查询有问题吗?

感谢您抽出宝贵时间阅读。

【问题讨论】:

  • 有机会分享您的数据集吗?
  • 您好,请查看上面的评论。
  • 抱歉,无法分享。无论如何,它并不是真正的电影数据库。

标签: neo4j cypher


【解决方案1】:

这是一个非常密集的查询,您越深入,您可能越接近所有曾评价过任何电影的用户,因为您实际上只是在树形图中展开给定的电影。 @Huston 的 WHEREDISTINCT 子句将有助于修剪您已经看到的分支,但您仍然只是在树中扩展。

你的树的branching factor 可以用两个值来估计:

  • u,喜欢一部电影的平均用户数(进入 :Movie)
  • m,每个用户喜欢的电影平均数量(来自:User)

对于估计,您的第一步将返回m 用户。在下一步中,对于每个用户,您将获得他们每个人都喜欢的所有电影,然后是所有喜欢所有这些电影的用户:

gen(1) => u
gen(2) => u * (m * u)

对于每一代,你都会添加另一个m*u,所以你的第三代是:​​

gen(3) => u * (m * u) * (m * u)

或更笼统地说:

gen(n) => u^n * m^(n-1)

您可以通过计算喜欢/用户和喜欢/电影的平均值来估计分支因子,但这可能非常不准确,因为它给您 22.2 个喜欢/用户和 2.5 个喜欢/电影。对于任何值得评价的电影,这些数字都不合理。更好的方法是采用评级的中位数或查看直方图并将峰值用作分支因子。

为了说明这一点,average Netflix user rated 200 moviesNetflix Prize 训练集有 17,770 部电影、480,189 名用户和 100,480,507 个评分。那是 209 个评分/用户和 5654 个评分/电影。

为了简单起见(假设您的数据集要小得多),让我们使用:

m = 20 movie ratings/user 
u = 100 users have rated/movie

您在 gen-3 中的查询(没有不同)将返回:

gen(3) = 100^3 * 20^2
       = 400,000,000

4 亿节点(用户)

由于您只有 2700 个用户,我认为可以肯定地说您的查询可能会返回数据集中的每个用户(而是每个用户的 14.8 万份)。

您的 ASCII 电影节点 -- (n:Movie {movieid:"88cacfca-3def-4b2c-acb2-8e7f4f28be04"}) 最少 58 个字节。如果您的用户大致相同,假设每个节点是 60 字节,那么您对这个结果集的存储要求是:

400,000,000 nodes * 60 bytes
= 24,000,000,000 bytes
= 23,437,500 kb
= 22,888 Mb
= 22.35 Gb

因此,根据我的保守估计,您的查询需要 22 GB 的存储空间。 Neo4j 会耗尽内存似乎很合理。

我的猜测是,您试图在用户模式中找到相似之处,但您使用的查询返回的是数据集中的所有用户重复了很多次。也许您想对您的数据提出更多问题,例如:

  • 什么用户评价最像我的电影?
  • 用户对我评价的大多数相同电影的评价
  • 哪些电影的用户评价与我相似,但我还没有看过?

干杯, 厘米

【讨论】:

  • 内存计算的缺陷在于,当它只返回一个值时,它不需要将整个结果保存在内存中。匹配器向匹配结果集返回一个迭代器——这里不是偷懒的。
  • 但是必须查看的路径数仍然有效。并且 cypher 目前使用面包优先搜索,它在内存中保留了相当多的状态(到目前为止的遍历分支)
【解决方案2】:

为了尽量减少@cod3monk3y 所说的爆炸,我会限制中间结果的数量。

START movie1=node:Movie("MovieId:88cacfca-3def-4b2c-acb2-8e7f4f28be04") 
MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)-[:LIKES]->(moviesGen1)
WITH distinct moviesGen1
MATCH (moviesGen1)<-[:LIKES]-(usersGen2)-[:LIKES]->(moviesGen2)
RETURN moviesGen2;

甚至像这样

START movie1=node:Movie("MovieId:88cacfca-3def-4b2c-acb2-8e7f4f28be04") 
MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)-[:LIKES]->(moviesGen1)
WITH distinct moviesGen1
MATCH (moviesGen1)<-[:LIKES]-(usersGen2)
WITH distinct usersGen2
MATCH (usersGen2)-[:LIKES]->(moviesGen2)
RETURN distinct moviesGen2;

如果您愿意,您可以在 neo4j shell 中使用“profile start ...”来查看您在两者之间创建了多少命中/db-rows,从您的查询开始,然后是这两个。

【讨论】:

  • 感谢您的想法,我尝试了您的第二个建议,但不知何故,它最终在查询中花费了 10 倍的时间。可能是因为 neo4j 在每个 WITH 语句都做了完整的节点解析?
  • 您是否也测试了其他建议?特别是。 “个人资料一号”?
【解决方案3】:

Cypher 是一种模式匹配语言,重要的是要记住 MATCH 子句总是会在 Graph 中存在的任何地方找到模式。

您使用的 MATCH 子句的问题在于,有时 Cypher 会发现不同的模式,其中 'usersGen2' 与 'usersLikingMovie1' 相同,而 'movie1' 与不同模式的 'movieGen1' 相同。因此,从本质上讲,Cypher 每次都在 Graph 中找到该模式,在查询期间将其保存在内存中,然后返回所有 moviesGen2 节点,这实际上可能是同一个节点 n 次。

MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)-[:LIKES]->(moviesGen1)<-[:LIKES]-(usersGen2)

如果您明确告诉 Cypher,每个匹配模式的电影和用户应该不同,它应该可以解决问题。试试这个?此外,DISTINCT 参数将确保您只抓取每个“moviesGen2”节点一次。

START movie1=node:Movie("MovieId:88cacfca-3def-4b2c-acb2-8e7f4f28be04") 
MATCH (movie1)<-[:LIKES]-(usersLikingMovie1)-[:LIKES]->(moviesGen1)<-[:LIKES]-(usersGen2)-[:LIKES]->(moviesGen2)
WHERE movie1 <> moviesGen2 AND usersLikingMovie1 <> usersGen2
RETURN DISTINCT moviesGen2;

此外,在 2.0 中,开始子句不是必需的。因此,您实际上可以将 START 子句全部省略(但是 - 仅当您不使用旧索引并使用标签时)...

希望这可行...如果有语法错误,请更正我的答案...

【讨论】:

  • 如果没有 start 子句,您不能将旧索引混合到密码中。 ://
  • 我要感谢您第一个回复并能够立即发现我的想法中的一个主要谬误。在测试了您的建议的各个方面后,我发现我造成的主要具体问题是我试图返回整个 Movie 实体,这些实体也与它们的关系一起返回,这就是导致 JVM 爆炸的原因。在我修改您的查询以仅返回 MovieIds 后,查询花了一分钟,在此过程中仅使用了 30% 的 cpu。
  • 谢谢韦斯!好电话!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-12
  • 2015-11-01
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多