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

Автор: | 07/28/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, посмотрим на основные компоненты и как происходит доступ к данным на диске. Документация тут>>>.

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

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

Admin password

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

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

cypher-shell

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

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

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>

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

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

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

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

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

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

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

cypher-shell && CQL

CREATE

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

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

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

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

DELETE

Удалим её:

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

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

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

Labels

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

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

Проверяем:

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

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

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

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

Relations

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

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

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

Проверяем:

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

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

Backup && Restore

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

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

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

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

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   |
+------------------------------------------------------------------------------------------------+

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

Neo4j dump

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

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

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

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
...

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

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

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

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

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

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

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

docker stop neo4j
neo4j

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

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:~$

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

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

Проверяем:

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

Restore

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

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

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

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

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

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

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

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 |
+---+
+---+

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

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

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:~$

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

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

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

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

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

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

Всё на месте.