Модель и Cypher — это как хранить и как спрашивать. Осталось главное для практики: как спроектировать граф, чтобы запросы были быстрыми, и как он живёт в проде.
Проектируйте от запросов, а не от сущностей
Реляционную схему часто рисуют от сущностей: вот таблицы, вот их поля, связи добавим потом. В графе наоборот — сначала запросы. Ключевой вопрос: «на какие обходы должна быстро отвечать база». Из ответов растёт структура: что делать узлом, что свойством, куда направить рёбра.
Узел или свойство. Правило простое: если по значению нужно ходить по связям — это узел; если значение только читают вместе с сущностью — это свойство. Город, в котором живёт человек, — свойство city, пока вам не понадобилось «найти всех в этом городе» или «города одной страны»: тогда :City становится узлом со своими рёбрами. Свойство нельзя обойти, узел — можно.
Направление связи задают по смыслу и по частым запросам: КУПИЛ идёт от человека к товару. Обходить связь можно в любую сторону, направление не мешает чтению — но осмысленное направление делает модель понятной и помогает планировщику.
Свойства на рёбрах — то, чего нет в реляционной модели даром. «С какого года работает», «сумма перевода», «дата дружбы» живут на самом ребре, а не в отдельной таблице связи. Это делает граф компактнее и выразительнее.
Индексы и ограничения уникальности
Index-free adjacency ускоряет обход, но у любого обхода есть точка входа — узел, с которого он начинается. Найти этот стартовый узел по свойству (например, Person {имя: 'Иван'}) без индекса — это полное сканирование всех узлов метки. Поэтому:
- Индекс на свойство метки (
CREATE INDEX FOR (p:Person) ON (p.email)) нужен для всех свойств, по которым запрос «заземляется» на конкретный узел. Это первое, что проверяют, когда запрос медленный. - Ограничение уникальности (
CREATE CONSTRAINT FOR (p:Person) REQUIRE p.email IS UNIQUE) не только гарантирует уникальность, но и создаёт индекс. Оно критично дляMERGE: без уникального ключаMERGEработает медленно и рискует наплодить дубли при параллельной записи.
Индексы в Neo4j ускоряют поиск стартового узла, а не сам обход — обход и так дёшев. Это переворачивает привычку из SQL, где индекс нужен под каждый JOIN.
Супер-узлы: главная ловушка
Самая частая проблема производительности в графе — супер-узел (supernode): узел с гигантским числом рёбер. Скажем, узел «страна Россия», к которому привязаны десятки миллионов людей, или популярный товар с миллионами покупок. Обход через такой узел упирается в перебор всех его рёбер и убивает скорость.
Лечат по-разному: выносят частые фильтры в свойства рёбер, разбивают супер-узел на подкатегории (не «город», а «район города»), либо моделируют так, чтобы обход не шёл через горячий центр. Главное — замечать супер-узлы при проектировании, а не ловить их потом на проде.
Эксплуатация: память, бэкапы, масштаб
- Память. Neo4j держит горячую часть графа в кэше страниц; чем больше графа помещается в память, тем быстрее обход. Это первый ресурс, за которым следят: рабочее множество должно влезать в page cache.
- Бэкапы. Как у любой базы — регулярные резервные копии и проверка восстановления. Отдельная база в проде — это отдельная цена: репликация, бэкапы, мониторинг, ещё одна система в голове у команды.
- Масштаб. Neo4j масштабируется прежде всего вертикально (больше памяти и ядер на один узел) и через реплики на чтение (причинно-согласованные копии для читающих запросов). Шардинг графа — разрезание связного графа на машины — сложен по своей природе (связь может пересечь границу шарда) и нужен реже, чем кажется; сначала выжимают вертикаль и реплики.
Где это применяется
Модель «от запросов» и внимание к точкам входа и супер-узлам — то, что отличает рабочий граф от медленного. Практический порядок такой: описать нужные обходы → решить узел/свойство/ребро → поставить индексы и ограничения уникальности на стартовые свойства → проверить, нет ли супер-узлов.
Где спотыкаются начинающие:
- Нет индекса на стартовое свойство — и каждый запрос начинается с полного сканирования узлов. Обход быстрый, а вход — нет.
MERGEбез ограничения уникальности — медленно и с риском дублей при конкурентной записи.- Тянут графовую СУБД под одну иерархию — дерево категорий или оргструктура решаются рекурсивным SQL в той базе, что уже есть; вторая база в эксплуатации дороже.
- Сразу думают о шардинге — почти всегда раньше времени; вертикаль и реплики закрывают большинство нагрузок.
Что почитать дальше: SQL или графовая СУБД — нужен ли граф вообще; Neo4j: применение — рекомендации, антифрод, граф знаний и GraphRAG; графы и взвешенные графы — как устроены обходы и кратчайшие пути под капотом.