使用 Neo4j .Net 客户端时边创建速度太慢
Edge creation too slow when using Neo4j .Net client
我有大约 80 万个节点,我正在尝试使用 Neo4j .Net 客户端将大约 800 万条边插入到 Neo4j 企业版中。
我正在做这样的事情,这个过程真的很慢。尝试过 Neo4j 驱动程序,但速度也很慢。我还为名称字段编制了索引。
有人可以建议一种替代方法来更快地创建边缘吗?
public static async Task AddEdges( List<Edge> edgeTable, IGraphClient client)
{
try
{
foreach (var item in edgeTable)
{
await client.Cypher
.Match("(parentNode:MyNodeType)", "(childNode:MyNodeType)")
.Where((MyNodeType parentNode, MyNodeType childNode) => parentNode.Name == item.SourceName && childNode.Name==item.MemberName)
.Create("(childNode)-[:belongsTo]->(parentNode)")
.ExecuteWithoutResultsAsync();
}
}
catch (Exception ex)
{
//ex handling
}
}
您正在使用 await
将每个异步请求(由于您使用 ExecuteWithoutResultsAsync
)转换为同步请求。这意味着当前的 http 请求必须在发送下一个请求之前做出响应,即使您不关心响应。 (顺便说一句,要达到同样的目的,您可以只使用 ExecuteWithoutResults
而不用 await
。)应该尽可能避免这种序列化。但是,鉴于您的用例,并行执行确实存在死锁的可能性(由于创建新关系时端节点的写锁定)。
此外,您只在每个交易请求中执行一个 CREATE
操作。这意味着您正在发出 800 万个序列化事务请求。当然,这会很慢。
一种允许一定程度的异步操作同时避免死锁的解决方案将涉及分析您的数据(理想情况下,以编程方式)以得出与其他组没有重叠节点的边缘组。即使组内的边仍然需要同步处理,不同的组可以并行处理而不会出现死锁。
如果您可以在单个事务中对一个组内的 N 个边执行操作,那么您就可以避免为该组发出 N 个同步事务请求的开销,并且也可以避免上述死锁。
使用 UNWIND
子句,您可以在单个请求中对列表中的数据进行迭代操作。像下面这样的东西应该可以工作。请注意,edgeTable
输入列表必须包含来自单个组的边,如上所述:
public static async Task AddEdges( List<Edge> edgeTable, IGraphClient client) {
try {
client.Cypher
.Unwind(edgeTable, "item")
.Match("(parentNode:MyNodeType)", "(childNode:MyNodeType)")
.Where((MyNodeType parentNode, MyNodeType childNode) => parentNode.Name == item.SourceName && childNode.Name==item.MemberName)
.Create("(childNode)-[:belongsTo]->(parentNode)")
.ExecuteWithoutResultsAsync();
} catch (Exception ex) {
//ex handling
}
}
请注意,我在不使用 await
的情况下使用 ExecuteWithoutResultsAsync,以便异步处理组。
不过,需要注意的是,您不想让 neo4j 服务器一次处理太多,以免内存不足。因此,如果任何组太大,或者同时处理的组太多,您可能希望限制调用 AddEdges 的速率,and/or 将大组拆分为更小的块,并确保这些块彼此同步处理。
我有大约 80 万个节点,我正在尝试使用 Neo4j .Net 客户端将大约 800 万条边插入到 Neo4j 企业版中。
我正在做这样的事情,这个过程真的很慢。尝试过 Neo4j 驱动程序,但速度也很慢。我还为名称字段编制了索引。 有人可以建议一种替代方法来更快地创建边缘吗?
public static async Task AddEdges( List<Edge> edgeTable, IGraphClient client)
{
try
{
foreach (var item in edgeTable)
{
await client.Cypher
.Match("(parentNode:MyNodeType)", "(childNode:MyNodeType)")
.Where((MyNodeType parentNode, MyNodeType childNode) => parentNode.Name == item.SourceName && childNode.Name==item.MemberName)
.Create("(childNode)-[:belongsTo]->(parentNode)")
.ExecuteWithoutResultsAsync();
}
}
catch (Exception ex)
{
//ex handling
}
}
您正在使用 await
将每个异步请求(由于您使用 ExecuteWithoutResultsAsync
)转换为同步请求。这意味着当前的 http 请求必须在发送下一个请求之前做出响应,即使您不关心响应。 (顺便说一句,要达到同样的目的,您可以只使用 ExecuteWithoutResults
而不用 await
。)应该尽可能避免这种序列化。但是,鉴于您的用例,并行执行确实存在死锁的可能性(由于创建新关系时端节点的写锁定)。
此外,您只在每个交易请求中执行一个 CREATE
操作。这意味着您正在发出 800 万个序列化事务请求。当然,这会很慢。
一种允许一定程度的异步操作同时避免死锁的解决方案将涉及分析您的数据(理想情况下,以编程方式)以得出与其他组没有重叠节点的边缘组。即使组内的边仍然需要同步处理,不同的组可以并行处理而不会出现死锁。
如果您可以在单个事务中对一个组内的 N 个边执行操作,那么您就可以避免为该组发出 N 个同步事务请求的开销,并且也可以避免上述死锁。
使用 UNWIND
子句,您可以在单个请求中对列表中的数据进行迭代操作。像下面这样的东西应该可以工作。请注意,edgeTable
输入列表必须包含来自单个组的边,如上所述:
public static async Task AddEdges( List<Edge> edgeTable, IGraphClient client) {
try {
client.Cypher
.Unwind(edgeTable, "item")
.Match("(parentNode:MyNodeType)", "(childNode:MyNodeType)")
.Where((MyNodeType parentNode, MyNodeType childNode) => parentNode.Name == item.SourceName && childNode.Name==item.MemberName)
.Create("(childNode)-[:belongsTo]->(parentNode)")
.ExecuteWithoutResultsAsync();
} catch (Exception ex) {
//ex handling
}
}
请注意,我在不使用 await
的情况下使用 ExecuteWithoutResultsAsync,以便异步处理组。
不过,需要注意的是,您不想让 neo4j 服务器一次处理太多,以免内存不足。因此,如果任何组太大,或者同时处理的组太多,您可能希望限制调用 AddEdges 的速率,and/or 将大组拆分为更小的块,并确保这些块彼此同步处理。