Прямой ответ
Графовая база хранит данные как узлы и связи между ними, причём связь — объект первого класса: у неё есть тип, направление и собственные свойства. В реляционной модели связь выражается внешним ключом и соединением таблиц; в графовой — явным ребром, по которому можно перемещаться.
Модель свойств (property graph):
- Узел (node) — сущность с меткой и набором свойств:
(:Customer {id: 42, city: "Казань"}). - Связь (relationship) — направленное и типизированное ребро со свойствами:
-[:PURCHASED {date: ..., amount: ...}]->. - Метка (label) — классификатор узла, используется для индексации и фильтрации.
Главное техническое отличие — index-free adjacency: каждый узел хранит прямые ссылки на соседние. Обход графа стоит пропорционально размеру пройденного подграфа, а не объёму всей базы. В реляционной модели каждое соединение требует поиска по индексу, и при трёх-четырёх переходах запрос деградирует.
Язык запросов Neo4j — Cypher, где схема рисуется символами:
// клиенты, купившие то же, что и клиент 42, кроме него самого
MATCH (c:Customer {id: 42})-[:PURCHASED]->(p:Product)<-[:PURCHASED]-(other:Customer)
WHERE other <> c
RETURN other.id, count(p) AS common_items
ORDER BY common_items DESC
LIMIT 10;
// путь длиной до четырёх связей: кто кого привёл
MATCH path = (a:Customer)-[:REFERRED*1..4]->(b:Customer {id: 99})
RETURN [n IN nodes(path) | n.id] AS chain, length(path) AS depth;
Для сравнения: тот же запрос «рекомендации по общим покупкам» в SQL требует двух соединений таблицы заказов с самой собой, а при росте глубины связей — рекурсивного CTE, который читается и поддерживается заметно хуже.
Практика: где граф выигрывает и где мешает
Сценарии, где графовая модель естественно подходит:
- Рекомендации и связи «похож на». Общие покупки, общие теги, соседи по графу интересов.
- Обнаружение мошенничества. Поиск колец и общих признаков: одни и те же устройства, адреса, платёжные реквизиты у разных аккаунтов.
- Управление доступом и зависимостями. Кто через какие группы имеет доступ к какому ресурсу; какие сервисы зависят от каких.
- Сетевые топологии и влияние. Распространение сбоя по графу зависимостей, маршрутизация, планирование.
- Знание и онтологии. Связи между сущностями с разными типами отношений, которые в реляционной схеме превратились бы в десятки таблиц связей.
Сценарии, где граф не нужен:
- Простой CRUD с одним-двумя соединениями. Реляционная база справится лучше и знакомее для команды.
- Массовые агрегации по всему набору. Колоночные хранилища созданы именно для этого — см. OLAP-хранилища.
- Строгие транзакционные гарантии на большом объёме. Зрелость инструментов и экосистемы у реляционных СУБД выше.
Рекомендации:
- Проектируйте связи так же внимательно, как узлы. Тип связи и её направление — часть модели. Неопределённый тип вроде
RELATED_TOлишает граф преимуществ. - Не превращайте свойства в связи. Если значение используется только как атрибут и по нему не идут обходы, это свойство узла, а не отдельный узел.
- Индексируйте точки входа. Обход начинается с поиска стартового узла; без индекса по идентификатору или метке запрос начнётся с полного перебора. О работе индексов — в статье про индексы PostgreSQL.
- Ограничивайте глубину обхода. Запрос без ограничения глубины на связанном графе может обойти всю базу. Практика — явные границы
*1..4и лимиты на число результатов. - Проверяйте на реальных данных. Преимущество графа проявляется на связных данных; если граф распадается на мелкие несвязанные компоненты, выигрыша не будет.
Практический сценарий: поиск связанных аккаунтов в реляционной схеме не сходился по времени
Контекст: антифрод-сервис искал аккаунты, связанные общими устройствами, телефонами и платёжными картами. Схема на PostgreSQL работала, но запрос с тремя уровнями связей выполнялся 40 секунд.
- Диагностика. План показывал вложенные соединения таблицы связей с самой собой; каждый уровень добавлял порядок к времени. Индексы были на месте, но число промежуточных строк на втором уровне достигало миллионов.
- Что перенесли. В графовую базу вынесли только подграф связей: аккаунты, устройства, телефоны, карты и рёбра между ними. Основную бизнес-схему оставили в PostgreSQL.
- Запрос. Поиск всех аккаунтов в пределах трёх переходов от подозрительного занял в среднем 90 мс при том же объёме данных.
- Интеграция. Подграф обновлялся по потоку изменений из основной базы через CDC; задержка синхронизации — несколько секунд, что для задачи допустимо. О механизме — в статье про CDC.
- Что не переносили. Отчёты и агрегации остались в хранилище: граф не ускоряет массовые подсчёты.
Вывод: графовая база редко заменяет основную систему. Она решает конкретный класс запросов — обход связей на несколько уровней, — где реляционная модель деградирует из-за соединений.
FAQ
Обязательно ли использовать Neo4j? Нет. Модель графа поддерживают Amazon Neptune, JanusGraph, ArangoDB, а также расширения для реляционных СУБД, включая Apache AGE для PostgreSQL. Выбор зависит от требуемых гарантий, экосистемы и знакомства команды.
Что такое Gremlin и чем он отличается от Cypher? Gremlin — язык обхода графа из проекта Apache TinkerPop, императивный по стилю: запрос описывается как последовательность шагов. Cypher декларативен и визуально повторяет структуру графа. Оба решают одну задачу.
Можно ли хранить граф в реляционной базе? Да, через таблицу рёбер. Это работает для неглубоких обходов и даёт привычные транзакции. Проблемы начинаются на трёх и более уровнях связей, где рекурсивные запросы становятся дорогими и трудными для поддержки.
Насколько сложно мигрировать на графовую модель? Основная работа — не перенос данных, а переосмысление схемы: то, что было таблицей связей, становится типизированным ребром со свойствами. Команде также нужно освоить новый язык запросов.
Источники: Neo4j docs (getting started, Cypher manual), Apache TinkerPop docs — ссылки в шапке.