1

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

这些插入通常是遵循星型模式的节点。一个节点(导入数据集的根节点)有多达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叶:3035ms
  • 100000叶:4290ms
  • 200000叶:10268ms
  • 400000叶:20913ms
  • 800000 个叶子:Java 堆空间

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

系统监视器

为了更好地了解究竟在运行什么并存储在堆中,我使用最后一个测试(800000 个叶子)运行了 JProfiler。以下是一些截图:

堆使用:

堆

CPU使用率:

中央处理器

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

能够将 neo4j 与这些数据量一起使用会很棒。图表中已经存储了 200000 个叶子,我注意到与嵌入式经典 RDBMS 相比,性能提高了 10 倍甚至更多。我不想放弃像 neo4j 提供的数据建模和查询这些数据的好方法。

4

2 回答 2

3

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

输出:导入 1000000 个孩子用时 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文档状态,它不是为这种类型的任务而设计的

于 2013-09-02T21:25:24.620 回答
1

如果您将 800K 子节点连接到一个节点,那么您实际上是在创建一个密集节点,也就是类似键值的结构。现在的 Neo4j 并没有优化来有效地处理这些结构,因为所有连接的关系在遍历节点时都被加载到内存中。如果您只想在接触这些结构时加载部分关系,Neo4j 2.1 将通过可配置的优化来解决这个问题。

目前,我建议要么将这些结构放入索引中并查找连接的节点,要么沿着一个值平衡密集结构(例如,沿着关系上的一个属性构建一个包含 100 个子类别的子树,例如时间,例如参见http://docs.neo4j.org/chunked/snapshot/cypher-cookbook-path-tree.html

那会有帮助吗?

于 2013-09-02T20:50:35.070 回答