Neo4j批量导入数据的几种方式

Neo4j是一个比较新的数据库,ETL工具较少,公司一个项目需要导入上百亿数据,想找一个最合适的方案来导入数据。
于是就想测测各种导入方式的效率以及成本

(1) 常见数据插入方式概览

目前主要有以下几种数据插入方式:

  1. Cypher create 语句,为每一条数据写一个create
  2. Cypher load csv 语句,将数据转成CSV格式,通过LOAD CSV读取数据。
  3. 官方提供的neo4j-import工具,未来将被neo4j-admin import代替
  4. 官方提供的Java API - BatchInserter
  5. 大牛编写的 batch-import 工具
  6. neo4j-apoc  load.csv + apoc.load.relationship
  7. 针对实际业务场景,定制化开发

这些工具有什么不同呢?速度如何?适用的场景分别是什么?

just try create语句 load csv语句 neo4j-import BatchInserter batch-import apoc
适用场景 1 ~ 1w 0 ~ 1000w 千万以上 千万以上 千万以上 1 ~ 数千万
速度 很慢 1000/s 一般 5000/s 非常快 x w/s 很快 x w/s 很快x w/s 1w /s
实际测试 9.5k/s(节点+关系)
用到了merge,数据量越大,速度越慢
12w/s(节点+关系) 1w/s(节点+关系) 1w/s(节点+关系) 4k/s(1亿数据上增量更新)
1w/s(百万数据上更新)
用到了merge,数据量越大,速度越慢
优点 1.使用方便
2.可实时插入
1.官方ETL工具
2.可以加载本地/远程CSV
3.可实时插入
1.官方工具
2.占用资源少
1.官方API 1.可以增量更新
2.基于BatchInserter
1.官方ETL工具
2.可以增量更新
3.支持在线导入
4.支持动态传Label RelationShip
缺点 1.速度慢
2.处理数据,拼CQL复杂,很少使用
1.导入速度较慢
2.只能导入节点
3.不能动态传Label RelationShip
1.需要脱机导入 停止Neo4j数据库
2.只能用于初始化导入
1.只能在JAVA中使用
2.需要脱机导入 停止Neo4j数据库
1.需要脱机导入 停止Neo4j数据库 1.速度一般

(2) 各种导入数据方法测试

(2.1) create语句

在学习neo4j的时候会用到create,在批量导入数据的时候,一般很少用create
未测
./neo4j-shell -c < /data/stale/data01/neo4j/neo4j_script.cypther

./bin/neo4j-shell -path ./data/databases/graph.db -conf ./conf/neo4j.conf -file create_index.cypther

(2.2) load csv 语句

服务器配置 CPU 32核,256G内存,10T机械硬盘

数据格式

uuid,name,Label
b6b0ea842890425588d4d3cfb38139a9,"文烁",Label1
5099c4f943d94fa1873165e3f6f3c2fb,"齐贺喜",Label3
c83ed0ae9fb34baa956a42ecf99c8f6e,"李雄",Label2
e62d1142937f4de994854fa1b3f0670a,"房玄龄",Label

下面是详细测试结果

(2.1) 导入10w数据(仅节点)

neo4j-sh (?)$ using periodic commit 10000 load csv with headers from "file:/data/stale/data01/neo4j/node_uuid_10w.csv" as line with line create (:Test {uuid:line.uuid, name:line.name});
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 100000
Properties set: 200000
Labels added: 100000
3412 ms

10万数据(只有节点,没有关系)导入用时3.412s

(2.2) 导入1kw数据(仅节点)

/data/stale/data01/neo4j/node_uuid_1kw.csv文件加上标题一共10000010条,有10000009条数据

neo4j-sh (?)$ load csv from "file:/data/stale/data01/neo4j/node_uuid_1kw.csv" as line return count(*);
+----------+
| count(*) |
+----------+
| 10000010 |
+----------+
1 row
7434 ms
neo4j-sh (?)$ using periodic commit 10000 load csv with headers from "file:/data/stale/data01/neo4j/node_uuid_1kw.csv" as line with line create (:Test {uuid:line.uuid, name:line.name});
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 10000009
Properties set: 20000018
Labels added: 10000009
151498 ms

1千万数据(只有节点,没有关系)导入用时151.498s
导入时CPU利用率在150%左右,RES Memory 5G左右,VIRT Memory 70G左右

(2.3) 导入100w数据(仅关系)

neo4j-sh (?)$ match (n) return count(n);
+----------+
| count(n) |
+----------+
| 18004002 |
+----------+
1 row
12 ms
neo4j-sh (?)$ match ()-->() return count(*);
+----------+
| count(*) |
+----------+
| 15001999 |
+----------+
1 row
18 ms
neo4j-sh (?)$ using periodic commit 100000 load csv with headers from "file:/data/stale/data01/neo4j/relathionship_uuid_100w.csv" as line with line merge (n1:Test {uuid:line.uuid1}) merge (n2:Test {uuid:line.uuid2}) with * create (n1)-[r:Relationship]->(n2);
+-------------------+
| No data returned. |
+-------------------+
Relationships created: 1000000
75737 ms
neo4j-sh (?)$ match (n) return count(n);
+----------+
| count(n) |
+----------+
| 18004002 |
+----------+
1 row
6 ms
neo4j-sh (?)$ match ()-->() return count(*);
+----------+
| count(*) |
+----------+
| 16001999 |
+----------+

创建100w关系用时75.737s
因为我节点已经提前导入了,所以merge的时候节点全部存在,根据结果可以看到,只创建了100w关系,没有创建节点
但是这种方式有一个弊端,关系要写死,在只有一种关系时试用,在有多种关系时,不适用。还有一个不好的地方就是用的merge,uuid是String类型,会随着数据的正常速度变慢。

load csv 的速度我用的是 导入节点时间+导入关系时间
导入100w 节点+数据 (Label写死,RelationShip写死,也就是只有一种Label和一种RelationShip) 共花费15.149 + 75.737 = 90.886 。load csv的速度大概在1.1w/s,但这种情况一般很少使用,仅供参考。

(2.3) neo4j-import (在以后版本会被neo4j-admin import替掉)

服务器配置 CPU 32核,256G内存,10T机械硬盘
空库初始化导入1千万数据(1kw节点 1kw关系 2kw属性,id使用integer,属性中只有数字和英文)花费27s 932ms
空库初始化导入1千万数据(1kw节点 1kw关系 2kw属性,包含中文属性)花费1min 50s 9ms
空库初始化导入1.1亿数据(1.1亿节点 1.1关系 2.2亿属性)花费15min 9s 37ms

数据格式

node.csv

uuid:ID(users),name:String,:Label
c63bc1e7dc594fd49fbe36dd664ff0a6,"维特",Label1
b52fb5f2266b4edbadc82b5ec4c430b8,"廖二松",Label2
d95d430cfeee47dd95f9bf5e0ec1ae93,"徐青偏",Label3
b2d1fffc8173461fa603d4fbb601b3ee,"杨础维",Label2

relationship.csv

uuid:START_ID(users),uuid:END_ID(users),:TYPE
c63bc1e7dc594fd49fbe36dd664ff0a6,b2d1fffc8173461fa603d4fbb601b3ee,RelationShip1
d95d430cfeee47dd95f9bf5e0ec1ae93,c63bc1e7dc594fd49fbe36dd664ff0a6,RelationShip2
b2d1fffc8173461fa603d4fbb601b3ee,b52fb5f2266b4edbadc82b5ec4c430b8,RelationShip3
b52fb5f2266b4edbadc82b5ec4c430b8,d95d430cfeee47dd95f9bf5e0ec1ae93,RelationShip1

部分详细日志如下

(2.3.1) 导入1000w数据(1kw节点 1kw关系 2kw属性,id使用integer,属性中只有数字和英文)

空库初始化导入1千万数据(1kw节点 1kw关系 2kw属性,id使用integer,属性中只有数字和英文)花费27s 932ms
部分日志如下:

Done in 779ms

IMPORT DONE in 27s 932ms.
Imported:
  10000000 nodes
  10000000 relationships
  20000000 properties
Peak memory usage: 209.81 MB

(2.3.2) 导入1000w数据(1kw节点 1kw关系 2kw属性,包含中文属性)

空库初始化导入1千万数据(1kw节点 1kw关系 2kw属性,包含中文属性)花费1min 50s 9ms
部分日志如下:

IMPORT DONE in 1m 50s 9ms.
Imported:
  10000000 nodes
  10000000 relationships
  20000000 properties
Peak memory usage: 209.81 MB

(2.3.3) 导入1.1亿数据(1.1亿节点 1.1关系 2.2亿属性)

空库初始化导入1.1亿数据(1.1亿节点 1.1关系 2.2亿属性)花费15min 9s 37ms
部分日志如下:

IMPORT DONE in 15m 9s 37ms.
Imported:
  110000010 nodes
  110000000 relationships
  220000020 properties
Peak memory usage: 2.27 GB
There were bad entries which were skipped and logged into /data/stale/data01/neo4j/neo4j-community-3.1.0/data/databases/test_uuid_1y_graph.db/bad.log

(2.4) BatchInserter

batch-import调的BatchInserter的代码,所以BatchInserter没测,可以认为BatchInster和batch-import速度一样

(2.5) batch-import

在数据库中已有1kw数据的情况下,导入100w数据(100w节点 100w关系 200w属性,包含中文属性)花费92s

node.csv

uuid:string:users,name:String,:label    #官方的文件头
uuid:ID(users),name:String,:Label	#修改程序后的文件头
c63bc1e7dc594fd49fbe36dd664ff0a6,"维特",Label1
b52fb5f2266b4edbadc82b5ec4c430b8,"廖二松",Label2
d95d430cfeee47dd95f9bf5e0ec1ae93,"徐青偏",Label3
b2d1fffc8173461fa603d4fbb601b3ee,"杨础维",Label2

relationship.csv

uuid:string:users,uuid:string:users,type    #官方的文件头
uuid:START_ID(users),uuid:END_ID(users),:TYPE	#修改程序后的文件头
c63bc1e7dc594fd49fbe36dd664ff0a6,b2d1fffc8173461fa603d4fbb601b3ee,RelationShip1
d95d430cfeee47dd95f9bf5e0ec1ae93,c63bc1e7dc594fd49fbe36dd664ff0a6,RelationShip2
b2d1fffc8173461fa603d4fbb601b3ee,b52fb5f2266b4edbadc82b5ec4c430b8,RelationShip3
b52fb5f2266b4edbadc82b5ec4c430b8,d95d430cfeee47dd95f9bf5e0ec1ae93,RelationShip1
Using: Importer batch.properties /data/stale/data01/neo4j/neo4j-community-3.1.0/data/databases/test_uuid_1000w_graph.db /data/stale/data01/neo4j/node_uuid_100w.csv /data/stale/data01/neo4j/relationship_uuid_100w.csv

Using Existing Configuration File
..........
Importing 1000000 Nodes took 15 seconds
..........
Importing 1000000 Relationships took 16 seconds

Total import time: 92 seconds

(6) apoc

load csv + merge + apoc.create.relationship

neo4j-sh (?)$ using periodic commit 1000000
> load csv from 'file:/data/stale/data01/neo4j/relathionship_uuid_1kw.csv' as line fieldterminator ','
> merge (n1:Test {uuid: line[0]})
> merge (n2:Test {uuid: line[1]})
> with n1, n2, line
> CALL apoc.create.relationship(n1, line[2], {}, n2) YIELD rel
> return count(rel) ;
+------------+
| count(rel) |
+------------+
| 10000010   |
+------------+
1 row
Nodes created: 8645143
Properties set: 8645143
Labels added: 8645143
2395852 ms

在1.1亿数据上增量更新1kw数据花费2395.852s
VIRT Memory 90G RES Memory 78G

(3) 结论

根据实际情况选用最好的方式

  1. neo4j-import导入速度快,但是要求是空库,导入时要停止neo4j,也就是脱机导入,而且你要提前处理好数据,数据最好不要有重复,如果有重复,可以导入时跳过,然后根据bad.log来查看或者修正这部分数据
  2. batch-import可以增量导入,但是要求导入时停止neo4j数据库(脱机导入),而且增量更新的数据不会和库里存在的数据对比,所以要求数据全是新的,否则会出现重复数据
  3. load csv比较通用,而且可以在neo4j数据库运行时导入,但是导入速度相对较慢,要提前整理好数据,而且不能动态创建 Label RelationShip
  4. apoc挺好用的,可以动态创建RelationShip,但是不能动态创建Label (动态创建Label只能在程序里通过拼接字符串的方法实现)

实际情况中,处理数据比导入数据更花费时间

Neo4j的查询速度为何这么慢?这能商用吗? 这篇文章中 HackerWhite 写的那部分特别好,可以参考。

References

[1] guide-importing-data-and-etl
[2] guide-import-csv
[3] load-csv
[4] import
[5] how-to-insert-bulk-data-into-neo4j
[6] Neo4j的查询速度为何这么慢?这能商用吗?
[7] 使用batch-import工具向neo4j中导入海量数据
[8] 如何快速导入网络数据到图数据库Neo4j
[9] bulk-data-import-neo4j-3-0
[10] neo4j-etl github
[11] neo4j google group
[12] import-tool
[13] Neo4j在并发的load CSV文件的时候出现deadlock问题。怎么有效处理提高并发呢?