如何在创建边缘时提高Neo4J性能?

时间:2014-07-30 14:07:19

标签: performance neo4j cypher

我正在使用Neo4J,NodeJS和GTFS数据构建流量计划应用程序;目前,我正试图获得 在柏林地铁网络上为一天工作的东西。这些是总计 到目前为止我收集了:

10 routes 211 stops 4096 trips 83322 stoptimes

简而言之,GTFS(通用运输饲料规范)具有stoptime的概念,表示 特定火车或公共汽车的事件停止乘客上车和下车。 stoptime发生trip, 这是一系列stoptimes,它们发生在特定的日期和时间,它们发生在给定的日期和时间 对于传输网络中的给定stop(或'行'),route。所以这里有很多参考资料。

我遇到的问题是数据量和构建数据库所需的时间。为了 为了加快速度,我已经(1)将数据减少到一天,(2)删除了数据库文件 并让服务器创建一个新的(非常有效!),(3)搜索了很多,以获得更好的查询。唉, 如上图所示,获得图表的所有边缘仍需要30~50分钟。

这些是我建立的指数:

CREATE CONSTRAINT ON (n:trip)     ASSERT n.id IS UNIQUE;
CREATE CONSTRAINT ON (n:stop)     ASSERT n.id IS UNIQUE;
CREATE CONSTRAINT ON (n:route)    ASSERT n.id IS UNIQUE;
CREATE CONSTRAINT ON (n:stoptime) ASSERT n.id IS UNIQUE;
CREATE INDEX ON :trip(`route-id`);
CREATE INDEX ON :stop(`name`);
CREATE INDEX ON :stoptime(`trip-id`);
CREATE INDEX ON :stoptime(`stop-id`);
CREATE INDEX ON :route(`name`);

我猜测唯一的主键应该是最重要的。

这里的查询占用了80%的运行时间(10%与Neo4J无关, 使用纯HTTP后请求提供节点数据需要10%:

MATCH (trip:`trip`), (route:`route`)
WHERE trip.`route-id` = route.id
CREATE UNIQUE (trip)-[:`trip/route` {`~label`: 'trip/route'}]-(route);

MATCH (stoptime:`stoptime`), (trip:`trip`)
WHERE stoptime.`trip-id` = trip.id
CREATE UNIQUE (trip)-[:`trip/stoptime` {`~label`: 'trip/stoptime'}]-(stoptime);

MATCH (stoptime:`stoptime`), (stop:`stop`)
WHERE stoptime.`stop-id` = stop.id
CREATE UNIQUE (stop)-[:`stop/stoptime` {`~label`: 'stop/stoptime'}]-(stoptime);

MATCH (a:stoptime), (b:stoptime)
WHERE a.`trip-id` = b.`trip-id`
AND ( a.idx + 1 = b.idx OR a.idx - 1 = b.idx )
CREATE UNIQUE (a)-[:linked]-(b);

MATCH (stop1:stop)-->(a:stoptime)-[:next]->(b:stoptime)-->(stop2:stop)
CREATE UNIQUE (stop1)-[:distance {`~label`: 'distance', value: 0}]-(stop2);

第一个查询仍然在几分钟的范围内,我觉得只有 数据库中有数千(不是数十万或数百万)tripsstoptime。随后的查询 让{{1}}在我的台式机上花费几十分钟。

(我还计算了每天的时间表是否确实包含83322个停止时间,是的,这似乎是合理的: 在柏林,地铁列车每天运行10班,每天20小时,每小时6或12次,共有173班 地铁站:10线x 2个方向x每线17.3个站点x 20个小时x每小时9个站点给出62280, 足够近。那些 有些错吗?数据中的/ double / extra stop节点(211 停止而不是173),但那些很少。)

坦率地说,如果我找不到加速至少十倍(更多)的方法,那么使用Neo4J就没什么意义了 对于这个项目。只是为了覆盖柏林的单个城市许多许多必须添加更多停留时间, 因为地铁只是这里整体公共交通的一小部分(例如公共汽车和有轨电车都有 拥有7,000个站点的170条路线,因此每天需要大约7,000,000个停车时间。)

更新上面的边缘创建查询,我逐个执行,现在已经运行了一个多小时但还没有完成,这意味着 - 如果事情以线性方式扩展 - 时间需要在一天内提供柏林公共交通数据,这将花费一周的时间。因此,代码目前执行几个数量级太慢而无法生存。

更新 @ MichaelHunger的解决方案确实有效;请参阅下面的回复。

2 个答案:

答案 0 :(得分:2)

我使用LOAD CSV在10分钟内导入12M节点和12M rels到Neo4j。

当您在shell中对查询进行概要分析时,您应该会看到问题。 使用profile为您的查询添加前缀,如果它提到使用索引或者只是标签扫描,则查看配置文件输出。

您是否为插入查询使用参数?那么Neo4j可以重用构建的查询吗?

对于这样的查询:

MATCH (trip:`trip`), (route:`route`)
WHERE trip.`route-id` = route.id
CREATE UNIQUE (trip)-[:`trip/route` {`~label`: 'trip/route'}]-(route);

很可能不会使用您的索引。 你能指出你的数据源吗?我们可以将其转换为CSV,如果它不是,然后更快地导入。 也许我们可以为您的模型创建图形要点?

我宁愿使用:

MATCH (route:`route`)
MATCH (trip:`trip` {`route-id` = route.id)
CREATE (trip)-[:`trip/route` {`~label`: 'trip/route'}]-(route);

对于您的初始导入,您也不需要创建唯一的,因为您只匹配每次旅行一次。 而且我不确定你的标签是什么"〜标签"有用吗?

与您的其他查询相似。

由于数据是公开的,因此在这方面合作会很酷。

我喜欢听到更多关于您计划如何表达查询用例的内容。

上次在莱比锡与培训与会者进行了公共交通时间表的讨论。您也可以通过neo4j.org发送电子邮件给我michael。

也许你想查看这些链接:

Tramchester

伦敦地铁图

答案 1 :(得分:1)

详细解决方案

我很高兴地报道@ MichaelHunger的解决方案就像一个魅力。我修改了边缘构建查询 从以下形状的问题出发,建议查询大纲:

MATCH (route:`route`)
MATCH (trip:`trip` {`route-id`: route.id})
CREATE (trip)-[:`trip/route` {`~label`: 'trip/route'}]->(route)

MATCH (trip:`trip`)
MATCH (stoptime:`stoptime` {`trip-id`: trip.id})
CREATE (trip)-[:`trip/stoptime` {`~label`: 'trip/stoptime'}]->(stoptime)

MATCH (stop:`stop`)
MATCH (stoptime:`stoptime` {`stop-id`: stop.id})
CREATE (stop)-[:`stop/stoptime` {`~label`: 'stop/stoptime'}]->(stoptime)

MATCH (a:stoptime)
MATCH (b:stoptime {`trip-id`: a.`trip-id`, `idx`: a.idx + 1})
CREATE (a)-[:linked {`~label`: 'linked'}]->(b)

MATCH (stop1:stop)--(a:stoptime)-[:linked]-(b:stoptime)--(stop2:stop)
CREATE (stop1)-[:distance {`~label`: 'distance', value: 0}]->(stop2)

可以看出,这里的技巧是为每个参与节点提供自己的MATCH语句 在第二个匹配条件中移动WHERE子句;据推测,如上所述,Neo4J只能 然后利用其索引。

在这些查询到位后,读取节点和构建边缘的过程大约需要13分钟; 在这13分钟内,从外部源获取数据,构建节点表示并发出CREATE个查询 需要大约10分钟,并且在大约3分钟内完成它们之间近50万个边缘

现在没有我的查询(特别是节点CREATE语句和停止距离更新)使用 参数化查询,这是性能提升的另一个潜在来源。

至于~label字段以及为什么我在名称中使用dahes的问题,其中下划线会更多 很方便,这是一个很长的故事,关于我认为有时冲突的好的和实用的命名 用一些语言的语法(大多数语言,我应该说)。但那是无聊的细节。也许更多 intersting是一个问题:为什么有~label属性重复元素标签所说的内容(什么 你在冒号之后写的?)好吧,这是尝试遵守Neo4J惯例(我们在这里使用标签),采取 cypher查询的“标识符,冒号,标签”语法的优点,以及标签所做的那样 出现在返回的值中。

请注意,标签是图形思维Neo4J方式的核心,但*在查询结果中,标签是 明显缺席。当您在结果集中包含除标签之外的任何关系时, 然后那个边缘将以空的形式到达 对象,只告诉你有某些东西而不是是什么。所以我决定复制一下 每个节点和每个边缘上的标签。不是最佳解决方案,但至少现在我得到了一个信息 在Neo4J浏览器中显示图形。

至于如何表达查询用例,这对我来说是一个活跃的研究领域。我猜它会 一切都从一个“感兴趣的领域”开始,比如“显示所有柏林地铁站”或“所有离开的公共汽车” 从我附近的公共汽车站下一个15分钟。数据已经允许查看哪些停靠点直接连接 通过地铁线,他们的地理距离,提供什么服务以及他们采取什么样的路线。这个想法 是抓住数据并以新颖,实用和美丽的方式呈现它们。 9292是完全正确的 接近我的想象;缺少的是空间和时间关系的图形表示。