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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章