Neo4j — графовая база данных, в которой данные хранятся не в таблицах, а в узлах и связях. Узел описывает сущность, связь — отношение между сущностями, а у обеих есть свойства. Язык запросов Cypher читается почти как текст: «найди пользователя, который купил товар, и покажи категории этого товара». Такой подход особенно полезен для рекомендаций, антифрода, управления доступом и анализа зависимостей.

Когда граф действительно нужен

Графовая модель выигрывает, когда основная ценность — в связях и их глубине. Если вы постоянно пишете рекурсивные JOIN, считаете кратчайшие пути или строите рекомендации «друзья друзей», граф упрощает и запрос, и схему. Для обычной отчётности, финансов и табличных выгрузок реляционная база остаётся разумнее.

  • Хорошо: социальные связи, каталоги, маршруты, права доступа.
  • Плохо: массовые агрегаты по миллионам строк, чистая OLAP-аналитика.
  • Важно: связи в Neo4j — первоклассные объекты, а не вычисляемые join.

Запуск в Docker

Для знакомства достаточно Community Edition. Пароль задаётся через переменную окружения при первом старте; смените его сразу и не храните в открытом виде. Порты 7474 и 7687 — это HTTP и Bolt соответственно.

docker run -d --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/ChangeMe123 \
  -v neo4j_data:/data -v neo4j_logs:/logs \
  neo4j:5

docker logs -f neo4j
docker exec -it neo4j cypher-shell -u neo4j -p ChangeMe123 "RETURN 1;"

Основы Cypher

Узлы обозначаются круглыми скобками, связи — стрелками. В CREATE можно сразу задать метки и свойства. Не создавайте всё подряд через CREATE: при повторном запуске скрипта появятся дубликаты.

CREATE (a:Person {name: 'Анна', city: 'Казань'})
CREATE (b:Person {name: 'Олег', city: 'Москва'})
CREATE (a)-[:KNOWS {since: 2019}]->(b)
CREATE (b)-[:WORKS_AT]->(:Company {name: 'Пример'});

MATCH (p:Person {name: 'Анна'})-[r:KNOWS]->(f)
RETURN f.name AS друг, r.since AS с_года;

MERGE и ограничения

MERGE ищет шаблон и создаёт его только при отсутствии совпадения. Это основа идемпотентной загрузки. Обязательно создайте ограничение уникальности: без него MERGE при параллельной записи может породить дубликаты, а планировщик не сможет использовать индекс.

CREATE CONSTRAINT person_id IF NOT EXISTS
FOR (p:Person) REQUIRE p.id IS UNIQUE;

MERGE (p:Person {id: 42})
ON CREATE SET p.createdAt = datetime()
ON MATCH SET p.lastSeen = datetime()
SET p.name = 'Анна';

MATCH (p:Person) RETURN count(p) AS всего;

Обход путей и переменная длина

Связи можно проходить на несколько шагов: [:KNOWS*1..3] означает от одного до трёх переходов. Ограничивайте глубину и количество результатов, иначе запрос может обойти весь граф. Для кратчайшего пути используйте shortestPath.

MATCH path = shortestPath(
  (a:Person {id: 42})-[:KNOWS*..6]-(b:Person {id: 7})
)
RETURN length(path) AS шагов, [n IN nodes(path) | n.name] AS маршрут;

MATCH (a:Person {id: 42})-[:KNOWS*1..2]-(fof)
WHERE fof.id <> 42
RETURN DISTINCT fof.name, count(*) AS общих
ORDER BY общих DESC LIMIT 10;

Индексы и планы запросов

Индекс ускоряет поиск начальной точки. Полнотекстовый индекс нужен для поиска по словам, диапазонный — для чисел и дат. Смотрите план через EXPLAIN, а фактическое выполнение — через PROFILE. Если в плане появился AllNodesScan, скорее всего, индекс не подхватился из-за несовпадения метки или типа свойства.

CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.name);
CREATE FULLTEXT INDEX person_search IF NOT EXISTS
FOR (p:Person) ON EACH [p.name, p.city];

PROFILE MATCH (p:Person {id: 42}) RETURN p;
EXPLAIN MATCH (p:Person) WHERE p.name = 'Анна' RETURN p;

Резервное копирование и восстановление

Для Community Edition используйте offline-дамп: базу нужно остановить, иначе копия будет несогласованной. В Enterprise доступны online-бэкапы. Проверяйте восстановление на отдельном инстансе — непроверенная копия не считается резервной.

docker stop neo4j
docker run --rm -v neo4j_data:/data -v ${PWD}/backup:/backup neo4j:5 \
  neo4j-admin database dump neo4j --to-path=/backup
docker start neo4j

docker run --rm -v neo4j_data:/data -v ${PWD}/backup:/backup neo4j:5 \
  neo4j-admin database load neo4j --from-path=/backup --overwrite-destination=true

Мониторинг и диагностика

HTTP-эндпоинт отвечает, даже если Bolt занят, поэтому проверяйте оба канала. Внутри базы смотрите активные транзакции и медленные запросы. Для регулярного мониторинга включите метрики Prometheus и следите за размером heap, числом страниц и временем выполнения запросов.

curl -s http://localhost:7474/ | head
docker exec neo4j cypher-shell -u neo4j -p ChangeMe123 "SHOW DATABASES;"
docker exec neo4j cypher-shell -u neo4j -p ChangeMe123 "SHOW TRANSACTIONS YIELD transactionId, currentQuery;"
docker stats neo4j --no-stream

Моделирование: узел или свойство

Главная ошибка новичка — переносить таблицы в граф один в один. Спросите себя, будет ли сущность участвовать в связях и запросах отдельно. Город человека лучше хранить свойством, а компанию — отдельным узлом, если по ней строятся отчёты и связи. Метки задавайте по роли объекта, а не по каждому изменению статуса. Помните, что связи тоже могут иметь свойства: дату начала, вес, источник. Если сомневаетесь, начните с минимальной модели и расширяйте её по мере появления реальных запросов.

  • Узел нужен, если у сущности есть собственные связи и атрибуты.
  • Свойство уместно для простых значений без отдельной жизни.
  • Не кодируйте тип связи в имени свойства — используйте тип связи.

Пакетная загрузка данных

Загружайте данные пачками через параметр rows и UNWIND: так драйвер передаёт один запрос вместо тысяч. Для больших объёмов используйте neo4j-admin import на пустой базе, но помните, что это офлайн-операция. После загрузки обязательно проверьте количество узлов и отсутствие дубликатов.

UNWIND $rows AS row
MERGE (p:Person {id: row.id})
SET p.name = row.name
WITH p, row
UNWIND row.tags AS tag
MERGE (t:Tag {name: tag})
MERGE (p)-[:HAS_TAG]->(t);

Чек-лист перед продакшеном

  • Пароль изменён, внешний доступ закрыт файрволом.
  • Созданы ограничения уникальности и нужные индексы.
  • Запросы проверены через PROFILE, глубина обхода ограничена.
  • Настроены дампы, ротация логов и проверка восстановления.
  • Определён объём heap и лимиты контейнера.
  • Есть план миграции схемы и загрузки данных.

Итог

Neo4j раскрывается там, где связи важнее таблиц. Начните с Docker-контейнера, опишите модель на бумаге, создайте ограничения и загружайте данные через MERGE. Затем измерьте планы запросов и только после этого расширяйте схему. Граф прощает гибкость модели, но не прощает отсутствия индексов и неограниченных обходов.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте