fbpx

Neo4jの大量データインポート 2020 #neo4j

この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。

Neo4jは、CSVファイルから大量データのインポートを行います。
CSVファイルから大量データをインポートする方式は、LOAD CSVとneo4j-admin importの2つがあります。

ここでは、LOAD CSVとneo4j-admin importの使用法をNeo4j v4.0(2020-01-15リリース)をベースに紹介します。

LOAD CSVとneo4j-admin import

LOAD CSV

LOAD CSVは、CSVファイルからデータを取り込み、既存のデータベースに対して追加や更新、削除を行います。CSVから読み取ったデータをCypherに埋め込んでデータベースに投入しているために非常に柔軟な処理が可能です。

neo4j-admin import

neo4j-admin import(旧neo4j-import)は、CSVファイルからデータを取り込み、新規のデータベースを構築します。この方式では、ヘッダファイル、ノードファイル、リレーショナルファイルなどを別々に作成し、一回のインポートでデータベースファイルを生成します。LOAD CAVよりも数十倍以上高速です。

load csvとneo4j-admin importの比較

項目 load csv neo4j-admin import
データファイルフォーマット CSV CSV
データベースのステータス オンライン オフライン
対応データサイズ スモールサイズ ラージサイズ
パフォーマンス 低速 高速
データの追加 出来る(繰り返し実行可能) 出来ない(一過性)
データベースの初期化 不要 必要(データベースファイルが存在しては行けない)
CypherQL 必要 不要
CRUD CUD(作成 更新 削除) C(作成)
ユースケース 差分データの追加・更新・削除 初期データのマイグレーション

LOAD CSVの使用法

[参考]
https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/#query-load-csv

LOAD CSVは2種類の実行方法があります。

  • Neo4jブラウザーからの実行
  • cypher-shellからの実行(neo4j-shellはcypher-shellに統合)。

CSVファイルのフォーマット

LOAD CSV で規定しているCSVファイルのフォーマットは、次のとおりです。

  • UTF-8
  • 改行は\n(Linux)/\r\n(windows)
  • デフォルトのデリミタ―はコンマ(,)です
  • 任意のデリミタ―も利用可能です。例えば、「IELDTERMINATOR ';'」のように定義できます
  • 文字列はダブルクォテーション("string")で囲むのが原則です。例えば、数字を文字として扱う場合や文字列に引用符がついている場合などです。特にデータタイプを意識しなくても良い場合は、省略しても問題ありません。
  • 引用符付きの文字列が使用可能です。読み込むときに、引用符は自動的に外されます。「The "Symbol"」のような結果を期待するデータの読み込みは「"The ""Symbol"""」のように設定します。
  • エスケープ文字は「\」を使います。上記の属性値の場合は、「"The \"Symbol\""」ように表現できます
  • CSVファイルの中にヘッダ(フィールド名)を指定する場合は、1行目に指定する必要があります。CSVファイルの中にヘッダを指定せず、ヘッダだけを別ファイルに指定することや、構文のなかで属性名を定義し、CSVファイルのなかの属性値とマッピングさせることも可能です。

では、LOAD CSVの例を見てみましょう。

例文1: ヘッダ無しのタイプ

ヘッダーなしのCSVファイルからインポートする方法です。

[artists.csv]

1,ABBA,1992
2,Roxette,1986
3,Europe,1979
4,The Cardigans,1992

[クエリ]

LOAD CSV FROM 'https://neo4j.com/docs/cypher-manual/4.0/csv/artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInteger(line[2])})

  • ヘッダーが存在しないためにデータはリストとしてマッピングされます。

CSVファイルを${NEO4J_HOME}/import/artists.csvのように格納し、次のようにクエリを書くこともできます。

LOAD CSV FROM 'file:///artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInteger(line[2])})

[実行結果]

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

例文2:ヘッダ有りのタイプ

[artists-with-headers.csv]

Id,Name,Year
1,ABBA,1992
2,Roxette,1986
3,Europe,1979
4,The Cardigans,1992

[クエリ]

LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line
CREATE (:Artist { name: line.Name, year: toInt(line.Year)})

  • name: line.Nameは、「属性名:識別子.ヘッダ名」を意味します。

[実行結果]

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

例文3:デリミタ―をカスタマイズ

[artists-fieldterminator.csv]

"1";"ABBA";"1992"
"2";"Roxette";"1986"
"3";"Europe";"1979"
"4";"The Cardigans";"1992"

[クエリ]

LOAD CSV FROM 'file:////artists-fieldterminator.csv' AS line FIELDTERMINATOR ';'
CREATE (:Artist { name: line[1], year: toInteger(line[2])})

  • FIELDTERMINATOR ';'→コンマの変わりにセミコロンをデリミタ―で使っています

[実行結果]
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

例文4:トランザクション処理の行数制限

LOAD CSV文で大量のデータを取り込むときは、1回のトラザクションで処理するデータの行数を制限し、Javaのヒープメモリが溢れないようにします。デフォルトは、1000行です。

USING PERIODIC COMMIT [行数]
LOAD CSV FROM 'file:///artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInteger(line[2])})

例文5:エスケープ文字対応

[データファイル]
artists-with-escaped-char.csv

"1","The ""Symbol""","1992"

LOAD CSV FROM 'file:///artists-with-escaped-char.csv' AS line
CREATE (a:Artist { name: line[1], year: toInteger(line[2])})
RETURN a.name AS name, a.year AS year, size(a.name) AS size

neo4j-admin importの使用法

neo4j-admin importは常に新しいデータベースを生成します。

  • Neo4jサーバーのプロセスをストップする必要があります
  • 既存のデータベースに対するインポートはできません
  • 既存のデータベースのディレクトリを再利用したい場合は、データベースファイルを削除して下さい
  • 新規のデータベースを作成する場合は、実行オプションとして「--database=」のように指定します

CSVファイルのフォーマット

基本的な内容はLOAD CSVと同じですが、Cypherが使えないのであらかじめグラフ形式のデータである必要があります。

  • ノードファイルでは、データベース全体でユニークなIDを付与し、ラベルを指定する必要があります
  • リレーションシップファイルでは、始点ノードIDと終点ノードIDとリレーションシップのタイプを指定する必要があります

例文1:標準的なタイプ

ノードファイルとリレーションシップファイルで構成します。

[movies.csv]

movieId:ID,title,year:int,:LABEL
tt0133093,"The Matrix",1999,Movie
tt0234215,"The Matrix Reloaded",2003,Movie;Sequel
tt0242653,"The Matrix Revolutions",2003,Movie;Sequel

  • ヘッダ付きのノードのデータです
  • movieId:ID→movieIdフィールドはIDであるという定義であり、IDはデータベース全体で一意である必要があります。
  • year:int→yearフィールドは数字であるという定義です
  • :LABEL→このフィールドはラベルであるという定義です
  • Movie;Sequel→リスト表現であり、階層型のラベルであるという意味です(:Movie:Sequel)

[actors.csv]

personId:ID,name,:LABEL
keanu,"Keanu Reeves",Actor
laurence,"Laurence Fishburne",Actor
carrieanne,"Carrie-Anne Moss",Actor

[roles.csv]

:START_ID,role,:END_ID,:TYPE
keanu,"Neo",tt0133093,ACTED_IN
keanu,"Neo",tt0234215,ACTED_IN
keanu,"Neo",tt0242653,ACTED_IN
laurence,"Morpheus",tt0133093,ACTED_IN
laurence,"Morpheus",tt0234215,ACTED_IN
laurence,"Morpheus",tt0242653,ACTED_IN
carrieanne,"Trinity",tt0133093,ACTED_IN
carrieanne,"Trinity",tt0234215,ACTED_IN
carrieanne,"Trinity",tt0242653,ACTED_IN

  • ヘッダ付きのリレーションシップのデータです
  • :START_ID→このフィールドはグラフの始点ノードのIDであるという定義です
  • role→このフィールドは映画のなかの配役であり、リレーションシップの属性という定義です
  • :END_ID→このフィールドはグラフの終点ノードのIDであるという定義です
  • :TYPE→このフィールドは関係性のタイプであるという定義です

次のようにインポートを実行します。

bin/neo4j-admin import --nodes=import/movies.csv --nodes=import/actors.csv \
--relationships=import/roles.csv

例文2:オプション指定

デリミターの設定をセミコロン(;)にし、属性値はシングルクォーテーション(')、ラベルはバーティカルバー(|)に区切って配列にしています。

[movies2.csv]

movieId:ID;title;year:int;:LABEL
tt0133093;'The Matrix';1999;Movie
tt0234215;'The Matrix Reloaded';2003;Movie|Sequel
tt0242653;'The Matrix Revolutions';2003;Movie|Sequel

[actors2.csv]

personId:ID;name;:LABEL
keanu;'Keanu Reeves';Actor
laurence;'Laurence Fishburne';Actor
carrieanne;'Carrie-Anne Moss';Actor

[roles2.csv]

:START_ID;role;:END_ID;:TYPE
keanu;'Neo';tt0133093;ACTED_IN
keanu;'Neo';tt0234215;ACTED_IN
keanu;'Neo';tt0242653;ACTED_IN
laurence;'Morpheus';tt0133093;ACTED_IN
laurence;'Morpheus';tt0234215;ACTED_IN
laurence;'Morpheus';tt0242653;ACTED_IN
carrieanne;'Trinity';tt0133093;ACTED_IN
carrieanne;'Trinity';tt0234215;ACTED_IN
carrieanne;'Trinity';tt0242653;ACTED_IN

次のようにインポートを実行します。

bin/neo4j-admin import --nodes=import/movies2.csv --nodes=import/actors2.csv \
--relationships=import/roles2.csv --delimiter=";" --array-delimiter="|" --quote="'"

例文3:ヘッダを分離

次はヘッダを別ファイルしたケースです。

[movies3-header.csv]

movieId:ID,title,year:int,:LABEL

[movies3.csv]

tt0133093,"The Matrix",1999,Movie
tt0234215,"The Matrix Reloaded",2003,Movie;Sequel
tt0242653,"The Matrix Revolutions",2003,Movie;Sequel

[actors3-header.csv]

personId:ID,name,:LABEL

[actors3.csv]

keanu,"Keanu Reeves",Actor
laurence,"Laurence Fishburne",Actor
carrieanne,"Carrie-Anne Moss",Actor

[roles3-header.csv]

:START_ID,role,:END_ID,:TYPE

[roles3.csv]

keanu,"Neo",tt0133093,ACTED_IN
keanu,"Neo",tt0234215,ACTED_IN
keanu,"Neo",tt0242653,ACTED_IN
laurence,"Morpheus",tt0133093,ACTED_IN
laurence,"Morpheus",tt0234215,ACTED_IN
laurence,"Morpheus",tt0242653,ACTED_IN
carrieanne,"Trinity",tt0133093,ACTED_IN
carrieanne,"Trinity",tt0234215,ACTED_IN
carrieanne,"Trinity",tt0242653,ACTED_IN

次のようにインポートを実行します。

bin/neo4j-admin import --nodes=import/movies3-header.csv,import/movies3.csv \
--nodes=import/actors3-header.csv,import/actors3.csv \
--relationships=import/roles3-header.csv,import/roles3.csv

  • ヘッダファイルとデータファイルをコンマ区切りで設定しています

例文4: 複数のデータファイル

同ノードのファイル、リレーションシップのファイルが複数存在する場合もあります。

[movies4-header.csv]

movieId:ID,title,year:int,:LABEL

[movies4-part1.csv]

tt0133093,"The Matrix",1999,Movie
tt0234215,"The Matrix Reloaded",2003,Movie;Sequel

[movies4-part2.csv]

tt0242653,"The Matrix Revolutions",2003,Movie;Sequel

[actors4-header.csv]

personId:ID,name,:LABEL

[actors4-part1.csv]

keanu,"Keanu Reeves",Actor
laurence,"Laurence Fishburne",Actor

[actors4-part2.csv]

carrieanne,"Carrie-Anne Moss",Actor

[roles4-header.csv]

:START_ID,role,:END_ID,:TYPE

[roles4-part1.csv]

keanu,"Neo",tt0133093,ACTED_IN
keanu,"Neo",tt0234215,ACTED_IN
keanu,"Neo",tt0242653,ACTED_IN
laurence,"Morpheus",tt0133093,ACTED_IN
laurence,"Morpheus",tt0234215,ACTED_IN

[roles4-part2.csv]

laurence,"Morpheus",tt0242653,ACTED_IN
carrieanne,"Trinity",tt0133093,ACTED_IN
carrieanne,"Trinity",tt0234215,ACTED_IN
carrieanne,"Trinity",tt0242653,ACTED_IN

次のようにインポートを実行します。

bin/neo4j-admin import --nodes=import/movies4-header.csv,import/movies4-part1.csv,import/movies4-part2.csv \
--nodes=import/actors4-header.csv,import/actors4-part1.csv,import/actors4-part2.csv \
--relationships=import/roles4-header.csv,import/roles4-part1.csv,import/roles4-part2.csv

  • コンマ区切りでファイルを並べます

例文5:正規表現を使ったファイル指定

次のように「例文4」のような構文のファイル名を正規表現でシンプルに書き換えることが出来ます。

bin/neo4j-admin import --nodes=import/movies4-header.csv,import/movies4-part.* \
--nodes=import/actors4-header.csv,import/actors4-part.* \
--relationships=import/roles4-header.csv,import/roles4-part.*

例文6:ノードのシンプルラベル設定

ノードファイルとラベルが1:1の関係である場合、ファイルのなかにはラベルを持たず、インポート文に設定することが出来ます。

[movies5a.csv]

movieId:ID,title,year:int
tt0133093,"The Matrix",1999

[sequels5a.csv]

movieId:ID,title,year:int
tt0234215,"The Matrix Reloaded",2003
tt0242653,"The Matrix Revolutions",2003

[actors5a.csv]

personId:ID,name
keanu,"Keanu Reeves"
laurence,"Laurence Fishburne"
carrieanne,"Carrie-Anne Moss"

[roles5a.csv]

:START_ID,role,:END_ID,:TYPE
keanu,"Neo",tt0133093,ACTED_IN
keanu,"Neo",tt0234215,ACTED_IN
keanu,"Neo",tt0242653,ACTED_IN
laurence,"Morpheus",tt0133093,ACTED_IN
laurence,"Morpheus",tt0234215,ACTED_IN
laurence,"Morpheus",tt0242653,ACTED_IN
carrieanne,"Trinity",tt0133093,ACTED_IN
carrieanne,"Trinity",tt0234215,ACTED_IN
carrieanne,"Trinity",tt0242653,ACTED_IN

次のようにインポートを実行します。

bin/neo4j-admin import --nodes=Movie=import/movies5a.csv \
--nodes=Movie:Sequel=import/sequels5a.csv \
--nodes=Actor=import/actors5a.csv \
--relationships=import/roles5a.csv

  • --nodes=Movie=import/movies5a.csvのようにファイル名の前にラベルを設定しています(=Movie=)

例文7:リレーションシップのシンプルタイプ指定

「例文6」のようなやり方はリレーションシップにも適用できます。

[movies5b.csv]

movieId:ID,title,year:int,:LABEL
tt0133093,"The Matrix",1999,Movie
tt0234215,"The Matrix Reloaded",2003,Movie;Sequel
tt0242653,"The Matrix Revolutions",2003,Movie;Sequel

[actors5b.csv]

personId:ID,name,:LABEL
keanu,"Keanu Reeves",Actor
laurence,"Laurence Fishburne",Actor
carrieanne,"Carrie-Anne Moss",Actor

[roles5b.csv]

:START_ID,role,:END_ID
keanu,"Neo",tt0133093
keanu,"Neo",tt0234215
keanu,"Neo",tt0242653
laurence,"Morpheus",tt0133093
laurence,"Morpheus",tt0234215
laurence,"Morpheus",tt0242653
carrieanne,"Trinity",tt0133093
carrieanne,"Trinity",tt0234215
carrieanne,"Trinity",tt0242653

次のようにインポートを実行します。

neo4j_home$ bin/neo4j-admin import --nodes=import/movies5b.csv --nodes=import/actors5b.csv \
 --relationships=ACTED_IN=import/roles5b.csv

  • --relationships=ACTED_IN=import/roles5b.csvのようにファイル名の前にタイプを設定しています(=ACTED_IN=)

例文8:属性のデータタイプ指定

多様な属性のデータタイプをヘッダに指定できます。

[movies6.csv]

movieId:ID,title,year:int,:LABEL
tt0099892,"Joe Versus the Volcano",1990,Movie

[actors6.csv]

personId:ID,name,:LABEL
meg,"Meg Ryan",Actor

[roles6.csv]

[:START_ID,roles:string[],:END_ID,:TYPE]
meg,"DeDe;Angelica Graynamore;Patricia Graynamore",tt0099892,ACTED_IN

bin/neo4j-admin import --nodes=import/movies6.csv --nodes=import/actors6.csv \
--relationships=import/roles6.csv

例文9:ID操作の例外

基本的にノードIDはデータベース全体で一意性を持つべきですが、同ノードファイルの中で一意であればいい場合もあります。
ノードファイルが一意のシーケンシャル番号、自動インクメンタルなどで生成された場合です。

[movies7.csv]

movieId:ID(Movie-ID),title,year:int,:LABEL
1,"The Matrix",1999,Movie
2,"The Matrix Reloaded",2003,Movie;Sequel
3,"The Matrix Revolutions",2003,Movie;Sequel

  • IDにMovie-IDのように識別子を指定します

[actors7.csv]

personId:ID(Actor-ID),name,:LABEL
1,"Keanu Reeves",Actor
2,"Laurence Fishburne",Actor
3,"Carrie-Anne Moss",Actor

roles7.csv

:START_ID(Actor-ID),role,:END_ID(Movie-ID)
1,"Neo",1
1,"Neo",2
1,"Neo",3
2,"Morpheus",1
2,"Morpheus",2
2,"Morpheus",3
3,"Trinity",1
3,"Trinity",2
3,"Trinity",3

  • START_ID(Actor-ID)とEND_ID(Movie-ID)のように設定します

bin/neo4j-admin import --nodes=import/movies7.csv --nodes=import/actors7.csv \
--relationships=ACTED_IN=import/roles7.csv

neo4j-admin importのオプション

neo4j-admin importには、データを正確にインポートするためのオプションが多数あります。
こちらを参照してください。

[参考]
https://neo4j.com/docs/operations-manual/4.0/tools/import/options/

neo4.confの設定

大量データをインポートする時は、メモリ割り当てが気になります。
neo4j-admin memrecは、該当マシンのリソースをチェックし、初期値を割り出してくれます。

bin/neo4j-admin memrec
…
Based on the above, the following memory settings are recommended:
dbms.memory.heap.initial_size=5100m
dbms.memory.heap.max_size=5100m
dbms.memory.pagecache.size=2900m
dbms.tx_state.max_off_heap_memory=4000m
…

次の3つをconf/neo4j.confに設定します。
dbms.tx_state.max_off_heap_memoryは、トランザクションの状態を管理するoff-heapメモリでDefaultで2Gもありますので無視して下さい。

dbms.memory.heap.initial_size=5100m
dbms.memory.heap.max_size=5100m
dbms.memory.pagecache.size=2900m

以上、Neo4jの大量データインポートでした。

Author

モダンアーキテクチャー基盤のソリューションアーキテクトとして活動しています。

[著書]
・Amazon Cloudテクニカルガイド―EC2/S3からVPCまで徹底解析
・Amazon Elastic MapReduceテクニカルガイド ―クラウド型Hadoopで実現する大規模分散処理
・Cypherクエリー言語の事例で学ぶグラフデータベースNeo4j
・Neo4jを使うグラフ型データベース入門(共著)
・RDB技術者のためのNoSQLガイド(共著)

leeの記事一覧

新規CTA