Neo4jにCSVロードする方法は少なくとも4つはあるようです。

  • CSV LOAD
  • apoc.load.csv
  • apoc.import.csv
  • neo4j-admin import

この記事ではこの4つの方法について、pythonから実行する具体的な実装を説明したいと思います。

ロードするデータ

サンプル実装でロードするデータはこちらです。リレーションLINKEDにはscoreというリンクの強さを表すプロパティが設定されています。

検証環境

今回は、docker-hubneo4j:latestイメージで、docker-composeで検証します。docker-compose.yamlの設定は基本的には以下のような感じです。

version: '3'

services:
  neo4j:
    image: neo4j:latest
    ports:
      - "7474:7474" # 管理画面用
      - "7687:7687" # bolt用
    volumes:
      - ${HOME}/neo4j/data:/data
      - ${HOME}/neo4j/logs:/logs
      - ${HOME}/neo4j/conf:/conf
      - ${HOME}/neo4j/import:/import

    environment:
      - NEO4J_AUTH=neo4j/password
      # admin memrecに従って設定
      - NEO4J_dbms_memory_heap_max__size=4G
      - NEO4J_dbms_memory_heap_initial__size=4G
      - NEO4J_dbms_memory_pagecache_size=454900k
      - NEO4J_dbms_tx__state_max__off__heap__memory=2500m
      # APOC関係
      - NEO4JLABS_PLUGINS=["apoc"]
      - NEO4J_apoc_import_file_use__neo4j__config=true # import時に/importフォルダからの相対パスを指定
      - NEO4J_apoc_import_file_enabled=true

CSVインポートはサーバサイドで実行されるため、ロード用のCSVファイルはHTTPで提供するか、サーバに配置する必要があります。今回はサーバに配置する形式なので、ホストマシン側のフォルダをdockerコンテナ側の/importフォルダにマウントして、そこにCSVファイルを配置しています。

方法1: CSV LOAD

公式サイトはこちら

実際みるとリレーション10万件で30分かかったので、1万件行かないぐらいのデータ量で気軽にCSVロードしたいならこの方式がいいのではないかと思いました。

特徴

  • 小規模なデータロードに使える
  • トランザクションを利用せず、USING PERIODIC COMMITを使って一括コミットすることで、ある程度の速度は出る。
  • ノードとリレーションを別々にロードする
  • リレーションは、1レコードずつ既存のノードとMATCHさせてからCREATEする

低速ではあるものの、特別な配慮なく通常のCypherクエリと同様にロードできますので、一番気楽に利用できます。

CSV

普通のCSVのformatです。

  • nodes.csv

    id
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
  • relations.csv

    from_id,to_id,score
    1,2,0.412
    1,3,0.623
    3,4,0.512
    4,5,0.79
    6,7,0.31
    7,8,0.79
    

python実装

from neo4j import GraphDatabase, Driver

driver: Driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'), encrypted=False)
with driver.session() as session:
    # ノード用CSVデータをロード
    nodes_query: str = """
        USING PERIODIC COMMIT 10000
        LOAD CSV WITH HEADERS FROM $csv_path AS line
        CREATE (:Node {id:toInteger(line.id)})
    """
    session.run(nodes_query, csv_path='file:///nodes.csv')

    # リレーション用CSVデータをロード
    relations_query: str = """
        USING PERIODIC COMMIT 10000
        LOAD CSV WITH HEADERS FROM $csv_path AS line
        MATCH (nf:Node {id:toInteger(line.from_id)}), (nt:Node {id:toInteger(line.to_id)})
        CREATE (nf)-[:LINKED {score: toFloat(line.score)}]->(nt)
    """
    session.run(relations_query, csv_path='file:///relations.csv')

少しでも高速化するためにトランザクションを使用せず、USING PERIODIC COMMIT 10000を指定しています。また、CSVデータはデフォルトで文字列扱いになるため、toIntegertoFloatで目的の型に変換してあげる必要があります。

方法2: apoc.load.csv

公式サイトはこちら

LOAD CSV同様に、データ量が少なくLOAD CSVで提供されてない機能(例えば行番号をIDに設定したい、とか)の場合に利用することになると思います。

APOC

apocは、Neo4j本体で実装されていない、データインテグレーション、グラフアルゴリズム、データ変換などのプロシージャや関数を提供してくれるライブラリです。
今回は、docker-composeで検証しているため、利用のためにはdocker-compose.yamlで環境変数の設定をしています。

特徴

  • 小規模なデータロードに使える
  • LOAD CSVと同様だが、色々な機能が追加で追加で提供されている
    • 行番号を利用できる
    • CSVの各行をmap形式やlist形式で返すことができる
    • 自動でデータの型を変換してくれる
    • など。他にもいくつか
  • apoc.periodic.iteratebatchSizeを指定することで、LAOD CSVと同様に一括コミットすることで高速化できる。詳細はこちら
  • リレーションは、1レコードずつ既存のノードとMATCHさせてからCREATEする

CSV

LOAD CSVと同様です。

python実装

from neo4j import GraphDatabase, Driver

driver: Driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'), encrypted=False)

with driver.session() as session:
    nodes_query: str = """
        CALL apoc.load.csv($csv_path) yield map as line
        CREATE (:Node {id:toInteger(line.id)})
    """
    session.run(nodes_query, csv_path='file:///nodes.csv')

    # リレーション用CSVデータをロード
    relations_query: str = """
        CALL apoc.load.csv($csv_path) yield map as line
        MATCH (nf:Node {id:toInteger(line.from_id)}), (nt:Node {id:toInteger(line.to_id)})
        CREATE (nf)-[:LINKED {score: toFloat(line.score)}]->(nt)
    """
    session.run(relations_query, csv_path='file:///relations.csv')

    # リレーション用CSVデータをロード(バッチインサート版)
    relations_batch_query: str = """
        CALL apoc.periodic.iterate('
            CALL apoc.load.csv($path) yield map as line
        ', '
            MATCH (nf:Node {id:toInteger(line.from_id)}), (nt:Node {id:toInteger(line.to_id)})
            CREATE (nf)-[:LINKED {score: toFloat(line.score)}]->(nt)
         ', {batchSize:10000, iterateList:true, parallel:false, params:{path:$csv_path}});
    """
    session.run(relations_batch_query, csv_path='file:///relations.csv')    

方法3: apoc.import.csv

公式サイトはこちら

既存のデータベースに小規模から中規模のデータをロードしたい場合、こちらを利用すると良いと思います。実際にやってみたところ、リレーション100万件で50秒だったので、1000万件ぐらいまでだったら十分実用的ではないかと思います。

特徴

  • 小規模〜中規模のデータロードに使える
  • Neo4j import toolのheader formatを利用して、ID値、リレーションを結ぶノードのLABEL、プロパティ名・型などを指定する
  • ノードとリレーションのCSVを一括でアップロードし、その中に含まれるノード間にリレーションを設定する(CSV内に存在しないノードを指定したリレーションは無視される)

CSV

CSV LOADのCSVファイルと、ヘッダーの記載方法だけ変わっています。

  • nodes.csv

    :ID
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
  • relations.csv

    Node:START_ID,Node:END_ID,score:float
    1,2,0.412
    1,3,0.623
    3,4,0.512
    4,5,0.79
    6,7,0.31
    7,8,0.79
    

python実装

ノードのLABELやリレーションのTypeはprocedureのオプションとして指定します。

from neo4j import GraphDatabase, Driver

driver: Driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'), encrypted=False)

with driver.session() as session:
    query: str = """
        CALL apoc.import.csv(
            [{fileName: $node_csv_path, labels: ['Node']}],
            [{fileName: $relation_csv_path, type: 'LINKED'}],
            {}
        )
    """
    session.run(query, node_csv_path=f'file:///nodes.csv', relation_csv_path=f'file:///relations.csv')

方法4: neo4j-admin import

公式サイトはこちら

neo4j-adminのコマンドラインツールの「importコマンドを利用する方法です。空のDBに、億を超える大量データをロードするようなケースで利用できると思います。
実際にやってみたところ、リレーション3億件で60分ほどでした。

特徴

  • 大規模のデータロードに使える
  • 既存データが存在するDBには使えない(空のDBにだけ使える)
    • 利用ケースによってはこれが致命的w
  • Neo4jのDBサーバ側でCLIを実行する必要があるので、pythonから実行するには結構工夫が必要
  • Neo4j import toolのheader formatを利用して、ID値、リレーションを結ぶノードのLABEL、プロパティ名・型などを指定する
  • ノードとリレーションのCSVを一括でアップロードし、その中に含まれるノード間にリレーションを設定する(CSV内に存在しないノードを指定したリレーションは無視される)

処理の流れ

neo4j-adminはCLIツールで、DBサーバ内で実行する必要があるため、pythonから実行するためには以下のように結構トリッキーな流れになりました。

  1. pythonでCSVファイルを作成(この部分の実装は記事上は割愛)
  2. pythonからsubprocess.calldocker-compose upを実行する
  3. dockerコンテナ起動時に、EXTENSION_SCRIPTで指定したbashスクリプトを実行する
  4. bashスクリプト内で、neo4j-adminコマンドを利用しCSVファイルをロードする
  5. pythonでCSVファイルを削除(この部分の実装は記事上は割愛)

CSV

今回は大量データを想定して、複数CSVファイルに別れて、CSVのヘッダーを別ファイルで指定するケースを想定します。ファイルを分割してるだけで、ファイルの内容はapoc.import.csvと同様です。

  • nodes_header.csv

    id:ID
  • nodes_data_01.csv

    1
    2
    3
    4
  • nodes_data_02.csv

    5
    6
    7
    8
    9
  • relations_header.csv

    Node:START_ID,Node:END_ID,score:float
  • relations_data_01.csv

    1,2,0.412
    1,3,0.623
    3,4,0.512
  • relations_data_02.csv

    4,5,0.79
    6,7,0.31
    7,8,0.79

docker-compose.yaml

EXTENSION_SCRIPTでコンテナ起動時に実行するスクリプトを指定しています。
また、このファイルはDBサーバ内に存在する必要があるため、ホストマシン側のフォルダをdockerコンテナ側の/scriptフォルダにマウントしています。

version: '3'

services:
  neo4j:
    image: neo4j:latest
    ports:
      - "7474:7474" # 管理画面用port
      - "7687:7687" # websocket用port
    volumes:
      - ${HOME}/neo4j/data:/data
      - ${HOME}/neo4j/logs:/logs
      - ${HOME}/neo4j/conf:/conf
      - ${HOME}/neo4j/import:/import # ここにCSVファイルを配置
      - ${HOME}/neo4j/script:/script # ここに起動時実行するスクリプトを配置

    environment:
      - NEO4J_AUTH=neo4j/password
      - EXTENSION_SCRIPT=/script/import_csv.sh # 起動時に実行するスクリプト

import_csv.sh(コンテナ起動時に実行されるシェル)

/importフォルダにCSVファイルが存在する場合、既存のDBを削除して、CSVロードを実行します。
CSVファイルは正規表現を使って指定することができます。

#!/bin/bash
set -euC

# CSVファイルがなければ何もしない
if [[ "$(ls -1 /import | wc -l)" == "0" ]]; then
    echo "import csv skipped."
    return
fi

# データを全削除
echo "delete database started."
rm -rf /var/lib/neo4j/data/databases
rm -rf /var/lib/neo4j/data/transactions
echo "delete database finished."

# CSVインポート
echo "importing csv started."
/var/lib/neo4j/bin/neo4j-admin import \
  --id-type=INTEGER \
  --nodes="/import/nodes_header.csv,/import/nodes_data_[0-9]+.csv" \
  --relationships="/import/relations_header.csv,/import/relations_data_[0-9]+.csv"
echo "importing csv finished."

python実装

いくつかポイントがあります。

  • withで扱えるように__ente__でサーバを起動し、__exit__でサーバを停止する作りにしています。
  • バックグラウンドでdockerの立ち上げをするために-dオプションをつけています
  • Neo4jが立ち上がったかどうかは、Neo4jに接続してみてServiceUnavailableが発生するかどうかで判断しています
from neo4j import GraphDatabase, Driver
from neobolt.exceptions import ServiceUnavailable
import subprocess
import time
import traceback

class Neo4jServer:
    def __enter__(self):
        """
        dockerコンテナを起動する
        """
        subprocess.call(['docker-compose', 'up', '-d'])
        while self.__neo4j_available() is False:
            time.sleep(10)
        return self

    def __exit__(self, exc_type, exc_value, tb):
        """
        dockerコンテナを停止する
        """
        if tb is not None:
            print(''.join(traceback.format_tb(tb)))
        subprocess.call(['docker-compose', 'stop'])

    def __neo4j_available(self) -> bool:
        """
        Neo4jが利用可能かどうかをチェックする
        """
        try:
            GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'), encrypted=False)
        except ServiceUnavailable:
            print('neo4j is not available yet.')
            return False
        print('neo4j is already available.')
        return True

# 実際の利用イメージ
with Neo4jServer():
    driver: Driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'), encrypted=False)
    with driver.session() as session:
        print(f"node_count: {session.run('MATCH (n:Node) RETURN count(n) as cnt').single()['cnt']}")
        print(f"relationship_count: {session.run('MATCH ()-[l:LINKED]->() RETURN count(l) as cnt').single()['cnt']}")

ログ

  • pythonの実行ログ

    Starting nayose_processing_neo4j_1 ... done // dockerコンテナ立ち上げ
    neo4j is not available yet. // CSVロード中
    neo4j is not available yet. // CSVロード中
    neo4j is not available yet. // CSVロード中
    neo4j is not available yet. // CSVロード中
    neo4j is not available yet. // CSVロード中
    neo4j is already available. // CSVロード終了+Neo4jサーバ立ち上げ完了
    node_count: 9 // クエリでノードが9件登録されていることを確認
    relationship_count: 6 // クエリでリレーションが6件登録されていることを確認
    Stopping nayose_processing_neo4j_1 ... done // dockerコンテナ停止
  • docker-compose ログ
    docker-compose logsで確認したログです。ちょっと長いですが、参考までに載せておきます。

    neo4j_1  | Warning: Folder mounted to "/data/databases" is not writable from inside container. Changing folder owner to neo4j.
    neo4j_1  | Changed password for user 'neo4j'.
    neo4j_1  | delete database started.
    neo4j_1  | delete database finished.
    neo4j_1  | importing csv started.
    neo4j_1  | Neo4j version: 4.0.1
    neo4j_1  | Importing the contents of these files into /data/databases/neo4j:
    neo4j_1  | Nodes:
    neo4j_1  |   /import/nodes_header.csv
    neo4j_1  |   /import/nodes_data_01.csv
    neo4j_1  |   /import/nodes_data_02.csv
    neo4j_1  | 
    neo4j_1  | Relationships:
    neo4j_1  |   /import/relations_header.csv
    neo4j_1  |   /import/relations_data_01.csv
    neo4j_1  |   /import/relations_data_02.csv
    neo4j_1  | 
    neo4j_1  | 
    neo4j_1  | Available resources:
    neo4j_1  |   Total machine memory: 9.735GiB
    neo4j_1  |   Free machine memory: 8.790GiB
    neo4j_1  |   Max heap memory : 3.833GiB
    neo4j_1  |   Processors: 4
    neo4j_1  |   Configured max memory: 5.311GiB
    neo4j_1  |   High-IO: false
    neo4j_1  | 
    neo4j_1  | Type normalization:
    neo4j_1  |   Property type of 'score' normalized from 'float' --> 'double' in /import/relations_header.csv
    neo4j_1  | 
    neo4j_1  | Import starting 2020-03-23 01:23:06.332+0000
    neo4j_1  |   Estimated number of nodes: 9.00 
    neo4j_1  |   Estimated number of node properties: 9.00 
    neo4j_1  |   Estimated number of relationships: 6.00 
    neo4j_1  |   Estimated number of relationship properties: 6.00 
    neo4j_1  |   Estimated disk space usage: 963B
    neo4j_1  |   Estimated required memory usage: 1020MiB
    neo4j_1  | 
    neo4j_1  | (1/4) Node import 2020-03-23 01:23:06.380+0000
    neo4j_1  |   Estimated number of nodes: 9.00 
    neo4j_1  |   Estimated disk space usage: 513B
    neo4j_1  |   Estimated required memory usage: 1020MiB
    neo4j_1  | -......... .......... .......... .......... ..........   5% ∆97ms
    neo4j_1  | .......... .......... .......... .......... ..........  10% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  15% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  20% ∆3ms
    neo4j_1  | .......... .......... .......... .......... ..........  25% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  30% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  35% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  40% ∆3ms
    neo4j_1  | .......... .......... .......... .......... ..........  45% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  50% ∆3ms
    neo4j_1  | .......... .......... .......... .......... ..........  55% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  60% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  65% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  70% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  75% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  80% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  85% ∆9ms
    neo4j_1  | .......... .......... .......... .......... ..........  90% ∆5ms
    neo4j_1  | .......... .......... .......... .......... ..........  95% ∆6ms
    neo4j_1  | .......... .......... .......... .......... .......... 100% ∆6ms
    neo4j_1  | 
    neo4j_1  | (2/4) Relationship import 2020-03-23 01:23:06.649+0000
    neo4j_1  |   Estimated number of relationships: 6.00 
    neo4j_1  |   Estimated disk space usage: 450B
    neo4j_1  |   Estimated required memory usage: 1.004GiB
    neo4j_1  | .......... .......... .......... .......... ..........   5% ∆72ms
    neo4j_1  | .......... .......... .......... .......... ..........  10% ∆5ms
    neo4j_1  | .......... .......... .......... .......... ..........  15% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  20% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  25% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  30% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  35% ∆3ms
    neo4j_1  | .......... .......... .......... .......... ..........  40% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  45% ∆3ms
    neo4j_1  | .......... .......... .......... .......... ..........  50% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  55% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  60% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  65% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  70% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  75% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  80% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  85% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  90% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  95% ∆1ms
    neo4j_1  | .......... .......... .......... .......... .......... 100% ∆0ms
    neo4j_1  | 
    neo4j_1  | (3/4) Relationship linking 2020-03-23 01:23:06.754+0000
    neo4j_1  |   Estimated required memory usage: 1020MiB
    neo4j_1  | -......... .......... .......... .......... ..........   5% ∆28ms
    neo4j_1  | .......... .......... .......... .......... ..........  10% ∆5ms
    neo4j_1  | .......... .......... .......... .......... ..........  15% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  20% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  25% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  30% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  35% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  40% ∆4ms
    neo4j_1  | .......... .......... .......... .......... ..........  45% ∆3ms
    neo4j_1  | .......... .......... .......... .......... ..........  50% ∆2ms
    neo4j_1  | .......... .......... .......... .......... ..........  55% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  60% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  65% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  70% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  75% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  80% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  85% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  90% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  95% ∆0ms
    neo4j_1  | .......... .......... .......... .......... .......... 100% ∆0ms
    neo4j_1  | 
    neo4j_1  | (4/4) Post processing 2020-03-23 01:23:06.938+0000
    neo4j_1  |   Estimated required memory usage: 1020MiB
    neo4j_1  | -......... .......... .......... .......... ..........   5% ∆15s 312ms
    neo4j_1  | .......... .......... .......... .......... ..........  10% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  15% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  20% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  25% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  30% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  35% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  40% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  45% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  50% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  55% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  60% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  65% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  70% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  75% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  80% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  85% ∆0ms
    neo4j_1  | .......... .......... .......... .......... ..........  90% ∆1ms
    neo4j_1  | .......... .......... .......... .......... ..........  95% ∆0ms
    neo4j_1  | .......... .......... .......... .......... .......... 100% ∆1ms
    neo4j_1  | 
    neo4j_1  | 
    neo4j_1  | IMPORT DONE in 16s 290ms. 
    neo4j_1  | Imported:
    neo4j_1  |   9 nodes
    neo4j_1  |   6 relationships
    neo4j_1  |   15 properties
    neo4j_1  | Peak memory usage: 1.004GiB
    neo4j_1  | importing csv finished.
    neo4j_1  | Directories in use:
    neo4j_1  |   home:         /var/lib/neo4j
    neo4j_1  |   config:       /var/lib/neo4j/conf
    neo4j_1  |   logs:         /logs
    neo4j_1  |   plugins:      /var/lib/neo4j/plugins
    neo4j_1  |   import:       /import
    neo4j_1  |   data:         /var/lib/neo4j/data
    neo4j_1  |   certificates: /var/lib/neo4j/certificates
    neo4j_1  |   run:          /var/lib/neo4j/run
    neo4j_1  | Starting Neo4j.
    neo4j_1  | 2020-03-23 01:23:23.348+0000 INFO  ======== Neo4j 4.0.1 ========
    neo4j_1  | 2020-03-23 01:23:23.357+0000 INFO  Starting...
    neo4j_1  | 2020-03-23 01:23:43.913+0000 INFO  Bolt enabled on 0.0.0.0:7687.
    neo4j_1  | 2020-03-23 01:23:43.917+0000 INFO  Started.
    neo4j_1  | 2020-03-23 01:23:44.947+0000 INFO  Remote interface available at http://0.0.0.0:7474/
    neo4j_1  | 2020-03-23 01:23:53.825+0000 INFO  Neo4j Server shutdown initiated by request
    neo4j_1  | 2020-03-23 01:23:53.880+0000 INFO  Stopping...
    neo4j_1  | 2020-03-23 01:23:59.137+0000 INFO  Stopped.