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按钮,点击后才可以看到上面的标签 

构造数据

使用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 database.

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

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

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

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

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

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

批处理删除

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

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

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

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

$ grep VmPauseMonitorComponent logs/debug.log | tail -n 10
2019-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看内存堆的内容,可以看到如下: 

这次占用空间不是命令了,而是我们要删除的那些节点。 为了避免所有结点都加载到内存中,我们可以使用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,这下所有结点都被顺利删除了。我们可以继续干其他事了。

原文链接:https://markhneedham.com/blog/2019/04/14/neo4j-delete-all-nodes/

你可能感兴趣的:(Neo4j中使用Cypher进行大批量节点删除的优化)