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查询有问题吗?
感谢您花时间阅读。
答案 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的WHERE
和DISTINCT
条款将有助于修剪你已经看过的分支,但你仍然只是在树上扩展。
可以使用两个值估算树的branching factor:
对于估算值,您的第一步将返回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 movies。 Netflix 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 ...”来查看你在中间创建的命中/数据库行数,从查询开始然后再这两行。