Тестируем импорт данных в Neo4j

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Neo4j без преувеличения самая распространенная графовая база данных. Подход «schema free», гибкий язык запросов «cypher» — познакомиться с ней стоит хотя бы для расширения кругозора. Мы в компании Bimeister провели серию экспериментов по переезду на Neo4j для повышения производительности. Под катом я рассмотрю одну из сторон возможного апгрейда — импорт данных в графовую БД, проведу оценку ее преимуществ и недостатков и оценю время загрузки каждым из способов.

Если обратиться к документации, то СУБД предоставляет два основных способа импорта: загрузку CSV-файлов через утилиту администратора neo4j-admin и загрузку все тех же файлов запросом с клиента. Также есть третий способ, который не раскрыт в документации, но об этом чуть ниже. Для сравнения все варианты буду оценивать по следующим критериям:

  • возможность импорта на любом этапе цикла жизни БД;

  • возможность промежуточного сохранения результата;

  • возможность использования для массовых DM-операций;

  • возможность передачи данных в соединении.

NEO4J-ADMIN

Импорт с помощью утилиты администратора рассчитан на инициализацию пустой БД. Вы не сможете дописать или обновить уже созданную БД. Все данные необходимо разбить на вершины и связи в отдельные CSV-файлы. Импорт осуществляется одной транзакцией, передача данных — через файловую систему.

LOAD CSV

Импорт с оператором LOAD CSV возможен с клиента СУБД и рассчитан на порции данных до 10М записей. Есть возможность разбивать на транзакции в конструкции оператора. Этот вариант импорта можно сочетать с командами удаления и обновления данных. Основным требованием остается передача данных через CSV-файлы.

UNWIND

Третий способ загрузки заключается в возможности языка «cypher» работать с коллекциями и словарями c помощью оператора UNWIND. В качестве параметров запроса можно передавать коллекции, содержащие словари, содержащие коллекции, содержащие словари... ну, вы поняли. Таким образом можно импортировать коллекции вложенных объектов, передавать данные напрямую в соединении с СУБД и обрабатывать все одним запросом.

 

Жизненный цикл БД

Промежуточное сохранение

DM-операции

Данные в соединении

NEO4J-ADMIN

-

-

-

-

LOAD CSV

+

+

+

-

UNWIND

+

+

+

+

Сравнение скорости импорта

Для сравнения скорости загрузки использовались данные об иерархии ~1M объектов (~100K связей). Импорт через утилиту neo4j-admin и LOAD CSV требует подготовки файлов в формате CSV с заголовками:

  • Nodes (Uid,Title)

  • Edges (ParentId,ChildId)

NEO4J-ADMIN

Импорт с помощью утилиты администратора выполняется одной командой с указанием всех файлов:

docker run -v " $(pwd)/data:/data" \
-e "NEO4J_dbms_directories_import:/import" \
-v "$(pwd)/import:/import" \
neo4j:4.4.3-community bin/neo4j-admin import \
--nodes="Exemplar=/import/nodes.csv" \
--relationships="HAS_COMPOSITION=/import/edges.csv"   

LOAD CSV

Импорт с оператором LOAD CSV подразумевает загрузку каждого файла в отдельности. Для выполнения запросов можно воспользоваться утилитой «cypher-shell»:

docker run -v " $(pwd)/data:/data" \
-e "NEO4J_dbms_directories_import:/import" \
-v "$(pwd)/import:/import" \
neo4j:4.4.3-community bin/cypher-shell
  1. В первую очередь необходимо создать индекс на ключевое поле импортируемых вершин:

create constraint exemplar_uid for (n:Exemplar) require n.Uid is unique;

2. Загрузить вершины:

USING PERIODIC COMMIT 10000
LOAD CSV WITH HEADERS FROM 'file:///nodes.csv' as line
CREATE (:Exemplar {Uid: line.Uid, Title: line.Title});
  1. Загрузить связи:

USING PERIODIC COMMIT 10000
LOAD CSV WITH HEADERS FROM 'file:///edges.csv' as line
MATCH (p:Exemplar {Uid: line.parentId}), (c:Exemplar {Uid: line.childId})
MERGE (p)-[:HAS_COMPOSITION]->(c);

UNWIND

Для импорта оператором UNWIND необходимо написать клиент на одном из поддерживаемых языков. Данные нужно сгруппировать в коллекцию объектов со связями. В моем случае возможны два способа:

{
  "Uid": "...",
  "Title": "...",
  "ParentId": "..."
}

Или

{
  "Uid": "...",
  "Title": "...",
  "ChildIds": [...]
}
  1. Также сперва стоит создать индекс:

create constraint exemplar_uid for (n:Exemplar) require n.Uid is unique;
  1. В первом случае импорт выполняется запросом:

UNWIND $page as inExemplar
MERGE (c:Exemplar {Uid: inExemplar.Uid})
SET c.Title = inExemplar.Title
WITH c, inExemplar.ParentId as parentId
WHERE parentId is not null
MERGE (p:Exemplar {Uid: parentId})
MERGE (p)-[:HAS_COMPOSITION]->(c)

$page — параметр, в котором передается коллекция объектов.

  1. Во втором случае:

UNWIND $page as inExemplar
MERGE (p:Exemplar {Uid: inExemplar.Uid})
SET p.Title = inExemplar.Title
WITH p, inExemplar.ChildIds as childIds
WHERE not isEmpty(childIds)
UNWIND childIds as childId
MERGE (c:Exemplar {Uid: childId})
MERGE (p)-[:HAS_COMPOSITION]->(c)

В результате эксперимента были получены следующие результаты:

 

Время импорта

NEO4J-ADMIN

34s

LOAD CSV

1m 37s

UNWIND1

2m 07s

UNWIND2

2m 55s

В случае с импортом оператором UNWIND на скорость загрузки оказывает влияние способ группировки данных, наличие вложенных операций увеличивает время.

Вывод

Импорт через утилиту NEO4J-ADMIN и оператор LOAD CSV — это инструменты администратора, которые позволяют инициализировать или редактировать БД из консоли. Самый быстрый способ, с помощью утилиты администратора, имеет больше всего ограничений. Главный недостаток в этих подходах — передача данных через файловую систему. Такой способ требует дополнительных усилий по организации общего файлового хранилища между клиентом и сервером. В условиях, когда СУБД используется в микросервисной архитектуре, такое ограничение может быть весьма неприятным. С другой стороны, импорт с оператором UNWIND дает большую степень свободы при организации архитектуры приложения, хотя и показывает меньшую скорость загрузки. Для создания массовых DM операций оператор UNWIND, на мой взгляд, подходит наилучшим образом.

Литература

  1. Рейтинг графовых бд: https://eandy.ru/oop/samye-populyarnye-grafovye-subd-dlya-c

  2. LOAD JSON from URL AS Data: https://neo4j.com/blog/cypher-load-json-from-url/

  3. Efficient Neo4j Data Import Using Cypher-Scripts: https://medium.com/neo4j/efficient-neo4j-data-import-using-cypher-scripts-7d1268b0747

  4. Fast Batched Updates of Graph Structures: https://medium.com/neo4j/5-tips-tricks-for-fast-batched-updates-of-graph-structures-with-neo4j-and-cypher-73c7f693c8cc

  5. Data Import Decisions: Why Choose Kettle for Neo4j Data Import: https://jennifer-reif.medium.com/data-import-decisions-why-choose-kettle-for-neo4j-data-import-1bd91ab85300

Источник: https://habr.com/ru/company/bimeister/blog/665230/


Интересные статьи

Интересные статьи

Ранее мы рассмотрели способы расчёта нарастающего (накопительного) итога в SQL. Самый распространённый вопрос - как посчитать тоже самое, но на данных с разрывами? После написания исходной статьи мне ...
Hystax — подходящее решение для миграции, если нужно перенести ВМ с Linux или Windows между разными платформами: VMware, OpenStack, AWS и так далее. C его помощью можно переехать на любую из этих плат...
Привет, Мы хотим поделиться небольшой историей, почему наша команда разработки Computer Vision Annotation Tool заинтересовалась поддержкой разметки медицинских данных и к...
Как устроены индексы в MySql, чем отличается индексирование в двух наиболее популярных движках MyISAM и InnoDb, чем первичные ключи отличаются от простого индекса, что та...
Хабр, привет! Мой прошлый пост про отечественное ПО вызвал бурю эмоций. Не знаю почему, но после выхода публикации создатели AlterOffice спешно вытерли все следы присутствия linux-дистрибутива св...