在neo4j中使用更大量的数据的Java堆空间错误

时间:2013-09-02 14:43:03

标签: java performance neo4j

我目前正在评估neo4j在图中插入大量节点/关系。它不是关于批量插入可以实现的初始插入。它是关于在运行时期间在嵌入模式下使用neo4j的java应用程序中频繁处理的插入(当前版本为1.8.1,因为它随spring-data-neo4j 2.2.2.RELEASE一起提供)。

这些插入通常是遵循星型模式的节点。一个节点(导入数据集的根节点)最多包含 1000000(一百万!)个子节点。子节点通常也与其他附加节点有关系。但到目前为止,这些测试还没有涵盖这些关系。 总体目标是最多五分钟导入大量数据!

为了模拟这种插入,我编写了一个小的junit测试,它使用Neo4jTemplate来创建节点和关系。每个插入的叶子都有一个关键字,以便以后处理:

@Test
@Transactional
@Rollback
public void generateUngroupedNode()
        {
        long numberOfLeafs = 1000000;
        Assert.assertTrue(this.template.transactionIsRunning());
        Node root = this.template.createNode(map(NAME, UNGROUPED));
        String groupingKey = null;
        for (long index = 0; index < numberOfLeafs; index++)
            {
            // Just a sample division of leafs to possible groups
            // Creates keys to be grouped by to groups containing 2 leafs each
            if (index % 2 == 0)
                {
                groupingKey = UUID.randomUUID().toString();
                }
            Node leaf = this.template.createNode(map(GROUPING_KEY, groupingKey, NAME, LEAF));
            this.template.createRelationshipBetween(root, leaf, Relationships.LEAF.name(),
                    map());
            }
        }

对于此测试,我使用gcr缓存来避免垃圾收集器问题:

cache_type=gcr
node_cache_array_fraction=7
relationship_cache_array_fraction=5
node_cache_size=400M
relationship_cache_size=200M

此外,我将MAVEN_OPTS设置为:

export MAVEN_OPTS="-Xmx4096m -Xms2046m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:-UseGCOverheadLimit"

但无论如何,当运行该测试时,我总是会收到Java heap space错误:

java.lang.OutOfMemoryError: Java heap space
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2427)
    at java.lang.Class.getMethod0(Class.java:2670)
    at java.lang.Class.getMethod(Class.java:1603)
    at org.apache.commons.logging.LogFactory.directGetContextClassLoader(LogFactory.java:896)
    at org.apache.commons.logging.LogFactory$1.run(LogFactory.java:862)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.commons.logging.LogFactory.getContextClassLoaderInternal(LogFactory.java:859)
    at org.apache.commons.logging.LogFactory.getFactory(LogFactory.java:423)
    at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:685)
    at org.springframework.transaction.support.TransactionTemplate.<init>(TransactionTemplate.java:67)
    at org.springframework.data.neo4j.support.Neo4jTemplate.exec(Neo4jTemplate.java:403)
    at org.springframework.data.neo4j.support.Neo4jTemplate.createRelationshipBetween(Neo4jTemplate.java:367)

我做了一些测试,数据量较少,导致以下结果。 1个节点连接到:

  • 50000 leafs:3035ms
  • 100000叶子:4290ms
  • 200000 leafs:10268ms
  • 400000 leafs:20913ms
  • 800000 leafs:Java堆空间

以下是这些操作期间系统监视器的屏幕截图:

System Monitor

为了更好地了解正在运行的内容并将其存储在堆中,我使用最后一个测试运行了JProfiler(800000个叶子)。以下是一些截图:

堆使用情况:

HEAP

CPU使用率:

CPU

对我来说最大的问题是:neo4j是不是设计用于使用那种大量的数据?还是有其他方法来实现这种插入(以及后来的操作)?在官方的neo4j网站和各种截屏视频中,我发现了neo4j能够运行数十亿个节点和关系的信息(例如http://docs.neo4j.org/chunked/stable/capabilities-capacity.html)。我没有找到任何可用的flush()clean()方法等功能,例如在JPA中手动保持堆清洁。

能够将neo4j与这些数据量一起使用会很棒。已经有200000个叶子存储在图表中,我注意到与嵌入式经典RDBMS相比,因子10的性能改进和更多。我不想放弃数据建模的好方法,也不想像neo4j那样查询这些数据。

2 个答案:

答案 0 :(得分:3)

通过使用Neo4j核心API,创建孩子需要18到26秒,而我的MacBook Air没有任何优化:

输出:导入100万名儿童需要26秒。

public class CreateManyRelationships {

    public static final int COUNT = 1000 * 1000;
    public static final DynamicRelationshipType CHILD = DynamicRelationshipType.withName("CHILD");
    public static final File DIRECTORY = new File("target/test.db");

    public static void main(String[] args) throws IOException {
        FileUtils.deleteRecursively(DIRECTORY);
        GraphDatabaseService gdb = new GraphDatabaseFactory().newEmbeddedDatabase(DIRECTORY.getAbsolutePath());
        long time=System.currentTimeMillis();
        Transaction tx = gdb.beginTx();
        Node root = gdb.createNode();
        for (int i=1;i<= COUNT;i++) {
            Node child = gdb.createNode();
            root.createRelationshipTo(child, CHILD);
            if (i % 50000 == 0) {
                tx.success();tx.finish();
                tx = gdb.beginTx();
            }
        }
        tx.success();tx.finish();
        time = System.currentTimeMillis()-time;
        System.out.println("import of "+COUNT+" children took " + time/1000 + " seconds.");
        gdb.shutdown();
    }
}

和Spring Data Neo4j docs state,它不适用于此类任务

答案 1 :(得分:1)

如果要将800K子节点连接到一个节点,则实际上是在创建一个密集节点,即.k.a.键值结构。 Neo4j现在没有被优化来有效地处理这些结构,因为所有连接的关系在遍历节点时被加载到存储器中。如果您只想在触摸这些结构时加载部分关系,Neo4j 2.1将通过可配置的优化来解决这个问题。

目前,我建议将这些结构放入索引中,然后对连接的节点进行查找,或者沿着一个值平衡密集结构(例如,沿着其中一个属性构建一个子树,例如100个子类别)关系,例如时间,例如见http://docs.neo4j.org/chunked/snapshot/cypher-cookbook-path-tree.html

那会有帮助吗?