【问题标题】:Neo4j, bulk load with Cypher commandsNeo4j,使用 Cypher 命令批量加载
【发布时间】:2018-03-16 11:01:21
【问题描述】:

我是 Neo4j 的新手,对基础知识肯定有一些我不了解的地方。

我有很多 Java 对象,我想使用 Java 驱动程序和 Cypher 使用它们来填充 Neo4j 图形。我的代码是这样工作的:

// nodes
for ( Person person: persons )
  session.run ( String.format ( 
    "CREATE ( :Person { id: '%s', name: \"%s\", surname: \"%s\" })",
    person.getId(), person.getName(), person.getSurname ()
  ));

// relations
session.run ( "CREATE INDEX ON :Person(id)" );

for ( Friendship friendship: friendships )
  session.run ( String.format ( 
    "MATCH ( from:Person { id: '%s' } ), ( to:Person { id: '%s' } )\n" +
    "CREATE (from)-:KNOWS->(to)\n",
    friendship.getFrom().getId(), 
    friendship.getTo().getId() 
  )); 

(确实,它稍微复杂一些,因为我有十几种节点类型和大约相同数量的关系类型)。

现在,这非常慢,比如加载 300k 节点和 1M 关系需要 1 个多小时(在相当快的 MacBookPro 上,Neo4j 占用 12/16GB 的 RAM)。

我做错了吗?我应该改用batch inserter 吗? (我希望能够通过网络访问graphDB)。我会通过将更多插入分组到一个事务中来获得一些东西吗? (从文档看来,事务似乎只对回滚和隔离需求有用)。

【问题讨论】:

    标签: java neo4j cypher


    【解决方案1】:

    我认为值得报告我在这方面的经验。

    我已遵循@sjc 的建议并尝试使用 UNWIND。然而,这并不是那么简单,因为 Cypher 不允许您参数化节点标签或关系类型(我有十几个标签和关系类型)。但最终,我能够遍历所有可能的类型并向每个 UNWIND 块发送足够的项目(大约 1000 个)。

    在我看来,使用 UNWIND 的代码要快得多,但还不够快(在具有几百万个节点的体面 PC 上应该没问题,对于数亿个或更多节点来说不是很好)。

    插入器组件要快得多(上传 1-2 百万个节点只需几秒钟),尽管它需要关闭 HTTP 访问,而且我在它对 Lucene 5.4 的依赖方面遇到了很多问题,因为我需要在使用 Lucene 6 的应用程序(生成数据)中使用它,当我尝试在类路径中简单地将 5.4 与 6 交换时,发生了可怕的事情。我读过there is some mechanism to make this possible,但它似乎并不容易,而且文档当然也没有那么好。

    我绝对没想到高效执行这样一个基本操作会遇到这么多麻烦。

    【讨论】:

    • Cypher 不允许参数化标签或关系类型,但该语句是由字符串构建的,因此您可以遍历节点类型列表以简单地为每种类型重新创建字符串。
    • 嗨@sjc。这是一个老问题,自那以来发生了很多事情。我必须在每个标签的基础上拆分我的数据,然后节点类型作为 Cypher 查询的常量部分发送,该查询与许多相同类型的节点一起使用。如果我理解正确,这与您的建议相似。我实施这种方法的一个项目在这里:github.com/Rothamsted/rdf2neo
    【解决方案2】:

    我来自 Python 中的 Neo4j,但我认为这里的问题在于您的 Cypher 命令。我有两个建议。

    单独匹配边缘可能会更快。在我的原始基准测试中,我看到了 24 毫秒和 15 毫秒的差异(编辑:这个基准是可疑的):

    MATCH ( from:Person { id: '%s' } )
    MATCH ( to:Person { id: '%s' } )
    CREATE (from)-:KNOWS->(to)
    

    另一个选项是使用 UNWIND。我将它与 BOLT 接口一起使用以发送更少的事务,但不使用 Batch Inserter。请原谅我在此处复制的 Python 实现,希望您可以将其与 Javascript Neo4j 驱动程序文档一起查看以进行转换。

    payload = {"list":[{"a":"Name1","b":"Name2"},{"a":"Name3","b":"Name4"}]}
    
    statement = "UNWIND {list} AS d "
    statement += "MATCH (A:Person {name: d.a}) "
    statement += "MATCH (B:Person {name: d.b}) " 
    statement += "MERGE (A)-[:KNOWS]-(B) "
    
    tx = session.begin_transaction()
    tx.run(statement,payload)
    tx.commit()
    

    【讨论】:

    • 谢谢,但我认为它不适用于我的情况。考虑到我的应用程序在不到 3 分钟的时间内填充其内部图(使用哈希图),24-15 毫秒并没有太大的不同,而 Neo4j 需要很长时间才能做到这一点。这不可能是正确的,它应该或多或少相同。至于 WIND,考虑到我有这么多节点和边,我认为发送一个列表作为参数最终会变成一个太大的查询。
    • 我的基准可能会偏离,但我强烈建议至少尝试 UNWIND。您的有效负载列表不必是 all 您的数据,您可以将其分块(就像我在实践中所做的那样,而不是在上面的示例代码中)。如果有效负载的大小约为 100k,并且您可以用 N 个事务填充边缘,那么相对于 N*100k 个单个事务,这将节省大量时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多