Neo4j中使用Cypher进行大批量节点删除的优化。

看了好几篇介绍删除海量节点优化的文章,就数这篇写的详细。

有这么一个场景:当我们做实验时,经常需要删除数据库的所有数据然后重新开始。这个操作听起来简单,但实际做的时候可不像想的那么简单,本文记录下我的一些经验教训供大家参考。

我是通过Neo4j Desktop的默认配置来操作Neo4j数据库的,这意味着内存堆最大值为1G。

本文假设已经安装了Neo4j APOC库,如果没有安装,可以看这里(https://neo4j-contrib.github.io/neo4j-apoc-procedures/#_installation_with_neo4j_desktop)。

Cypher Shell

本文都是在Cypher Shell中执行Cypher请求。具体位置见Neo4j Desktop下面的截图 

译者言: 这里如果想看到这个界面,在使用Neo4j Desktop时,创建数据库时必须选择“Create a Local Graph”,然后“Start”后,再才看到Manage按钮,点击后才可以看到上面的标签 

640.png

构造数据

使用APOC库的apoc.periodic.iterate方法创建100百万个节点的图。

neo4j> CALL apoc.periodic.iterate(         "UNWIND range(1, 1000000) as id RETURN id",         "CREATE (:Node {id: id})",         {}       )       YIELD timeTaken, operations       RETURN timeTaken, operations;+-------------------------------------------------------------------------+| timeTaken | operations                                                  |+-------------------------------------------------------------------------+| 8         | {total: 1000000, committed: 1000000, failed: 0, errors: {}} |+-------------------------------------------------------------------------+1 row available after 8249 ms, consumed after another 0 ms

运行完上面的语句后,我们来看一下现在图中有多少个节点:

neo4j> MATCH () RETURN count(*);+----------+| count(*) |+----------+| 1000000  |+----------+1 row available after 0 ms, consumed after another 0 ms

得了,100百万节点创建成功了,接下来我们要删除它们了。

删除结点

第一次删除这些节点是使用下面的查询语句,先找到,再删除。

neo4j> MATCH (n)       DETACH DELETE n;There is not enough memory to perform the current task. Please try increasing "dbms.memory.heap.max_size" in the neo4j configuration (normally in "conf/neo4j.conf" or, if you you are using Neo4j Desktop, found through the user interface) or if you are running an embedded installation increase the heap by using "-Xmx" command line flag, and then restart the >

哦~, 内存溢出了!为什么内存会溢出呢?最好的办法是先把堆存储打印出来,确认一下。

我们可能通过修改Neo4j的配置,使其在内存溢出时将内存内容转储成文件。配置如下:

dbms.jvm.additional=-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/neo4jdump.hprof

如果是使用Neo4j Desktop的,可以找到其Settings标签页进行设置。

640.png

如果我们要看转储出来的内容,就需要使用YourKit或VisualVM这类工具。我使用的是VisualVM, 下图为堆内容的截图:

640.png

 我们的写查询会产生大量的日志,而这些日志需要用命令进行保存,而堆中的大部分空间都被这此保存命令所占用。

批处理删除

如果我们成批的删除节点,这样在内存中就不会有那么多的命令了,这是个好办法。而apoc.periodic.iterate正是用于批处理执行语句的,我们试一下:

CALL apoc.periodic.iterate(  "MATCH (n) RETURN n",  "DELETE n",  {batchSize: 10000})YIELD timeTaken, operationsRETURN timeTaken, operations

通过下图可以看出来,有时间是可以正常运行的,但有时他仍会占满整个堆内存,造成垃圾回收暂停。

640.png

 通过Neo4j Desktop上的Terminal标签 可以看到debug日志,通过搜索这个日志可以看到所有的垃圾回收器暂停的信息。

$ grep VmPauseMonitorComponent logs/debug.log | tail -n 102019-04-14 16:14:22.377+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=9143, gcTime=4619, gcCount=7}2019-04-14 16:14:28.845+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=6367, gcTime=6451, gcCount=10}2019-04-14 16:14:35.730+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=2131, gcTime=6875, gcCount=12}2019-04-14 16:14:44.455+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=9080, gcTime=4523, gcCount=5}2019-04-14 16:14:46.721+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=6364, gcTime=6449, gcCount=18}2019-04-14 16:15:09.106+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=19938, gcTime=22355, gcCount=28}2019-04-14 16:15:13.288+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=6428, gcTime=4176, gcCount=7}2019-04-14 16:15:17.807+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=4418, gcTime=4515, gcCount=5}2019-04-14 16:16:00.108+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=19724, gcTime=42279, gcCount=40}2019-04-14 16:16:00.209+0000 WARN [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=22476, gcTime=10, gcCount=1}

此时使用VisualVM看内存堆的内容,可以看到如下: 

640.png

这次占用空间不是命令了,而是我们要删除的那些节点。 为了避免所有结点都加载到内存中,我们可以使用apoc.periodic.commit 代替apoc.periodic.iterate。而apoc.periodic.commit所需要的查询语句必须带有LIMIT子句,同时还需要包含一个RETURN子句,只要有返回结果,他就会持续迭代下去。

neo4j> CALL apoc.periodic.commit(         "MATCH (n) WITH n LIMIT $limit DELETE n RETURN count(*)",         {limit: 10000}       )       YIELD updates, executions, runtime, batches       RETURN updates, executions, runtime, batches;+------------------------------------------+| updates | executions | runtime | batches |+------------------------------------------+| 1000000 | 100        | 7       | 101     |+------------------------------------------+1 row available after 7540 ms, consumed after another 0 ms

OK,这下所有结点都被顺利删除了。我们可以继续干其他事了。

品略图书馆 http://www.pinlue.com/

猜你喜欢

转载自blog.51cto.com/14325182/2401356
今日推荐