В отличии от традиционной 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
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]
Всё на месте.