【问题标题】:Neo4j OutOfMemory Error: GC overhead limit exceededNeo4j OutOfMemory 错误:超出 GC 开销限制
【发布时间】:2017-11-12 12:19:07
【问题描述】:

我有一个 java 应用程序需要从 MySQL/Maria 数据库加载 300K 记录,以便将它们导入到 neo4j 嵌入式数据库。要获得所有必填字段,我需要加入 4 个表。他们每个人都有近 30 万条记录与 1:1 关系匹配。

这是代码:

String query = ""
    + "SELECT "
    + "     a.field1, "
    + "     a.field2, "
    + "     a.field3, "
    + "     f.field4, "
    + "     a.field5, "
    + "     a.field6, "
    + "     a.field7, "
    + "     a.field8, "
    + "     a.field9, "
    + "     a.field10, "
    + "     b.field11, "
    + "     b.field12, "
    + "     b.field13, "
    + "     l.field14, "
    + "     l.field15, "
    + "     a.field16 "
    + "FROM table1 a "
    + "LEFT OUTER JOIN table2 f ON f.pkTable2 = a.fkTable2 "
    + "LEFT OUTER JOIN table3 b ON b.pkTable3 = a.fkTable3 "
    + "LEFT OUTER JOIN table4 l ON l.pk1Table4 = a.fk1Table4 AND l.pk2Table4 = a.fk2Table4 ";

try (
    Connection connection = ds.getConnection();
    PreparedStatement statement = connection.prepareStatement(query);
    ResultSet rs = statement.executeQuery();
) {

    Transaction tx = graphDB.beginTx(); // open neo4j transaction
    int count = 0;

    int count = 0;
    rs.setFetchSize(10000);
    while(rs.next()) {
        String field1 = rs.getString("field1");
        String field2 = rs.getString("field2");
        String field3 = rs.getString("field3");
        String field4 = rs.getString("field4");
        String field5 = rs.getString("field5");
        String field6 = rs.getString("field6");
        String field7 = rs.getString("field7");
        String field8 = rs.getString("field8");
        String field9 = rs.getString("field9");
        String field10 = rs.getString("field10"); // <-- error comes here
        String field11 = rs.getString("field11");
        String field12 = rs.getString("field12");
        String field13 = rs.getString("field13");
        String field14 = rs.getString("field14");
        String field15 = rs.getBigDecimal("field15"); 
        String field16 = rs.getBigDecimal("field16");

        // process data - insert/update/delete in neo4j embedded DB
        if("D".equals(field16)) { // record deleted in mysql db - delete from neo4j too
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("field1", field1);
            graphDB.execute(" MATCH (p:NODELABEL {field1:{field1}}) OPTIONAL MATCH (p)-[r]-() DELETE r,p", params);
        } else {
            Node node;
            if("M".equals(field16)) { // record modified, load the existing node and edit it
                node = graphDB.findNode(Labels.NODELABEL, "field1", field1);
            } else { // new record, create node from scratch
                node = graphDB.createNode(Labels.NODELABEL);
            }

            node.setProperty("field1", field1);
            node.setProperty("field2", field2);
            node.setProperty("field3", field3);
            node.setProperty("field4", field4);
            node.setProperty("field5", field5);
            node.setProperty("field6", field6);
            node.setProperty("field7", field7);
            node.setProperty("field8", field8);
            node.setProperty("field9", field9);
            node.setProperty("field10", field10);
            node.setProperty("field11", field11);
            node.setProperty("field12", field12);
            node.setProperty("field13", field13);
            node.setProperty("field14", field14);
            node.setProperty("field15", field15);
        }

        count++;
        if(count % 10000 == 0) {
            LOG.debug("Processed " + count + " records.");
            tx.success(); // commit
            tx.close();   // close neo4j transaction (should free the memory)
            tx = graphDB.beginTx(); // reopen the transaction
        }
    }

    // commit remaining records and close the last transaction
    tx.success();
    tx.close();
} catch (SQLException ex) {
    // LOG exception
}

一切正常,但导入停止在 300k,等待大约 5 秒并抛出 OutOfMemoryException:

java.lang.OutOfMemoryError: GC overhead limit exceeded
    at com.mysql.cj.core.util.StringUtils.toString(StringUtils.java:1665)
    at com.mysql.cj.core.io.StringValueFactory.createFromBytes(StringValueFactory.java:93)
    at com.mysql.cj.core.io.StringValueFactory.createFromBytes(StringValueFactory.java:36)
    at com.mysql.cj.core.io.MysqlTextValueDecoder.decodeByteArray(MysqlTextValueDecoder.java:232)
    at com.mysql.cj.mysqla.result.AbstractResultsetRow.decodeAndCreateReturnValue(AbstractResultsetRow.java:124)
    at com.mysql.cj.mysqla.result.AbstractResultsetRow.getValueFromBytes(AbstractResultsetRow.java:225)
    at com.mysql.cj.mysqla.result.ByteArrayRow.getValue(ByteArrayRow.java:84)
    at com.mysql.cj.jdbc.result.ResultSetImpl.getString(ResultSetImpl.java:880)
    at com.mysql.cj.jdbc.result.ResultSetImpl.getString(ResultSetImpl.java:892)
    at org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getString(DelegatingResultSet.java:266)
    at org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getString(DelegatingResultSet.java:266)
    at com.js.Importer.importData(Importer.java:99)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)

当我为table3 和table4 添加外连接时出现了这个异常。在这些新加入之前没有错误。

我尝试在我的电脑上重新执行代码监控资源使用情况,结果发现该应用程序在处理数据时占用了高达 2GB 的 RAM 和 100% 的 CPU。当它达到 2GB RAM 时,它会耗尽内存。

我读过this answer。在 cmets 部分,您可以找到:

Tim:将您的答案总结如下是否正确:“这就像'Out of Java Heap space'错误。使用-Xmx 给它更多内存。” ?

OP:@Tim:不,那是不正确的。虽然给它更多内存可以减少问题,但您还应该查看您的代码并了解它为什么会产生这么多垃圾以及为什么您的代码略低于“内存不足”标记。这通常是代码损坏的迹象。

所以我也可以只给应用更高的 RAM,但这似乎是一种解决方法,所以我想解决这个问题。

我还尝试使用 VisualVM 分析应用程序,结果如下:

即使我一次处理 10K 个节点以避免内存开销,neo4j 似乎也将所有节点保留在内存中。

如何阻止它做这样的事情?

如何解决内存问题?

【问题讨论】:

标签: java memory neo4j garbage-collection


【解决方案1】:

要解决内存问题,我建议您将方法更改为:

  • 首先,将 MySQL/MariaDB 选择语句的结果集导出到 CSV 文件。
  • 之后,使用LOAD CSV 子句将 CSV 文件导入到您的 Neo4j 数据库中。使用LOAD CSV 时,您可以使用USING PERIODIC COMMIT 设置定期提交的速率。

来自the docs:

如果 CSV 文件包含大量行(接近 数十万或数百万),USING PERIODIC COMMIT 可以使用 指示 Neo4j 在多行之后执行提交。 这个 减少事务状态的内存开销。默认情况下, 提交将每 1000 行发生一次。

基本导入脚本如下所示:

USING PERIODIC COMMIT 10000 // Commit after 10000 rows
LOAD CSV FROM 'path/to/csv/file.csv' AS line
// you can use line.field1 to access field1 property

// your Cypher statements go here, for example
CREATE (:Node { field1: line.field1})

如果内存问题仍然存在,请尝试将定期提交率降低到较低的值。

【讨论】:

    【解决方案2】:

    你如何开始你的申请?这是嵌入式还是服务器扩展或程序?

    如果是后者,则有一个外部 Neo4j 事务导致您的内部批处理无法正常工作。

    您的应用程序的堆配置和页面缓存配置是什么?

    如果在没有 Neo4j 位的情况下执行查询会发生什么?

    您可以在此处使用DETACH DELETE,但您应该关闭结果以释放资源。此外,根据您在此处拥有多少关系,您的事务中的记录数可能会大大增加,因此您可能需要减少批量大小。

    graphDB.execute(" MATCH (p:NODELABEL {field1:{field1}}) DETACH DELETE p", params).close();
    

    【讨论】:

      猜你喜欢
      • 2016-05-25
      • 2021-03-14
      • 1970-01-01
      • 1970-01-01
      • 2016-12-03
      • 2010-11-26
      • 1970-01-01
      • 2018-04-10
      相关资源
      最近更新 更多