GraphDBを学ぶ（４）neo4jにTPC-Hのテーブルを登録(＋バックアップ・リストア)

# TL;DR

* 前回までの学びを活用して，RDBMSのデータベースを，neo4jに取り込む手順を紹介します
    * MariaDBに格納したRDBMSベンチマークTPC-Hのテーブルを，neo4j用に変換して取り込みます
    * TPC-Hに限らず，考え方は他のRDBMSのデータベース構造にも応用できます

# neo4jコンテナの起動

* この記事の手順は，メモリをたくさん消費したり，ulimit制限に引っかかったりします
* そのため，以下のようにneo4jのコンテナにいくつかオプションを付与して，問題を回避します

```bash
docker run -d \
  --ulimit nofile=64000 \
  -p 7474:7474 -p 7687:7687 \
  -v ${PWD}/neo4j/data:/var/lib/neo4j/data \
  -v ${PWD}/neo4j/import:/var/lib/neo4j/import \
  -e NEO4J_dbms_memory_pagecache_size=6G \
  -e NEO4J_dbms_memory_heap_max__size=6G \
neo4j
```

|オプション|説明|
|:---|:---|
| --ulimit nofile=64000 | ulimitサイズを64000にする |
| -v ${PWD}/neo4j/data:/var/lib/neo4j/data | neo4jのデータベースの不揮発ボリューム |
| -v ${PWD}/neo4j/import:/var/lib/neo4j/import | インポートするcsvを置くディレクトリ |
| -e NEO4J_dbms_memory_pagecache_size=6G | ページキャッシュサイズを6GBにする |
| -e NEO4J_dbms_memory_heap_max__size=6G | ヒープメモリサイズを6GBにする |

# 事前準備

* [データベースベンチマークTPC-HをMySQLで実行する(TPC-H v2.18.0)](https://qiita.com/myoshimi/items/bd460164af48ece1ab2d)を参考に， **sf=0.01** のサイズでデータベースを作成し，mariadbに登録しておきます
    * sf=1でやったらデータの登録が終わらなくなったので，この記事では小さい(sf=0.01)データを扱います

# テーブル間関係の確認

* TPC-Hのテーブル間の関連を確認します
    * 矢印がhas-manyの関連です
    * テーブル間の文字列は，関係を表す名前（自由に決められるもの）です

![tpch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/0ef449bd-2ac3-5cbe-930a-b575fd934568.png)

# グラフノードの登録

* 以下の手順でNeo4jにグラフを作ります

1. MariaDBからテーブルのcsvファイルを作成する
2. Neo4jに，テーブル＝ラベル，行＝ノードとして，外部キー以外のエントリを書き込む
3. Neo4jに外部キーの接続関係を登録する

## 1. MariaDBからテーブルの抜き出し

* TPC-Hは8つのテーブルから成るので，それぞれをcsvファイルで取り出します
    * tpch-mysqlのtblファイルをそのまま使っても良いですが，コマンドが長くなるので，一度csvファイルに書き出すことにしました

```bash
for i in region nation customer supplier partsupp part lineitem orders
do
mysql -h 127.0.0.1 \
    -u <MySQLユーザ> -p<MySQLユーザのパスワード> \
    -e "select * from sf001.${i} into outfile '/tmp/${i}.csv' fields terminated by ',' optionally enclosed by '\"';";
done
```

* ```/tmp/```に各テーブルがcsvとして書き出されます
    * データベース名が```sf001```でない場合は，適切に修正してください

## 2. Neo4jにcsvファイルをインポートする

RDBMSのテーブル=GraphDBのラベル，エントリ=ノードとして，csvファイルをNeo4jに書き込みます．

* ```neo4j-client```コマンドを使用して```LOAD CSV```句を使用します
    * このとき，主キーは登録しますが，外部キーは登録しません
    * 外部キーは次節の「関係」で表現します
    * ```USING PERIODIC COMMIT```句を追加して，コミットを頻度を落とし，メモリ節約をしています

```sql
neo4j>
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/region.csv' as line
  CREATE(:region{R_REGIONKEY:toInteger(line[0]),
                 R_NAME: line[1],
                 R_COMMENT: line[2]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/nation.csv' as line 
  CREATE(:nation{N_NATIONKEY:toInteger(line[0]),
                 N_NAME: line[1],
                 N_COMMENT: line[3]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/customer.csv' as line
  CREATE(:customer{C_CUSTKEY:toInt(line[0]),
                   C_NAME:line[1],
                   C_ADDRESS:line[2],
                   C_PHONE:line[4],
                   C_ACCTBAL:toFloat(line[5]),
                   C_MKTSEGMENT:line[6],
                   C_COMMENT:line[7]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/supplier.csv' as line
  CREATE(:supplier{S_SUPPKEY:toInteger(line[0]), 
                   S_NAME:line[1],
                   S_ADDRESS:line[2],
                   S_PHONE:line[4],
                   S_ACCTBAL:toFloat(line[5]),
                   S_COMMENT:line[6]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/part.csv' as line
  CREATE(:part{P_PARTKEY:toInteger(line[0]),
               P_NAME:line[1],
               P_MFGR:line[2],
               P_BRAND:line[3],
               P_TYPE:line[4],
               P_SIZE:toInteger(line[5]),
               P_CONTAINER:line[6],
               P_RETAILPRICE:toFloat(line[7]),
               P_COMMENT:line[8]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/partsupp.csv' as line
  CREATE(:partsupp{PS_PARTKEY:toInteger(line[0]),
                   PS_SUPPKEY:toInteger(line[1]),
                   PS_AVAILQTY:toInteger(line[2]),
                   PS_SUPPLYCOST:toFloat(line[3]),
                   PS_COMMENT:line[4]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/orders.csv' as line
  CREATE(:orders{O_ORDERKEY:toInteger(line[0]),
                 O_ORDERSTATUS:line[2],
                 O_TOTALPRICE:toFloat(line[3]),
                 O_ORDERDATE:date(line[4]),
                 O_ORDERPRIORITY:line[5],
                 O_CLERK:line[6],
                 O_SHIPPRIORITY:toInteger(line[7]),
                 O_COMMENT:line[8]});
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/lineitem.csv' as line
  CREATE(:lineitem{L_ORDERKEY:toInteger(line[0]),
                   L_LINENUMBER:toInteger(line[3]),
                   L_QUANTITY:toFloat(line[4]),
                   L_EXTENDEDPRICE:toFloat(line[5]),
                   L_DISCOUNT:toFloat(line[6]),
                   L_TAX:toFloat(line[7]),
                   L_RETURNFLAG:line[8],
                   L_LINESTATUS:line[9],
                   L_SHIPDATE:date(line[10]),
                   L_COMMITDATE:date(line[11]),
                   L_RECEIPTDATE:date(line[12]),
                   L_SHIPINSTRUCT:line[13],
                   L_SHIPMODE:line[14],
                   L_COMMENT:line[15]});
```

* lineitem.csvを読むクエリの実行に時間がかかります
    * awsのt2.xlargeでは1.5時間くらいかかりました

* heapメモリが足りない場合には，以下のエラーメッセージが出力されます

```
<interactive>:0:0: error: There is not enough memory to perform the current task. Please try increasing 'dbms.memory.heap.max_size'
```

## 3. Neo4jに関係を登録する

* TPC-Hの「主キーと外部キーの関係」を，neo4jに登録します．
* 以下の手順で行います
    * 外部キーを持つテーブルのcsvファイルを読み込む
    * 1行ずつ，主キーを指定してneo4j上のノードを特定する
    * 当該ノードが持つべき関係を，外部キーを主キーとして持つ別のノードに関連付ける
* 複雑に見えますが，下記のコマンドを見ると，なんとなく掴めます

```sql
neo4j>
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/nation.csv' AS line
  MATCH (n:nation{N_NATIONKEY: toInteger(line[0])})
  MATCH (r:region{R_REGIONKEY: toInteger(line[2])})
  CREATE (n)-[:AT]->(r);
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/customer.csv' AS line
  MATCH (c:customer{C_CUSTKEY: toInteger(line[0])})
  MATCH (n:nation{N_NATIONKEY: toInteger(line[3])})
  CREATE (c)-[:LOCATES]->(n);
USING PERIODIC COMMIT 500
LOAD CSV FROM 'file:///sf001/orders.csv' AS line
  MATCH (o:orders{O_ORDERKEY: toInteger(line[0])})
  MATCH (c:customer{C_CUSTKEY: toInteger(line[1])})
  CREATE (o)-[:FROM]->(c);
USING PERIODIC COMMIT 500 
LOAD CSV FROM 'file:///sf001/lineitem.csv' AS line
  MATCH (l:lineitem{L_ORDERKEY:toInteger(line[0]), L_LINENUMBER:toInteger(line[3])})
  MATCH (o:orders{O_ORDERKEY: toInteger(line[0])})
  CREATE (l)-[:IN]->(o);
USING PERIODIC COMMIT 500 
LOAD CSV FROM 'file:///sf001/supplier.csv' AS line
  MATCH (s:supplier{S_SUPPKEY:toInteger(line[0])})
  MATCH (n:nation{N_NATIONKEY:toInteger(line[3])})
  CREATE (s)-[:LOCATES]->(n);
USING PERIODIC COMMIT 500 
LOAD CSV FROM 'file:///sf001/partsupp.csv' AS line
  MATCH(ps:partsupp{PS_PARTKEY:toInteger(line[0]), PS_SUPPKEY:toInteger(line[1])})
  MATCH(s:supplier{S_SUPPKEY:toInteger(line[1])})
  CREATE (ps)-[:FROM]->(s);
USING PERIODIC COMMIT 500 
LOAD CSV FROM 'file:///sf001/partsupp.csv' AS line
  MATCH(ps:partsupp{PS_PARTKEY:toInteger(line[0]), PS_SUPPKEY:toInteger(line[1])})
  MATCH(p:part{P_PARTKEY:toInteger(line[0])})
  CREATE (ps)-[:INCLUDES]->(p);
USING PERIODIC COMMIT 500 
LOAD CSV FROM 'file:///sf001/lineitem.csv' AS line
  MATCH (l:lineitem{L_ORDERKEY:toInteger(line[0]), L_LINENUMBER:toInteger(line[3])})
  MATCH (ps:partsupp{PS_PARTKEY:toInteger(line[1]), PS_SUPPKEY:toInteger(line[2])})
  CREATE (l)-[:IN]->(ps);
```

* 以上の手順で，neo4jにサイズsf=0.01のTPC-Hデータベースを登録することができました

![tpch-graph.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309617/f0d93528-d38b-7766-39c1-893f0df37bdb.jpeg)

# neo4jのデータベースバックアップ

* データの登録に時間が掛かるので，作ったデータベースのバックアップを取ります

## コンテナでneo4jを実行している場合

* バックアップ前にneo4jサーバを止めないといけないのですが，```neo4j start```で起動していない場合(コンテナ実行の場合)は，一度コンテナを削除する必要があります
* コンテナを立て直す，neo4jの起動スクリプトの代わりに，```neo4j-admin```コマンドでダンプを作成します
* 作成されるバックアップデータは，不揮発ボリュームに格納して，ホストからアクセスできるようにします

```bash
docker stop <neo4jコンテナ名>
docker rm -f <neo4jコンテナ名>

docker run --rm --ulimit nofile=64000 \
  -p 7474:7474 \
  -p 7687:7687 \
  -v ${PWD}/neo4j/data:/var/lib/neo4j/data \
  -v ${PWD}/neo4j/import:/var/lib/neo4j/import \
  -e NEO4J_dbms_memory_pagecache_size=6G \
  -e NEO4J_dbms_memory_heap_max__size=6G \
neo4j bin/neo4j-admin  dump --database=graph.db --to=./data/tpch-sf001.dump
```

* ホストから見たときの```${PWD}/neo4j/data```に```tpch-sf001.dump```が生成されています

## ホストでneo4jを実行している場合

コンテナでない場合は，```neo4j stop```コマンドでneo4jサーバを停止させます
* ```neo4j-admin dump```コマンドでダンプを作成します

```bash
neo4j stop
neo4j-admin dump --database=graph.db --to=./data/tpch-sf001.dump
```

# neo4jデータのリストア

* ダンプデータのリストアも，```neo4j-admin load```コマンドで，同様の方法で可能です．

## コンテナでneo4jを実行する場合

* 一度コンテナを作ってから，ダンプファイルを不揮発ボリュームにコピーし，ダンプデータをロードします．その後，あらためてコンテナを立ち上げます

```bash
# 各種ディレクトリを作るために，一度コンテナを作る
docker run --ulimit nofile=64000 \
  -p 7474:7474 \
  -p 7687:7687 \
  -v ${PWD}/neo4j/data:/var/lib/neo4j/data \
  -v ${PWD}/neo4j/import:/var/lib/neo4j/import \
  -e NEO4J_dbms_memory_pagecache_size=6G \
  -e NEO4J_dbms_memory_heap_max__size=6G \
neo4j
# 不揮発ボリュームにダンプデータをコピー
cp tpch-sf001.dump ${PWD}/neo4j/data/
# 作ったコンテナを消す
docker stop <neo4jコンテナ名>
docker rm -f <neo4jコンテナ名>
# neo4j-admin loadコマンドでデータベースをリストア
docker run --rm --ulimit nofile=64000 \
  -p 7474:7474 \
  -p 7687:7687 \
  -v ${PWD}/neo4j/data:/var/lib/neo4j/data \
  -v ${PWD}/neo4j/import:/var/lib/neo4j/import \
  -e NEO4J_dbms_memory_pagecache_size=6G \
  -e NEO4J_dbms_memory_heap_max__size=6G \
neo4j bin/neo4j-admin load --from=./data/tpch-sf001.dump --database=graph.db --force
# あらためてneo4jコンテナを立ち上げる
docker run --ulimit nofile=64000 \
  -p 7474:7474 \
  -p 7687:7687 \
  -v ${PWD}/neo4j/data:/var/lib/neo4j/data \
  -v ${PWD}/neo4j/import:/var/lib/neo4j/import \
  -e NEO4J_dbms_memory_pagecache_size=6G \
  -e NEO4J_dbms_memory_heap_max__size=6G \
neo4j
```

## ホストでneo4jを実行している場合

* neo4jサーバを止めてから，```neo4j-admin load```コマンドを実行するだけです

```bash
neo4j stop
neo4j-admin  load --from=./data/tpch-sf001.dump --database=graph.db --force
neo4j start
```

* 次回はクエリを実施します

# 参考

* [TPC-H](http://www.tpc.org/tpch/)
* [データベースベンチマークTPC-HをMySQLで実行する(TPC-H v2.18.0)](https://qiita.com/myoshimi/items/bd460164af48ece1ab2d)
* [neo4j document 12.7. Dump and load databases](https://neo4j.com/docs/operations-manual/current/tools/dump-load/)