Создание нескольких миллионов отношений в Neo4j занимает очень много времени

#.net #csv #neo4j

#.net #csv #neo4j

Вопрос:

Я использую последнюю версию пакета Neo4j.Driver (4.2.0) и последнюю версию сообщества сервера Neo4j (4.2.3).

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

У меня есть 4 CSV-файла:

  1. XyzTypes.csv — определяет 96 328 узлов типа.
  2. XyzMethods.csv — определяет 975 507 методов для всех типов.
  3. XyzTypeTypeDependencies.csv — определяет 121 834 отношения типа-типа DEPENDS_ON.
  4. XyzTypeMethods.csv — определяет 973 972 типа -метод ОБЪЯВЛЯЕТ отношения.

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

Вот мой код:

 var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "1"));
var session = driver.AsyncSession(o => o.WithDatabase("neo4j"));
try
{
    Console.Write("[DI");
    await session.RunAsync("DROP INDEX type_id_index IF EXISTS");
    await session.RunAsync("DROP INDEX method_id_index IF EXISTS");

    Console.Write("][C");
    await session.WriteTransactionAsync(async tx =>
    {
        await tx.RunAsync("match ()-[r]->() delete r");
        await tx.RunAsync("match (n) delete n");
        return default(object);
    });

    Console.Write("][T");
    await session.WriteTransactionAsync(async tx =>
    {
        await tx.RunAsync(@"
LOAD CSV WITH HEADERS FROM 'file:///C:/Temp/XyzTypes.csv' AS line
CREATE (:Type {
    typeId: toInteger(line.id),
    name: line.name,
    fullName: line.fullName,
    isCompilerGenerated: toBoolean(line.isCompilerGenerated),
    asmName: line.asmName
})");
        return default(object);
    });

    Console.Write("][M");
    await session.WriteTransactionAsync(async tx =>
    {
        await tx.RunAsync(@"
LOAD CSV WITH HEADERS FROM 'file:///C:/Temp/XyzMethods.csv' AS line
CREATE (:Method {
    methodId: toInteger(line.id),
    name: line.name,
    fullName: line.fullName,
    isCompilerGenerated: toBoolean(line.isCompilerGenerated)
})");
        return default(object);
    });

    Console.Write("][CI");
    await session.RunAsync("CREATE INDEX type_id_index FOR (t:Type) ON (t.typeId)");
    await session.RunAsync("CREATE INDEX method_id_index FOR (m:Method) ON (m.methodId)");
    
    Console.Write("][TT");
    await session.RunAsync(@"
USING PERIODIC COMMIT LOAD CSV WITH HEADERS FROM 'file:///C:/Temp/XyzTypeTypeDependencies.csv' AS line
MATCH (src:Type), (dst:Type)
WHERE src.typeId = toInteger(line.src) AND dst.typeId = toInteger(line.dst)
CREATE (src)-[:DEPENDS_ON]->(dst)
");

    Console.Write("][TM");
    await session.RunAsync(@"
USING PERIODIC COMMIT LOAD CSV WITH HEADERS FROM 'file:///C:/Temp/XyzTypeMethods.csv' AS line
MATCH (src:Type), (dst:Method)
WHERE src.typeId = toInteger(line.src) AND dst.methodId = toInteger(line.dst)
CREATE (src)-[:DECLARES]->(dst)
");

    Console.Write("] ... ");
}
finally
{
    await session.CloseAsync();
    await driver.CloseAsync();
}
 

CREATE INDEX Запросы возвращаются немедленно. Может быть законным, я не знаю, как быстро Neo4j может индексировать свойство number примерно в 1 млн узлов. Запуск :schema в браузере подтверждает два индекса, но у меня такое чувство, что они не работают.

Выполнение приведенного выше кода занимает почти 3 часа. Что я делаю не так?

ПРАВКА 1

Поэтому я изменил последние два запроса, чтобы использовать MERGE предложение:

     Console.Write("][TT");
    await session.RunAsync(@"
USING PERIODIC COMMIT LOAD CSV WITH HEADERS FROM 'file:///C:/Temp/XyzTypeTypeDependencies.csv' AS line
MERGE (src:Type {typeId: toInteger(line.src)})-[:DEPENDS_ON]->(dst:Type {typeId: toInteger(line.dst)})
");
    
    Console.Write("][TM");
    await session.RunAsync(@"
USING PERIODIC COMMIT LOAD CSV WITH HEADERS FROM 'file:///C:/Temp/XyzTypeMethods.csv' AS line
MERGE (src:Type {typeId: toInteger(line.src)})-[:DECLARES]->(dst:Method {methodId: toInteger(line.dst)})
");
 

Теперь это должно быть намного лучше, потому что я думаю, что то, что я делал раньше, вызывало декартово умножение между узлами. Тем не менее, последний запрос занимает неизвестное количество времени (не знаю, сколько времени на данный момент) — все равно плохо.

Комментарии:

1. Создание индексов и ограничений происходит асинхронно, поэтому вы, вероятно, поторопитесь, прежде чем индексы и ограничения будут установлены. Вероятно, вам следует использовать CALL db.awaitIndexes() на всякий случай. Кроме того, подход СЛИЯНИЯ не рекомендуется, поскольку он должен проверять, существует ли такой шаблон, и, если нет, создавать весь шаблон, что в конечном итоге приведет к дублированию узлов. Лучшим подходом является сопоставление на узлах, а затем либо СОЗДАНИЕ, либо ОБЪЕДИНЕНИЕ отношений (только СЛИЯНИЕ, если rel может уже существовать). Игнорируйте предупреждение о декартовом произведении, это именно то, что вам нужно (1 x 1 на строку) для создания связи.

2. Итак, по сути, вы говорите мне вернуться к версии перед РЕДАКТИРОВАНИЕМ 1 . Я добавлю и добавлю ожидание индексов.

3. @InverseFalcon — пожалуйста, оформите свой комментарий как ответ, чтобы я мог отдать вам должное, потому что это работает! Я думаю db.awaitIndexes , это ключ. Большое вам спасибо.

Ответ №1:

Создание индексов и ограничений происходит асинхронно, поэтому вы, вероятно, поторопитесь, прежде чем индексы и ограничения будут установлены. Вероятно, вам следует использовать CALL db.awaitIndexes() на всякий случай.

Кроме того, подход СЛИЯНИЯ не рекомендуется, поскольку он должен проверять, существует ли такой шаблон, и, если нет, создавать весь шаблон, что в конечном итоге приведет к дублированию узлов. Лучшим подходом является сопоставление на узлах, а затем либо СОЗДАНИЕ, либо ОБЪЕДИНЕНИЕ отношений (только ОБЪЕДИНЕНИЕ, если rel может уже существовать, или если одни и те же узлы могут быть сопоставлены в нескольких строках для заданных входных данных).

Игнорируйте предупреждение о декартовом произведении, это именно то, что вам нужно (1 x 1 на строку) для создания связи.