看了好幾篇介紹刪除海量節點優化的文章,就數這篇寫的詳細。
有這麼一個場景:當我們做實驗時,經常需要刪除數據庫的所有數據然後重新開始。這個操作聽起來簡單,但實際做的時候可不像想的那麼簡單,本文記錄下我的一些經驗教訓供大家參考。
我是通過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 >
哦~, 內存溢出了!爲什麼內存會溢出呢?最好的辦法是先把堆存儲打印出來,確認一下。
我們可能通過修改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, operationsRETURN timeTaken, operations
通過下圖可以看出來,有時間是可以正常運行的,但有時他仍會佔滿整個堆內存,造成垃圾回收暫停。
通過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看內存堆的內容,可以看到如下:
這次佔用空間不是命令了,而是我們要刪除的那些節點。 爲了避免所有結點都加載到內存中,我們可以使用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/