在单个事务中将 2M 节点/关系插入 Neo4J 永远不会成功



我最近一直在玩neo4j/cypher来存储对象。 我正在使用带有neo4j的netcore。驱动程序 v1.5。

一切都很好,直到我需要更高的性能,因为我需要在运行时存储数百万个节点/关系。

在花了一些时间挖掘其他一些问题和文章之后,我决定重构我以"幼稚"的方式调用neo4j时所拥有的东西。(每次创建 1 个请求)

我现在通过以下方式使用查询参数来减少查询数量:

用于节点创建

MERGE (model:EngineeringModel {id: $engineeringModelId})
WITH model
UNWIND $nodes AS node
CREATE (thing:Label1:Label2:LabelN)<-[:Model]-(model)
SET thing = node

其中$engineeringModelId是一个字符串,$nodes对应于每个节点属性的对象数组。

用于创建关系

MATCH (model:EngineeringModel {id: $engineeringModelId}) USING INDEX 
model:EngineeringModel(id) 
UNWIND $relationships AS relationship 
MATCH (s:Label1 {id: relationship.source}) USING INDEX s:Label1(id) 
UNWIND relationship.sourceRelationships AS sourceRelationship 
UNWIND sourceRelationship.targets AS target 
MATCH (t:IIdentifiable {id: target}) USING INDEX t:IIdentifiable(id) 
CALL apoc.create.relationship(s, sourceRelationship.type, {{}}, t) YIELD rel 
RETURN rel

哪里 $engineeringModelId是一个字符串, $relationships 是一个对象数组,其中包含源节点及其所有目标/关系类型。

我设法在 5 分钟内使用事务函数保存了大约 1M 个节点/关系,并将请求拆分为多达 20000 个对象。(拆分是在 C# 代码中完成的)

using (var session = Driver.Session())
{
session.WriteTransaction(tx => tx.Run(request, params));
}

这样做的问题是,在每 20000 个节点/关系执行一次提交的意义上,它不是"事务安全的"。

通过使用 4.3.2.3 中所述的显式事务 neo4j 文档 我无法在合理的时间内执行提交(我从未设法保存)。

this.driverLifeCycle.BeginTransaction(); // connect and start a new transaction
this.DriverLifeCycle.Transaction.Run(cqlQuery.ToString(), parameters); xN times
this.driverLifeCycle.CommitTransaction(); // commit the transaction

以下是我为 docker 容器提供的配置:

NEO4J_dbms_memory_pagecache_size=2G 
NEO4J_dbms_memory_heap_maxSize=2G 
NEO4J_dbms_memory_heap_initial__size=2G 

有人知道如何使此显式事务正常工作吗?

非常感谢大家的阅读!

因此,经过一些调查以及neo4j社区对neo4j-slack的一些输入,似乎不可能直接在我的neo4j版本(v 3.2.5)中使用.netcore的驱动程序v1.5实现我想要的东西,并且需要"手动"处理事务的解决方法。

解决方法基本上如下:

  • 为要在当前事务中创建的所有节点添加一个额外的TMP标签,其中包含在 POST 请求上生成的transactionId属性,
  • 在事务期间使用相同的事务 ID 创建的关系上添加额外的属性事务 ID

我还每几千个(在我的例子中是 10k)提交一次节点/关系,以便 neo4j 可以处理它并确保包含这些额外的标签和属性。

在操作结束时:

  • 成功后,我删除了应用于节点和关系的额外标签和属性

    MATCH (tmpNode:TMP { transactionId: $transactionId })
    USING INDEX tmpNode:TMP(transactionId)
    REMOVE tmpNode:TMP
    REMOVE tmpNode.transactionId
    MATCH ()-[r { transactionId: $transactionId }]->()
    REMOVE r.transactionId
    
  • 如果出现问题,我会删除创建的所有节点和关系。

    MATCH (tmpNode:TMP { transactionId: $transactionId })
    USING INDEX tmpNode:TMP(transactionId)
    DETACH DELETE tmpNode
    MATCH ()-[tmpRel { transactionId: $transactionId }]->()
    DELETE tmpRel
    

这两个操作都可以简化为一个请求而不是两个请求,但考虑到数据量,这样做更有效。

在我的本地机器上,我设法在大约 10 分钟内运行它,这仍然不是很好,但至少是可行的。

最新更新