Моделирование реляционных данных с помощью Neo4j

В последние несколько лет произошел взрыв новых парадигм в базах данных. Ранее система управления реляционными базами данных (СУБД), воплощенная в Microsoft SQLServer или Oracle MySQL, была де-факто маршрутом для тех, кто искал базу данных. Я коснулся причин этого и рассмотрел некоторые из новых или повторно открытых альтернатив в одной из моих более ранних работ; В этой статье я собираюсь более подробно изучить одну из них, базу данных Graph, чтобы изучить, что они могут делать, и показать некоторые варианты использования, в которых они наиболее эффективны.

Что такое график?

Графические базы данных, как следует из названия, организуют данные в виде графа, основываясь на математическом принципе теории графов. По сути, мы можем рассматривать граф как набор узлов и ребер. Узлы обычно представляют объекты, края используются для представления отношений между этими объектами. Что делает это полезным для нас с точки зрения баз данных, так это то, что узлы могут хранить данные, описывающие сущность, но ребра могут также содержать данные, описывающие природу и детали этих отношений. Эти данные могут быть такими же простыми, как тип отношений, или гораздо более подробными.

Давайте посмотрим на пример графика и посмотрим, как он выглядит.

На этом графике мы видим, что мы описываем ряд сущностей разных типов. У нас есть «люди» с типичными атрибутами, такими как имя и пол, а также «фильмы» с названием, датой выпуска и бюджетом. Они представлены на схеме пузырьками.

Связующие пузыри для создания структуры графа - это ребра. Они описывают отношения между связанными узлами; например, мы можем видеть, что «Мстители: Финал» режиссировали Энтони и Джозеф Руссо, которые также сняли «Капитан Америка: Зимний солдат», и что в «Финале» участвовал Роберт Дауни-младший.

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

Например, на диаграмме ниже показан приведенный выше график MCU, расширенный типом «персонаж», а также отношениями «появляется в» и «играет».

Теперь, когда у нас есть данные и смоделированные отношения, мы можем начать видеть типы запросов, которые мы можем использовать для извлечения информации из графа. Помимо простых и распространенных функций «найти всех режиссеров» или «найти общий бюджет для всех фильмов с участием Роберта Дауни-младшего», мы также можем вычислить более сложные наборы данных на основе взаимосвязей. Например, мы можем очень быстро найти все фильмы Джозефа и Энтони Руссо с Робертом Дауни-младшим в роли Железного человека. Очевидно, мы могли бы построить схему, которая позволила бы нам делать то же самое в других формах базы данных, включая традиционные СУБД; однако, как мы увидим, когда мы посмотрим на Neo4j, он проще и быстрее в графической базе данных. Это типичные варианты использования, в которых графическая база данных является отличным выбором.

Введите Neo4j

Neo4j - это выдающийся движок графической базы данных, предлагающий ACID-транзакции, а также собственное хранение и обработку графовых данных. Он доступен как в бесплатной версии с открытым исходным кодом, так и в коммерческой лицензированной версии Enterprise. Версия с открытым исходным кодом - это только одноузловая версия, в то время как корпоративная версия поддерживает как кластеризацию, так и поддержку горячего резервного копирования. Обе версии поддерживают язык запросов Cypher, целью которого является использование синтаксиса стиля «ASCII-art», чтобы сделать хранение и выполнение запросов к данным графа максимально простым и логичным.

Собираем наш пример MCU в Neo4j

Во-первых, предположим, что у вас запущен Neo4j или вы используете онлайн-песочницу, которую они предоставляют, нам нужно создать наш график. У нас есть несколько вариантов для этого: мы можем создать данные, используя серию дискретных операций Cypher (зеркальное отображение того, как приложение будет создавать данные в базе данных), мы можем использовать Cypher для загрузки данных из файла, такого как CSV или JSON, или мы можем использовать встроенную функцию ETL Neo4j для загрузки данных из РСУБД, подключенной к JDBC.

Давайте быстро рассмотрим пару, начав с использования импорта файла для создания данных, показанных в первом рассмотренном нами примере.

Во-первых, нам понадобятся данные для нашего графика в файлах CSV, которые мы можем импортировать, вы можете найти набор для использования в моем GitHub. Мы можем использовать их прямо из GitHub, используя следующий Cypher, чтобы импортировать их и построить наш начальный график. Обратите внимание, вам нужно запускать каждую команду отдельно.

Разбивая это на части, сначала мы создаем узлы для людей и фильмов на основе файлов CSV для каждого типа. Затем мы загружаем CSV, которые определяют отношения, и путем сопоставления узлов создаем подходящие отношения. Наконец, поскольку поле id было необходимо только для построения отношений из нереляционных файлов, мы удаляем его из узлов. Это ключ к тому, чтобы не относиться к графовым базам данных - поскольку отношения - это первоклассный гражданин, нам больше не нужно беспокоиться об идентификаторах для создания внешних ключей (ура!).

Теперь у нас есть исходные данные, давайте запустим несколько операций Cypher, чтобы расширить их до полного графика, показанного во втором из наших примеров. Сначала мы можем запустить следующее, чтобы создать нашего первого персонажа

CREATE (character:Character {name: "iron man"})

А затем мы можем связать его в график с парой отношений:

MATCH (person:Person {name: "robert downey jnr"}), (character:Character {name: "iron man"}) CREATE (person)-[:played]->(character)

MATCH (character:Character {name: "iron man"}), (movie:Movie {name: "avengers endgame"}) CREATE (character)-[:appeared_in]->(movie)

Как только мы это сделаем, мы можем использовать встроенный браузер Neo4j, чтобы запросить график и проверить, что у нас есть

Выполнение некоторых запросов

Теперь, когда мы построили наш график, давайте рассмотрим некоторые запросы, которые мы можем создать с помощью Cypher, чтобы изучить его. Вы можете запустить их прямо в браузере, который предоставляет Neo4j, чтобы увидеть результаты.

Во-первых, давайте посмотрим, что мы можем узнать о Avengers Endgame.

MATCH (m:Movie {name: "avengers endgame"}) RETURN m

Это возвращает все узлы в нашем графе, которые имеют тип Movie и имя «avengers endgame»). В традиционной СУБД это, скорее всего, будет запрос в стиле SELECT * FROM t WHERE x = y.

Теперь давайте посмотрим на что-то более сложное, давайте найдем все фильмы, которые снял Джозеф Руссо.

MATCH m=(p:Person {name: “joseph russo"})-[r:Directed]->() RETURN m

Этот запрос вернет и Captain America Winter Soldier, и Avengers Engame. Опять же, мы могли бы смоделировать это в РСУБД, используя три таблицы с внешними ключами, а затем создать оператор Select, чтобы присоединиться к ним. Однако именно в этих типах запросов Neo4j превосходит по производительности реляционную базу данных; поскольку Neo4j имеет отношения, которые эффективно хранятся, поскольку указатели, следующие за ними, являются тривиальной операцией по сравнению с алгоритмами соединения, используемыми РСУБД. Синтаксис также обычно более очевиден, чем операторы SQL, состоящие из нескольких типов соединений, и нам не нужно беспокоиться о внешних ключах.

Давайте воспользуемся этой теорией для выполнения более сложного запроса, более глубоко изучив отношения между людьми и фильмами. Следующий Сайфер возвращает отрывок из нашего графика, показывающий фильмы, которые были сняты Джозефом Руссо, и в которых Роберт Дауни-младший играет в роли актера.

MATCH m=(director:Person {name: “joseph russo"})-[r.Directed]->(movie:Movie)<-[a:ActedIn]-(actor:Person {name: "robert downey jnr"}) RETURN director, movie, actor

Как видите, сейчас мы строим довольно сложные запросы, основанные на отношениях, и опять же никаких сложных соединений или внешних ключей в поле зрения.

Если мы хотим выполнить агрегирование, например, чтобы найти общий бюджет фильмов, возвращенных выше, мы немного изменим запрос, как показано ниже.

MATCH m=(director:Person {name: “joseph russo"})-[r.Directed]->(movie:Movie)<-[a:ActedIn]-(actor:Person {name: "robert downey jnr"}) RETURN SUM(movie.budget)

Более сложные отношения

До сих пор мы рассматривали довольно простые отношения, которые определяют только природу отношений. Neo4j позволяет нам пойти дальше и добавить данные в отношения; мы построим новый график для отображения боев в MCU, чтобы увидеть, как это работает.

На нашем новом графике выше изображены несколько героев и злодеев MCU, а также их эпические кинематографические битвы в трех фильмах. Узлы представляют персонажей и имеют тип, обозначающий, являются ли они «героем» или «злодеем», а реальные корабли показывают, где эти персонажи сражались друг с другом. В этом примере конфликтная взаимосвязь также включает дополнительные данные для обозначения того, в каком фильме происходит битва. Учитывая количество ударов в MCU, это уже довольно сложный график, на который нужно смотреть, но мы можем использовать Cypher, чтобы начать его декодирование.

Во-первых, давайте воспользуемся этими дополнительными данными, чтобы найти имена всех, кто сражался с Таносом в Avengers Infinity War.

MATCH (hero:Character)-[:fought {film: "avengers infinity war"}]->(villain:Character {name: "thanos"}) RETURN hero.name

Если мы запустим это, мы получим четыре имени: Железный человек, Капитан Америка, Вижен и Черная вдова. Давайте теперь узнаем, кто сражался с Таносом в Engame, но не в Infinity Wars.

Match (hero:Character)-[:fought {film: "avengers infinity war"}]->(villain:Character {name: "thanos"}), (hero2:Character)-[:fought {film: "avengers endgame"]->(villain:Character {name:"thanos"}) WITH COLLECT (distinct hero.name) AS AIWheros, COLLECT (distinct hero2.name) as AEheros RETURN [x in AEheros WHERE NOT (x in AIWheros)]

Запустите это, и мы обнаружим, что Человек-Муравей сражался с Таносом только в Финале. Мы рассчитали это, создав коллекцию героев в каждом из фильмов «Война бесконечности» и «Финал», используя в основном тот же запрос, что и раньше, а затем возвращая их пересечение.

Добавляя метаданные к нашим отношениям, мы можем использовать Chypher, чтобы начать разбираться в очень сложных графах.

Заключение

Мы начали с того, что немного рассмотрели теорию графов и то, как она поддерживает класс баз данных NoSQL, которые мы можем использовать в наших решениях.

Затем мы посмотрели на Neo4j, как мы можем использовать его для создания графиков данных, а затем строить запросы для извлечения информации и значения из данных и особенно отношений.

Мы увидели, что графовые базы данных являются невероятно мощным инструментом для определенных классов проблем и что, хотя мы можем решать их с помощью более традиционных механизмов баз данных, графовые базы данных предоставляют гораздо более оптимизированное решение.