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
-
В первую очередь необходимо создать индекс на ключевое поле импортируемых вершин:
create constraint exemplar_uid for (n:Exemplar) require n.Uid is unique;
-
Загрузить вершины:
USING PERIODIC COMMIT 10000 LOAD CSV WITH HEADERS FROM 'file:///nodes.csv' as line CREATE (:Exemplar {Uid: line.Uid, Title: line.Title});
-
Загрузить связи:
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": [...] }
-
Также сперва стоит создать индекс:
create constraint exemplar_uid for (n:Exemplar) require n.Uid is unique;
-
В первом случае импорт выполняется запросом:
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 — параметр, в котором передается коллекция объектов.
-
Во втором случае:
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, на мой взгляд, подходит наилучшим образом.
Литература
ссылка на оригинал статьи https://habr.com/ru/company/bimeister/blog/665230/
Добавить комментарий