Neo4j-ogm:降低写入/映射性能

时间:2016-11-08 20:48:05

标签: neo4j database-performance spring-data-neo4j-4 neo4j-ogm yourkit

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

我不断插入数据,基本上是因为它可用于我的应用程序(所以我不能使用批量插入)。启动后,这工作正常,保存我的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 profiler(采样器模式!)我可以看到neo4j-ogm的EntityGraphMapper似乎花费了大部分时间,特别是

org.neo4j.ogm.context.EntityGraphMapper#haveRelationEndsChanged

YourKit Profiler

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

我还不确定,neo4j-ogm实际上是导致经济放缓的原因,因为我没有看到与良好的初步结果相比有什么变化。 在这种情况下,我通常怀疑内存泄漏/污染,但所有监控结果在我的应用程序中看起来都很好。对于neo4j服务器实例,除了debug.log之外,我真的不知道在哪里查找这些信息。

总而言之,我已经花了很长时间来调查这一点,并且不知道还有什么可以看。有什么想法或建议吗?我很乐意提供更多信息。

编辑:Follwing @ vince的输入,我再看一下内存分布,发现实际上Neo4jSession在让应用程序运行~3h之后已经发展了很多:

neo4j-ogm-memory

当时堆大了1,7 GB,其中70%引用了实时数据。其中,Neo4jSession目前引用了大约300mb(并保持活着)。这可能表明它已经变得太大了。 我怎样才能手动干扰?

3 个答案:

答案 0 :(得分:2)

实体在会话中坚持到收集垃圾为止。如果您要加载数千个实体,haveRelationEndsChanged可能会对性能产生一些影响,因此在每个事务之间进行session.clear()可能是值得的,看看是否有帮助

答案 1 :(得分:2)

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

我最近在一个Set中保存了一个约900个关系的节点时遇到了同样的情况,可以让它从~5秒到500ms执行。我最初使用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有一个索引/唯一字段,我可以完全控制该值。 neo4j-ogm将使用它来确定它是应该执行CREATE还是MERGE语句。在我的用例中,如果节点已经存在,我希望合并发生。

另一方面,关系创建依赖于节点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版修复了这个问题。

希望它有所帮助!

答案 2 :(得分:1)

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

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

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