Neo4j的查询速度为何这么慢?这能商用吗?

由于公司产品需要,最近搭建了一个neo4j开发环境,用load csv的方式导入了70000多个nodes,速度还凑合。然后要为这些nodes建立relationships,需要先查询出其中的168个node,然后两两建立关系。结果发现match的速度巨慢无比,就没有一次跑完过,甚至出现过crash。当把要查询的node减为10条20条是可以跑完的,但速度也很慢,10条都要将近1秒。插入操作也不快,我是用driver的方式循环create,每一条都要至少半秒。这种速度是我配…
关注者
96
被浏览
13,166

7 个回答

我这几亿条数据,找多维度的关系都不慢,你这个数据量怎么可能慢呢....
题主你得调优了,依我看是你代码没写好,咱先做基础的,再调服务器的。
-------------------
我使用neo4j的经验表明(数亿数据,多维度关系构建),个人觉得楼上说的什么I/O不是主要矛盾,服务器的性能不至于千差万别。
楼上还有个人提到了neo4j官方的tuning performace这个很不错,但是,但是,先写好你的代码,然后我再跟你聊聊jvm的事儿吧;
-------------------
建议:
先1、2、3,把索引弄明白,80%就成功了;
1. 织入数据的时候是否使用了索引策略
比如neo4j的模式索引、手工索引和自动索引技术
以上技术点都需要体现在代码中,要CODE的哦;
2. cypher是否用到了索引
对于cypher来说,一个查询其实有多种的写法,需要查看每一种的执行计划,去explain吧
3. 你添加的那些所谓的索引的状态是什么? 它们ONLINE了么?
neo4j支持两种方式添加
1. 异步, 即执行完索引命令后, neo4j默默的去执行,直到此索引ONLINE
2. 同步, 可阻塞
无论哪种,切忌,online的索引才生效
4. JVM tuning performance
Neo4j两种运行方式,两种的调优大体相同,毕竟是基于J2EE的项目;
1. Embedded模式
图数据库与你的程序像在一起一样,是像,不是真的在一起。
通过指定的磁盘目录标示图服务位置,使用neo4j原生API开启事务,在事务中进行图的各种操作;
所以,neo4j与你的应用一起共享一个jvm示例,与应用共命运同呼吸,一起GC。具体如何调优JVM,特别是NEO4J的jvm, 也还是有很多门道的,基础是JVM调优,减少GC次数和时间;如果题主索引都搞明白且代码上去跑效率还提不高,咱再说GC的事情;
2. Server Standalone模式
见名知意,我不啰嗦了。
希望能对从事或者喜爱neo4j的朋友有所帮助~~

你循环create当然慢,因为这样的数据导入是在Neo4j事物处理的框架内执行的。如果对大量数据进行初始化加载,那么就用Neo4j-import;增量数据的同步加载又不想暂停数据库服务,那就要用load csv,如果可以忍受数据库的短暂停服,那么Batch-inserter更适合你。有关性能方面的参考可以参见:如何将大规模数据导入Neo4。我目前也在用Neo4j做项目,加载1000w节点、600w边、2000w属性的时间大概耗时2min(p.s.我的硬盘是机械的)。

至于查询,那要看你的索引建立情况、数据的返回量、数据查询逻辑了。适当建立索引能大幅提高查询速度,尤其是唯一性索引。数据返回量方面考验的性能从前到后依次为cpu、磁盘、带宽,这是硬件性能方面,软件方面你所能做的是优化数据查询逻辑。如何优化查询逻辑,构建最优查询计划,具体可以参看neo4j-developer-manual-3.0中的quary tunning章节,以帮助你对Neo4j的查询计划构建有更深入的了解,进而帮助你写出更简洁高效的cypher语句。

我是用python的django框架开发的web应用,目前的性能大概是,在进行五层关联关系查询,返回1000条数据的情况下,查询可在500ms内完成,数据返回速度和前台展示的时间要看你的带宽大小和js逻辑是否复杂,与Neo4j性能无关。另外,不建议将过多属性存放在Neo4j中,除非是那些cypher语句中筛选条件里面可能会用到的属性。至于那些筛选条件不涉及的,又要在用户界面展示的数据,建议存放在像oracle、mysql等关系型数据库中,用一个唯一标识做连接。你可以将图数据库和关系型数据库的这种搭配想象成宜家商场,楼上是展示区,帮助你确定购买方案,并记录在购物清单中;楼下是货架,形成完整购物清单后你可以去楼下按照清单中记载的货物存放地址提货。

____________更新_____________

近期使用neo4j的unsafe.batchinserter库写了个项目,用于从oracle向neo4j加载数据,踩了很多坑,都是由于想使用多线程机制,最后全部的努力宣告失败。结论是,不论是createNode方法还是createRelationship方法,再或者LuceneBatchInserterIndex的add方法,在多线程调用时都必须加synchronized。多线程机制只会保证create操作不会被数据准备的操作(包括遍历ResultSet,数据类型转换,label和type生成,关系端点id查找等)打断。至于原因,大家可以查看一下这篇博客(m.blog.csdn.net/huaishu)。插入点的操作都会涉及到property链表的首地址生成,因此要加锁;插入边的操作涉及到首末节点的首边地址检查和邻接边链表id搜索,同样也涉及到属性链表首地址,因此也要加锁。由这篇博客我们也能得知,加载的边涉及超级节点(顶点度很大的节点)时,由于边链表的遍历操作,效率会受到一定影响。个人觉得,neo4j的点存储还可以改进,在存储边链表首元素的同时,如果能存储末元素,那么就省去了插入边时的链表遍历操作,完美应付超级节点现象。然而可能存量数据导入本来也不太在乎时间窗口,和改变存储结构可能会出现的版本兼容问题,官方并未做出类似调整。

因此,用这个库做数据导入的性能极限,只是create的操作不间断进行的效率。我需要加载一个TB的数据,插入点的同时同步一个lucene索引,供插入边时查找首末节点id用。为什么我不用oracle内码直接做neo点id呢?因为我得oracle内码是12位自然数,而batchinserter创建节点的最大id是2的35次幂,我的id超范围了。

效率上,程序跑了一个星期,加载了650G,点的加载效率可以达到10M+/s,但到了边,速度越来越慢,以至于最后徘徊在1M/s上下,可能是前文提到的超级节点导致的。也想过用点其他方法,比方说neo4j-import或者Michale写的batch-import,但效果并不理想,看了源码才知道,原来也是串行加载。还有一篇博客提到并行加载(jexp.de/blog/2012/10/pa),但是并没有贴出项目链接。

综上,本人在加载小规模数据集时,用neo4j-import效果很好,但加载大规模数据时,性能受到串行机制和超级节点的影响,且目前尚未找到有限解决方案。

附上我的项目链接:github.com/bigbai0210/O

之前一直写python,第一次写java项目,如有疏漏,还望海涵,并不吝赐教。


如果有人尝试过加载几TB的数据,性能很好的话,烦请指教,小生感激万分。

____________更新_____________

另外,本人根据业务需求,运用递归策略,用neo4j java core api写了一个用于图谱扩展的cypher udf插件,并部署到服务器端,这样就可以用RESTful的方式调用这个插件,实现图遍历了。

随后,本人用python api driver写了一个自动化测试项目,用于定制化地对服务器进行随机请求轰炸,并将响应结果进行统计后生成一个行向量,保存在sqlite中。

项目还未做最后整理,这里只能说个大概性能。我的测试库中有近六百万点,两千万边,文件规模大概在900MB。请求经由十个线程随机发送,总发送频率大概在每秒1000次,生成的响应结果以json字符串形式返回到客户端测试机,结果集大小在200k到150MB之间,从请求送达到响应结果生成完毕的平均时间在40ms上下。由于我的图谱遍历插件中采取了熔断策略,即当结果集规模过大时,会停止遍历,并返回一个异常,因此最长响应时间被控制在500ms。这种性能指标完全可以迈入oltp的行列了。

这个测试项目的代码和sqlite数据库文件,以及本文提到的cypher插件的代码正在陆续整理,如果大家感兴趣,我可以之后将项目链接贴过来供大家参考。后续1TB数据的查询测试也在紧张进行中,敬请期待。

____________更新_____________

项目做了一些改变,并已于近期整理完毕,现进行最后一次更新。

由于模型变更,前期说好的1TB数据告吹了。为什么变更模型呢?还是测试之初没有明确neo4j在项目中的地位导致的。neo4j比较适合做图存储和基于模式匹配的子图查询,并不适合做大量与搜索无关的数据存储和IO工作。然而先前的测试将节点的很多与模式匹配毫无关联的属性抽象成属性节点,并与主体对象用一个类型为“has”的边相连接,这导致了数据导入和查询环节的系统开销不必要地上升了。因此,本次改动仅仅是将这些抽象出来的属性点和上述“has”关系从模型中除去而已。

测试基于linux redhat系统平台进行,硬件资源方面,2TB SSD,CPU为40核心E5处理器,256GB MEM。

测试按目标的不同分为三部分,分别为存量数据加载,增量数据加载,以及数据查询三个部分,以下分别给出测试结果。

存量数据加载:测试共进行了53小时,累计加载7.7亿节点,14亿边,加载得到的数据集中,除去lucene索引外,工有200GB数据。索引外的数据加载效率约合每小时4GB。其中,节点加载耗时6小时,总效率约合每小时1.3亿个;边加载耗时47小时,效率约合每小时3千万条。

增量数据加载:该操作是基于存量数据集的数据挂载,加载采用了load csv的方式。加载之前,先对存量数据创建了唯一性约束,创建过程耗时2万秒,产生了28G的neo4j索引。随后正式进行增量数据挂载。测试耗时23分钟,累计加载230万节点,440万边,两千六百万属性。数据库中产生了4GB的transaction文件,以及890MB数据库文件。总加载效率约合2.27GB每小时。其中,加载节点耗时4分钟,效率约合8900个每秒;加载边耗时19分钟,效率约合3800条每秒。

数据查询测试:本测试中的数据查询使用本人自己编写的neo4j的udf插件,作为cypher语言的扩展,udf之于neo4j就像sql存储函数之于mysql,我这样说应该容易理解一些。查询得到一个局部图谱,该图谱呗转化为json形式返回客户端。查询到的点和边的总数从1到3万不等,3万是人为规定的界限,目的是防止单笔查询耗时过长造成的线程假死现象。查询过程中,结果集规模超过3万时,udf会抛出异常,这样的查询日志被写在errorlog中,而顺利返回查询结果的查询被记录到querylog中。具体细节此处就不过多陈述了,直接看结果吧

查询时的系统资源使用情况如下图



查询进行了100万次,产生了95万条querylog和5万条errorlog。两者综合起来形成general,其中,结果集中的节点数超过2k的查询被单独统计。又上述结果可以看出,当结果集规模相对较小时,查询速度较快,基本能够达到oltp的要求,而当查询得到的结果集规模十分庞大时,消耗的时间相对较长。

注:

1.以上测试是根据具体业务需求进行的,不同的业务需求下,测试的结果会大相径庭,因此本次测试的结果仅供参考

2.存量数据加载方面本人只是运用了batchinserter库提供的部分包,并未进行更为广泛的测试,故测试结果有较大改进空间

3.本人进行查询前并未对服务器各方面的性能进行系统性调优,故查询测试结果有较大改进空间。

---------------新一轮更新---------------

原本以为搞到这里就差不多了,然而随着项目的进行,有发展更多可以做的事情,包括解偶,分布式计算,存储过程,apoc,展示等方方面面,这里打算随性更新一下。没准日后可以开个博客一一介绍。

先上个图



上图是本人编写的存储过程所反回的结果,存储过程借鉴了apoc.graph的实现思路。生成上图的语句是:
call relations.search(
'F_CERT', 100,
['control', 'serve', 'guarantee', 'mortgage', 'pledge', 'invest', 'relative']
) yield graph return *