Граф как модель данных: property graph
Четыре кирпичика property graph — узлы, связи, метки, свойства — и почему именно эта модель прижилась.
Property graph (граф свойств) — модель данных, где есть узлы и направленные связи между ними, а у тех и других могут быть метки (тип) и произвольные свойства в виде пар «ключ-значение».
Зачем вообще отдельная модель
Существует несколько способов формально описать граф для базы данных. Историческое решение из мира семантического веба — RDF, где всё разбито на тройки «субъект — предикат — объект». Это мощно и стандартизировано, но многословно: чтобы повесить на связь один атрибут, приходится придумывать дополнительные узлы. Property graph выбрал прагматичный компромисс — разрешить свойства прямо на узлах и на связях. Эта простота и сделала модель популярной у разработчиков: она ближе к тому, как мы интуитивно рисуем схему на доске, и почти не требует «обвязки» вокруг данных.
Из чего состоит граф свойств
Neo4j и большинство современных графовых СУБД используют именно модель property graph. В ней всего четыре понятия, и их стоит запомнить твёрдо — на них держится весь язык запросов.
- Узел (node) — сущность: человек, фильм, город, аккаунт.
- Связь (relationship) — направленное ребро между двумя узлами: ДРУЖИТ, СНЯЛСЯ_В, КУПИЛ. У связи всегда есть тип и направление.
- Метка (label) — «тип» узла:
:Person,:Movie. У узла может быть несколько меток. - Свойство (property) — пара ключ-значение на узле или связи:
name: 'Алиса',since: 2020.
Маленький мир в одной схеме
Соберём крошечную киновселенную. Прямоугольник — узел с меткой, стрелка — связь с типом, в фигурных скобках — свойства:
┌─────────────────────┐ СНЯЛСЯ_В ┌────────────────────┐
│ :Person │ ─────────────────────> │ :Movie │
│ name: 'Киану Ривз' │ {role:'Нео'} │ title:'Матрица' │
│ born: 1964 │ │ year: 1999 │
└─────────────────────┘ └────────────────────┘Обратите внимание: свойство role: 'Нео' висит на связи, а не на узле. Роль — это характеристика именно участия актёра в конкретном фильме, и в графе ей место на ребре. Это одна из суперспособностей property graph: связи — полноценные объекты со своими атрибутами.
Чтобы прочувствовать, насколько это удобно, спросите себя: где хранить, что Киану Ривз снялся в «Матрице» в роли Нео, а в «Джоне Уике» — в роли Джона? Свойство «роль» нельзя повесить ни на сам узел актёра (ролей много), ни на узел фильма (актёров много). Естественное место — это конкретная связь между конкретным актёром и конкретным фильмом. Property graph даёт это место бесплатно. В реляционной модели тот же атрибут жил бы колонкой в таблице-связке, и чтобы им воспользоваться, пришлось бы тащить эту колонку через JOIN.
Почему метки, а не таблицы
Метка играет роль «таблицы» из SQL, но мягче. Один узел может одновременно нести метки :Person и :Director — человек, который и снимается, и режиссирует. В реляционной модели это потребовало бы либо лишних таблиц, либо костылей. В графе вы просто навешиваете вторую метку. Свойства тоже не привязаны к жёсткой схеме: у одного :Person есть twitter, у другого нет — и это нормально.
Эта гибкость — палка о двух концах, и важно понимать обе стороны. С одной стороны, граф легко эволюционирует: добавить новый тип связи или новое свойство можно без миграции схемы и без простоя — просто начните их создавать. Модель «прирастает» вместе с пониманием предметной области. С другой стороны, у этой свободы нет встроенного контролёра: ничто не мешает по ошибке записать дату как строку в одном узле и как число в другом. Поэтому в проде поверх гибкой модели обычно ставят ограничения (constraints) и договорённости команды — об этом будет отдельный разговор в разделе про индексы и ограничения.
Несколько меток на одном узле
Возможность повесить на узел несколько меток — не экзотика, а рабочий приём моделирования. Метки можно использовать как «теги-роли»: :Person:Customer:VIP. Тогда запрос по :VIP мгновенно отбирает нужную подгруппу, не перебирая всех людей. Это похоже на наследование классов, но без жёсткой иерархии: узел просто одновременно принадлежит нескольким категориям, и каждая из них даёт свою точку входа для запросов.
Сравнение словарей
| Реляционная модель | Property graph |
| Таблица | Метка узла |
| Строка | Узел |
| Колонка | Свойство |
| Внешний ключ / таблица-связка | Связь (объект со свойствами) |
| JOIN | Обход по связи |
Как работает под капотом
Связь в property graph — двунаправленный объект: она знает свой стартовый и конечный узел, а оба узла знают про эту связь. Поэтому, хотя у связи есть направление (важное для семантики: КУПИЛ ≠ ПРОДАЛ), физически по ней можно ходить в обе стороны. Это позволяет одному ребру (Покупатель)-[:КУПИЛ]->(Товар) отвечать и на вопрос «что купил человек», и на «кто купил товар» — без дублирования.
Частые ошибки
- Путать метку и свойство. Метка — это тип узла (по ней строится индексация и фильтрация), свойство — данные внутри.
:Movie— метка,genre:'sci-fi'— свойство. - Забывать про свойства на связях. «Когда подружились», «оценка», «вес» — часто это атрибут ребра, а не узла.
- Делать связь без направления «на всякий случай». В property graph связь всегда направлена при создании; ненаправленным может быть только обход.
Итоги
- Property graph = узлы + направленные связи + метки + свойства.
- Связи — объекты первого класса: у них есть тип, направление и собственные свойства.
- Метка ≈ таблица, но узел может иметь несколько меток; схема свойств гибкая.
- Направление связи задаётся при создании; ходить по ней обходом можно в любую сторону.