Neo4j-ogm:降低 write/mapping 性能

Neo4j-ogm: Decreasing write/mapping performance

在我的项目中,我使用 spring-data-neo4j 4.2.0.M1 和 neo4j-ogm 2.0.4。最初这是使用嵌入式 neo4j 实例,但在调查此问题的过程中,我已经使用 Bolt 协议迁移到专用的 neo4j 实例(运行ning 在同一台机器上)。

我不断地插入数据,基本上是在我的应用程序可用时插入数据(所以我不能使用批量插入)。启动后,这工作正常,保存我的 NodeEntity 实例需要大约 60 毫秒,这对我的用例来说非常合适。然而,随着时间的推移,这会慢慢退化。 10-20 分钟后,每次保存时间减慢到大约 2 秒,这不再那么好。时间似乎在这里达到顶峰,并没有减少更多。

最初我认为这是由于嵌入式实例太小造成的,因为我看到 neo4j 重复报告有关 GC 暂停的消息。然后我迁移到一个更大的专用实例,那些 GC 警告不再出现。降级仍然发生。

neo4j 报告的商店大小:

Array Store 8.00 KiB
Logical Log 151.36 MiB
Node Store 40.14 MiB
Property Store 1.83 GiB
Relationship Store 742.63 MiB
String Store> Size 120.87 MiB
Total Store Size 4.55 GiB

实例配置如下:

dbms.memory.pagecache.size=5g
dbms.memory.heap.initial_size=4g
dbms.memory.heap.max_size=4g
dbms.jvm.additional=-XX:+UseG1GC

使用 YourKit 分析器(采样器模式!)我可以看到大部分时间似乎都花在了 neo4j-ogm 的 EntityGraphMapper 上,特别是在

org.neo4j.ogm.context.EntityGraphMapper#haveRelationEndsChanged

正在保存的 NodeEntity 通常与其他节点有大约 40 种关系,其中大多数被建模为 RelationshipEntity。在较早的阶段,我已经注意到保存实体的速度非常慢,因为还映射了太多相关(但未更改)的实体。从那时起,我在保存时使用的深度为 1。 导致保存 NodeEntitites 的连续操作使用 200 个实体的事务大小。

我还不相信 neo4j-ogm 实际上是减速的原因,因为我看不出与良好的初始结果相比有什么变化。 在这种情况下,我通常怀疑内存 leaks/pollution,但在我的应用程序中,所有针对此的监控结果看起来都不错。对于 neo4j 服务器实例,除了 debug.log.

之外,我真的不知道去哪里寻找此类信息

总而言之,我已经花了相当多的时间对此进行调查,但不知道还有什么可看的。有什么想法或建议吗?我很乐意提供更多信息。

编辑:根据@vince 的输入,我再次查看了内存分布,发现实际上 Neo4jSession 在让应用程序 运行 ~3h 后增长了很多:

当时堆有 1.7 GB,其中 70% 引用了实时数据。其中,Neo4jSession 当前引用了大约 300mb(并保持活动状态)。这可能表明它已经变得太大了。 这里怎么手动干预?

前段时间我们遇到了几乎相同的情况,当时我们需要将大量数据存储到 neo4j。我们分析了如何处理这个问题的不同方法。所以我们找到了一些解决方案如何加快向 neo4j 插入数据的速度。

  1. 使用原生 neo4j java 驱动程序而不是 spring-data。首先它是异步的 api,如果 select 的数据可用性目前并不重要,它可以提供帮助。

  2. 使用事务插入多条记录(例如每个事务插入 1000 条记录)。它会加快插入速度,因为在任何事务提交之后,neo4j 会尝试使用 lucene 重新计算索引,这需要时间。在您的情况下(使用 spring-data),任何插入都在单独的事务中执行。

实体在会话中停留,直到它们被垃圾收集。如果您正在加载数千个实体,haveRelationEndsChanged 可能会对性能产生一些影响,因此可能值得在每个事务之间进行 session.clear() 并查看这是否有帮助

希望现在帮助解决这个问题还为时不晚。

我最近遇到了同样的情况,当我在一个集合中保存一个具有约 900 个关系的节点时,它可能会在约 5 秒到 500 毫秒内执行。我最初使用的是 neo4j-ogm 2.1.3,刚刚迁移到 3.0.0。尽管 3.0.0 快得多,但两个版本的性能提升相似。

这是一些伪代码(我现在不能分享真正的代码):

@NodeEntity(label = "MyNode")
public class MyNode {
    @GraphId
    private Long id;

    @Index(unique = true, primary = true)
    private String myUniqueValue;

    private String value;

    @Relationship(type = "CONNECTS_TO")
    private Set<MyRelationship> relationships;
    // constructors, getters, setters
}

@Relationship(type = "CONNECTS_TO")
public class MyRelationship {

    @GraphId
    private Long id;

    @StartNode
    private MyNode parent;

    @EndNode
    private MyNode child;
    // constructors, getters, setters
}

请注意 MyNode 有一个 indexed/unique 字段,我可以完全控制其中的值。 neo4j-ogm 将使用它来确定是否应该执行 CREATEMERGE 语句。在我的用例中,如果节点已经存在,我希望合并发生。

另一方面,关系创建依赖于节点 ID(@GraphId 字段)。这是创建它的生成语句的一小段:

UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId MATCH (endNode) WHERE ID(endNode) = row.endNodeId...

在慢速模式下,neo4j-ogm 将负责验证关系或其中的节点是否已保存,并将检索创建节点所需的 ID。这是您在 YourKit 中捕获的操作。

一个执行缓慢的例子:

void slowMode() {
    MyNode parent = new MyNode("indexed-and-unique", "some value");
    for (int j = 0; j < 900; j++) {
        MyNode child = new MyNode("indexed-and-unique" + j, "child value" + j);
        parent.addRelationship(new MyRelationship(parent, child));
    }
    session.save(parent); // save everything. slow.
}

我找到的解决方案是将这些操作分为三个部分:

  • 只保存父节点

  • 保存子节点

  • 保存关系

这要快得多:

void fastMode() {
    MyNode parent = new MyNode("indexed-and-unique", "some value");
    for (int j = 0; j < 900; j++) {
        MyNode child = new MyNode("indexed-and-unique" + j, "child value" + j);
        parent.addRelationship(new MyRelationship(parent, child));
    }
    session.save(parent, 0); // save only the parent
    session.save(getAllChildsFrom(parent), 0); // save all the 900 childs
    // at this point, all instances of MyNode will contain an "id". time to save the relationships!
    session.save(parent);
}

需要注意的一件事:neo4j-ogm 2.1.3 在保存节点集合(session.save(getAllChildsFrom(parent), 0))时没有执行单个批处理语句,这仍然很慢,但没有以前那么慢.版本 3.0.0 修复了这个问题。

希望对您有所帮助!