Neo4j在4度Cypher查询时崩溃

时间:2013-12-12 21:41:35

标签: neo4j cypher

neo4j noob在这里,在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;

我可以使用上述查询快速轻松地找到喜欢该电影的用户。我可以进一步按照这条路径来吸引喜欢与喜欢movie1的人一样的电影的用户。我称之为第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在100%cpu利用率和使用4GB RAM时旋转几分钟。然后它发回一个异常“OutOfMemoryError:超出GC开销限制”。

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

感谢您花时间阅读。

3 个答案:

答案 0 :(得分:1)

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

您正在使用的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子句(但是 - 只有当你没有使用传统索引并使用标签时)......

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

答案 1 :(得分:1)

这是一个非常激烈的查询,你越往前越近,你可能会得到一组曾经评价任何电影的用户,因为你实际上只是从树形图中扩展出来给了电影。 @Huston的WHEREDISTINCT条款将有助于修剪你已经看过的分支,但你仍然只是在树上扩展。

可以使用两个值估算树的branching factor

  • u,喜欢电影的平均用户数(传入:电影)
  • m,每个用户喜欢的平均电影数量(传出来自:用户)

对于估算值,您的第一步将返回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个用户,我认为可以肯定地说您的查询可能会返回您数据集中的每个用户(而不是每个用户的148,000个副本)。

您的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会耗尽内存。

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

  • 用户评价最像我的电影?
  • 用户对我评分的大部分相同电影的评分为
  • 哪些电影让我看过类似电影的用户看过我还没看过?

干杯, 厘米

答案 2 :(得分:1)

为了尽量减少@ 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 ...”来查看你在中间创建的命中/数据库行数,从查询开始然后再这两行。