Neo4j: graph database – запуск в Docker и примеры работы с Cypher QL

Автор: | 28/07/2020
 

В отличии от традиционной RDMS (Relational Database Management System – реляционная база данных), у которой в первую очередь играют роли данные, в Graph Database связи между данными имеют большее значение, и выделены в отдельные объекты, что даёт лучшую производительность, особенно при больших объёмах данных с множеством взаимосвязанных объектов, и делает управление такой базой более гибким.

Одной из первых таких систем является Neo4j, которую мы сегодня и потрагаем за приятные места.

Для запросов в Neo4j используется язык запросов Cypher (CQL), для которого существует cypher-shell, а для доступа к Neo4j через браузер имеется встроенный UI, плюс к этмоу полная поддержка REST API.

Neo4j распространяется по платной модели, при этом имеется бесплатная Community Edition со множеством ограничений (отсутствие кластеризации, скейлинга и прочее), и SaaS Aura. См. сравнение тут>>>.

В этом посте запустим один инстанс Neo4j Community Edition в Docker, кратко посмотрим на его язык запросов и бекап-восстановление данных.

Запуск Neo4j в Docker

Запустим на рабочей машине в Docker, посмотрим на основные компоненты и как происходит доступ к данным на диске. Документация тут>>>.

[simterm]

$ docker run --rm --name neo4j -p 7474:7474 -p 7687:7687 neo4j:latest
...
Directories in use:
  home:         /var/lib/neo4j
  config:       /var/lib/neo4j/conf
  logs:         /logs
  plugins:      /var/lib/neo4j/plugins
  import:       /var/lib/neo4j/import
  data:         /var/lib/neo4j/data
  certificates: /var/lib/neo4j/certificates
  run:          /var/lib/neo4j/run
Starting Neo4j.
...
2020-07-27 10:11:30.394+0000 INFO  Bolt enabled on 0.0.0.0:7687.
2020-07-27 10:11:31.640+0000 INFO  Remote interface available at http://localhost:7474/
2020-07-27 10:11:31.640+0000 INFO  Started

[/simterm]

Проверяем – открываем в браузере http://localhost:7474, и логинимся с дефолтными логином-паролем neo4j:neo4j:

Admin password

Что бы переопределить логин:пароль при запуске – используем --env NEO4J_AUTH:

[simterm]

$ docker run --rm --name neo4j --env NEO4J_AUTH=neo4j/pass -p 7474:7474 -p 7687:7687 neo4j:latest
Changed password for user 'neo4j'.
...

[/simterm]

cypher-shell

Для работы с данными можно использовать REST API, либо локальную консоль – cypher-shell.

И подключаемся к shell:

[simterm]

$ docker exec -ti neo4j cypher-shell -u neo4j -p pass
Connected to Neo4j 4.1.0 at neo4j://localhost:7687 as user neo4j.
Type :help for a list of available commands or :exit to exit the shell.
Note that Cypher queries must end with a semicolon.
neo4j@neo4j> 

[/simterm]

Файл настроек Neo4j

В контейнере файл располагается по пути $NEO4J_HOME/conf/neo4j.conf, т.е. /var/lib/neo4j/conf/neo4j.conf:

[simterm]

root@65d8061ac13e:/var/lib/neo4j# head /var/lib/neo4j/conf/neo4j.conf 
#*****************************************************************
# Neo4j configuration
#
# For more details and a complete list of settings, please see
# https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/
#*****************************************************************

# The name of the default database
#dbms.default_database=neo4j

[/simterm]

Что бы переопределить настройки – монтируем их в каталог /conf контейнера.

Все параметры для файла neo4j.conf можно найти тут>>>.

Получить текущие настройки прямо из консоли можно с помощью dbms.listConfig():

[simterm]

neo4j@neo4j> CALL dbms.listConfig()
             YIELD name, value
             WHERE name STARTS WITH 'dbms.default'
             RETURN name, value
             ORDER BY name
             LIMIT 3;
+-------------------------------------------------+
| name                              | value       |
+-------------------------------------------------+
| "dbms.default_advertised_address" | "localhost" |
| "dbms.default_database"           | "neo4j"     |
| "dbms.default_listen_address"     | "0.0.0.0"   |
+-------------------------------------------------+

3 rows available after 216 ms, consumed after another 13 ms

[/simterm]

cypher-shell && CQL

CREATE

Попробуем немного поработать с данными.

По типам данных есть хороший курс на Tutorialspoint вот тут>>>.

Создадим ноду:

[simterm]

neo4j@neo4j> create (test);
0 rows available after 56 ms, consumed after another 0 ms
Added 1 nodes

[/simterm]

DELETE

Удалим её:

[simterm]

neo4j@neo4j> MATCH (test) DETACH DELETE test;
0 rows available after 32 ms, consumed after another 0 ms
Deleted 1 nodes

[/simterm]

Для удаления всех записей – используем (n):

[simterm]

neo4j@neo4j> MATCH (n) detach delete n;

[/simterm]

Labels

Создать ноду с лейблой label1, и добавим ей Properties с ключами key1 и key2:

[simterm]

neo4j@neo4j> create (node1:label1 {key1: "value1", key2: "value2"} );
0 rows available after 47 ms, consumed after another 0 ms
Added 1 nodes, Set 2 properties, Added 1 labels

[/simterm]

Проверяем:

[simterm]

neo4j@neo4j> MATCH (node1) RETURN node1;
+--------------------------------------------+
| node1                                      |
+--------------------------------------------+
| (:label1 {key1: "value1", key2: "value2"}) |
+--------------------------------------------+

[/simterm]

Либо используя RETURN – получим ноду сразу после создания:

[simterm]

neo4j@neo4j> CREATE (node2:label2 {key1: "value1", key2: "value2"} ) RETURN node2;
+--------------------------------------------+
| node2                                      |
+--------------------------------------------+
| (:label2 {key1: "value1", key2: "value2"}) |
+--------------------------------------------+

[/simterm]

Проверяем в браузере, используя match(n) return n, что бы вывести все записи в базе:

Relations

Связи можно создать между новыми нодами, так и между уже существующими.

Для создания связи между новыми нодами – добавляем -[r:Имя]->:

[simterm]

neo4j@neo4j> create (node3:label3 {key1: "value1", key2: "value2"}) -[r:RelationName]-> (node4:label4{key1: "value1", key2: "value2"}) RETURN node3, node4;
+-----------------------------------------------------------------------------------------+
| node3                                      | node4                                      |
+-----------------------------------------------------------------------------------------+
| (:label3 {key1: "value1", key2: "value2"}) | (:label4 {key1: "value1", key2: "value2"}) |
+-----------------------------------------------------------------------------------------+

1 row available after 88 ms, consumed after another 8 ms
Added 2 nodes, Created 1 relationships, Set 4 properties, Added 2 labels

[/simterm]

Проверяем:

Создать связь между двумя существующими нодами можно с помощью MATCH:

[simterm]

neo4j@neo4j> MATCH (node3:label3), (node4:label4) CREATE (node3) -[r:RelationName2]-> (node4) RETURN node3, node4;
+-----------------------------------------------------------------------------------------+
| node3                                      | node4                                      |
+-----------------------------------------------------------------------------------------+
| (:label3 {key1: "value1", key2: "value2"}) | (:label4 {key1: "value1", key2: "value2"}) |
+-----------------------------------------------------------------------------------------+

1 row available after 124 ms, consumed after another 9 ms
Created 1 relationships

[/simterm]

Backup && Restore

Данные хранятся в каталоге $NEO4J_HOME/data, который является симлинком на /data, см. тут>>>.

Проверяем каталоги:

[simterm]

root@65d8061ac13e:/var/lib/neo4j# ls -l /var/lib/neo4j/data
lrwxrwxrwx 1 root root 5 Jul 23 09:01 /var/lib/neo4j/data -> /data

root@65d8061ac13e:/var/lib/neo4j# ls -l /data/
total 12
drwxrwxrwx 4 neo4j neo4j 4096 Jul 27 11:19 databases
drwxr-xr-x 2 neo4j neo4j 4096 Jul 27 11:19 dbms
drwxrwxrwx 4 neo4j neo4j 4096 Jul 27 11:19 transactions

[/simterm]

Файлы баз хранятся в папке databases, где по умолчанию созданы две базы – system, и neo4j, которые можно увидеть с помощью show databases:

[simterm]

neo4j@neo4j>  show databases;
+------------------------------------------------------------------------------------------------+
| name     | address          | role         | requestedStatus | currentStatus | error | default |
+------------------------------------------------------------------------------------------------+
| "neo4j"  | "localhost:7687" | "standalone" | "online"        | "online"      | ""    | TRUE    |
| "system" | "localhost:7687" | "standalone" | "online"        | "online"      | ""    | FALSE   |
+------------------------------------------------------------------------------------------------+

[/simterm]

База system хранит системные настройки сервера, а nedo4j – дефолтная пользовательская база.

Neo4j dump

На рабочей машине создаём каталоги:

[simterm]

$ mkdir -p /tmp/neo4/{data,logs}

[/simterm]

Перезапустим Neo4j, смонтируем локальные каталоги в контейнер:

[simterm]

$ docker run --rm --name neo4j --env NEO4J_AUTH=neo4j/pass -p 7474:7474 -p 7687:7687 -v /tmp/neo4/data/:/data -v /tmp/neo4/logs/:/logs neo4j:latest
Changed password for user 'neo4j'.
Directories in use:
  home:         /var/lib/neo4j
  config:       /var/lib/neo4j/conf
  logs:         /logs
  plugins:      /var/lib/neo4j/plugins
  import:       /var/lib/neo4j/import
  data:         /var/lib/neo4j/data
  certificates: /var/lib/neo4j/certificates
  run:          /var/lib/neo4j/run
...

[/simterm]

На хосте проверяем:

[simterm]

$ ll /tmp/neo4/data/databases/
total 0
drwxr-xr-x 2 7474 7474 720 Jul 27 16:07 neo4j
-rw-r--r-- 1 7474 7474   0 Jul 27 16:07 store_lock
drwxr-xr-x 3 7474 7474 740 Jul 27 16:07 system

[/simterm]

Подключаемся, создаём тестовую запись:

[simterm]

$ docker exec -ti neo4j cypher-shell -u neo4j -p pass
neo4j@neo4j> create (test:tobackup);
0 rows available after 131 ms, consumed after another 0 ms
Added 1 nodes

[/simterm]

Для создания дампа необходимо остановить Neo4j:

[simterm]

root@771f04312148:/var/lib/neo4j# neo4j-admin dump --database=neo4j --to=/data/backups/
The database is in use. Stop database 'neo4j' and try again.

[/simterm]

Выходим из контейнера, и останавливаем его:

[simterm]

$ docker stop neo4j
neo4j

[/simterm]

Запускаем его ещё раз, но команду передаём bash, что бы не запустился сам Neo4j:

[simterm]

$ docker run -ti --rm --name neo4j --env NEO4J_AUTH=neo4j/pass -p 7474:7474 -p 7687:7687 -v /tmp/neo4/data/:/data -v /tmp/neo4/logs/:/logs neo4j:latest bash
neo4j@6d4e9854bc1d:~$

[/simterm]

Создаём дамп:

[simterm]

neo4j@015ba14bdba2:~$ mkdir /data/backup
neo4j@015ba14bdba2:~$ neo4j-admin dump --database=neo4j --to=/data/backup/
Done: 34 files, 250.8MiB processed.

[/simterm]

Проверяем:

[simterm]

neo4j@015ba14bdba2:~$ ls -l /data/backup/
total 12
-rw-r--r-- 1 neo4j neo4j 9971 Jul 27 13:46 neo4j.dump

[/simterm]

Restore

На хосте создаём каталоги для второго инстанса:

[simterm]

$ mkdir -p /tmp/neo4-2/{data,logs}

[/simterm]

Копируем каталог с бекапом из папки старого контейнера в новый:

[simterm]

$ sudo cp -r /tmp/neo4/data/backup/ /tmp/neo4-2/data/

[/simterm]

Запускаем сервер, пока как обычно, монтируем ему /tmp/neo4-2, меняем порты и имя:

[simterm]

$ docker run --rm --name neo4j-2 --env NEO4J_AUTH=neo4j/pass -p 7475:7474 -p 7688:7687 -v /tmp/neo4-2/data/:/data -v /tmp/neo4-2/logs/:/logs neo4j:latest

[/simterm]

Подключаемся, проверяем данные:

[simterm]

$ docker exec -ti neo4j-2 cypher-shell -u neo4j -p pass 
Connected to Neo4j 4.1.0 at neo4j://localhost:7687 as user neo4j.
Type :help for a list of available commands or :exit to exit the shell.
Note that Cypher queries must end with a semicolon.
neo4j@neo4j> match (n) return n;
+---+
| n |
+---+
+---+

[/simterm]

Окей – наших записей нет, т.к. база новая.

Выходим, останавливаем контейнер, и запускаем его с bash:

[simterm]

$ docker run -ti --rm --name neo4j-2 --env NEO4J_AUTH=neo4j/pass -p 7475:7474 -p 7688:7687 -v /tmp/neo4-2/data/:/data -v /tmp/neo4-2/logs/:/logs neo4j:latest bash
neo4j@b0f324cb7c9b:~$

[/simterm]

Загружаем базу, добавляем --force, т.к. база с таким именем уже есть:

[simterm]

neo4j@7bca892e9538:~$ neo4j-admin load --from=/data/backup/neo4j.dump --database=neo4j --force
Done: 34 files, 250.8MiB processed.

[/simterm]

Выходим, перезапускаем контейнер, как обычно, что бы запустился сервис:

[simterm]

$ docker run -ti --rm --name neo4j-2 --env NEO4J_AUTH=neo4j/pass -p 7475:7474 -p 7688:7687 -v /tmp/neo4-2/data/:/data -v /tmp/neo4-2/logs/:/logs neo4j:latest

[/simterm]

Подключаемся, проверяем:

[simterm]

neo4j@neo4j> match (n) return n;
+-------------+
| n           |
+-------------+
| (:tobackup) |
+-------------+

[/simterm]

Всё на месте.