Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Графовые базы данных: Neo4j и модель свойств

Графовые базы данных: когда связи важнее таблиц

Графовые базы: модель свойств, Cypher, index-free adjacency, сценарии применения и границы, за которыми граф не нужен.

Прямой ответ

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

Модель свойств (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, который читается и поддерживается заметно хуже.

Практика: где граф выигрывает и где мешает

Сценарии, где графовая модель естественно подходит:

  1. Рекомендации и связи «похож на». Общие покупки, общие теги, соседи по графу интересов.
  2. Обнаружение мошенничества. Поиск колец и общих признаков: одни и те же устройства, адреса, платёжные реквизиты у разных аккаунтов.
  3. Управление доступом и зависимостями. Кто через какие группы имеет доступ к какому ресурсу; какие сервисы зависят от каких.
  4. Сетевые топологии и влияние. Распространение сбоя по графу зависимостей, маршрутизация, планирование.
  5. Знание и онтологии. Связи между сущностями с разными типами отношений, которые в реляционной схеме превратились бы в десятки таблиц связей.

Сценарии, где граф не нужен:

  1. Простой CRUD с одним-двумя соединениями. Реляционная база справится лучше и знакомее для команды.
  2. Массовые агрегации по всему набору. Колоночные хранилища созданы именно для этого — см. OLAP-хранилища.
  3. Строгие транзакционные гарантии на большом объёме. Зрелость инструментов и экосистемы у реляционных СУБД выше.

Рекомендации:

  1. Проектируйте связи так же внимательно, как узлы. Тип связи и её направление — часть модели. Неопределённый тип вроде RELATED_TO лишает граф преимуществ.
  2. Не превращайте свойства в связи. Если значение используется только как атрибут и по нему не идут обходы, это свойство узла, а не отдельный узел.
  3. Индексируйте точки входа. Обход начинается с поиска стартового узла; без индекса по идентификатору или метке запрос начнётся с полного перебора. О работе индексов — в статье про индексы PostgreSQL.
  4. Ограничивайте глубину обхода. Запрос без ограничения глубины на связанном графе может обойти всю базу. Практика — явные границы *1..4 и лимиты на число результатов.
  5. Проверяйте на реальных данных. Преимущество графа проявляется на связных данных; если граф распадается на мелкие несвязанные компоненты, выигрыша не будет.

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

Контекст: антифрод-сервис искал аккаунты, связанные общими устройствами, телефонами и платёжными картами. Схема на PostgreSQL работала, но запрос с тремя уровнями связей выполнялся 40 секунд.

  1. Диагностика. План показывал вложенные соединения таблицы связей с самой собой; каждый уровень добавлял порядок к времени. Индексы были на месте, но число промежуточных строк на втором уровне достигало миллионов.
  2. Что перенесли. В графовую базу вынесли только подграф связей: аккаунты, устройства, телефоны, карты и рёбра между ними. Основную бизнес-схему оставили в PostgreSQL.
  3. Запрос. Поиск всех аккаунтов в пределах трёх переходов от подозрительного занял в среднем 90 мс при том же объёме данных.
  4. Интеграция. Подграф обновлялся по потоку изменений из основной базы через CDC; задержка синхронизации — несколько секунд, что для задачи допустимо. О механизме — в статье про CDC.
  5. Что не переносили. Отчёты и агрегации остались в хранилище: граф не ускоряет массовые подсчёты.

Вывод: графовая база редко заменяет основную систему. Она решает конкретный класс запросов — обход связей на несколько уровней, — где реляционная модель деградирует из-за соединений.

FAQ

Обязательно ли использовать Neo4j? Нет. Модель графа поддерживают Amazon Neptune, JanusGraph, ArangoDB, а также расширения для реляционных СУБД, включая Apache AGE для PostgreSQL. Выбор зависит от требуемых гарантий, экосистемы и знакомства команды.

Что такое Gremlin и чем он отличается от Cypher? Gremlin — язык обхода графа из проекта Apache TinkerPop, императивный по стилю: запрос описывается как последовательность шагов. Cypher декларативен и визуально повторяет структуру графа. Оба решают одну задачу.

Можно ли хранить граф в реляционной базе? Да, через таблицу рёбер. Это работает для неглубоких обходов и даёт привычные транзакции. Проблемы начинаются на трёх и более уровнях связей, где рекурсивные запросы становятся дорогими и трудными для поддержки.

Насколько сложно мигрировать на графовую модель? Основная работа — не перенос данных, а переосмысление схемы: то, что было таблицей связей, становится типизированным ребром со свойствами. Команде также нужно освоить новый язык запросов.

Источники: Neo4j docs (getting started, Cypher manual), Apache TinkerPop docs — ссылки в шапке.