2017-06-10 10 views
2

Ich habe eine Java-Anwendung, die 300K Datensatz aus einer MySQL/Maria-Datenbank laden muss, um sie in eine neo4j eingebettete Datenbank zu importieren. Um alle erforderlichen Felder zu erhalten, muss ich 4 Tabellen verbinden. Jeder von ihnen hat fast 300k Datensätze, die mit 1:1 Beziehung mit den anderen übereinstimmen.Neo4j OutOfMemory Fehler: GC Overhead Limit überschritten

Dies ist der Code:

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 
} 

Alles geht in Ordnung, aber der Import stoppt bei 300k, wartet ca. 5 Sekunden und wirft ein 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) 

Diese Ausnahme kam, als ich äußere hinzugefügt beitreten für und table4. Vor diesen neuen Joins gab es keine Fehler.

Ich habe versucht, die Codeüberwachung Ressourcenverbrauch auf meinem PC erneut auszuführen, stellt sich heraus, dass die App bis zu 2 GB RAM und 100% CPU während der Verarbeitung von Daten nimmt. Wenn es 2 GB RAM erreicht, geht es aus dem Speicher.

Ich habe this answer gelesen. In den Kommentaren finden Sie:

Tim: Would it be correct to summarise your answer as follows: "It's just like an 'Out of Java Heap space' error. Give it more memory with -Xmx." ?

OP: @Tim: No, that wouldn't be correct. While giving it more memory could reduce the problem, you should also look at your code and see why it produces that amount of garbage and why your code skims just below the "out of memory" mark. It's often a sign of broken code.

So konnte ich auch nur die App höher RAM geben, aber diese Art der eine Abhilfe zu sein scheint, so möchte ich das Problem, anstatt zu lösen.

Ich habe auch versucht die App mit VisualVM Profilierung und das war das Ergebnis: enter image description hereenter image description here

Es scheint, dass Neo4j ist, alle Knoten im Speicher zu halten, auch wenn ich 10K Knoten zur Zeit verarbeiten Speicher-Overhead zu vermeiden.

Wie kann man so etwas verhindern?

Wie kann ich das Speicherproblem lösen?

+0

Lesen https://neo4j.com/docs/operations-manual/current/performance/ und versuchen, die [Seite Cache-Größe] zu reduzieren (https://neo4j.com/docs/operations-manual/ current/reference/Konfigurationseinstellungen/# config_dbms.memory.pagecache.size). – Leon

Antwort

0

das Speicherproblem zu lösen, ich schlage vor, Sie Ihren Ansatz ändern:

  • Zuerst exportieren Sie die Ergebnismenge Ihrer MySQL/MariaDB select-Anweisung in eine CSV-Datei.
  • Danach verwenden Sie die LOAD CSV Klausel, um die CSV-Datei in Ihre Neo4j-Datenbank zu importieren. Bei Verwendung von LOAD CSV können Sie die Rate des periodischen Commits mit USING PERIODIC COMMIT festlegen.

Von the docs:

If the CSV file contains a significant number of rows (approaching hundreds of thousands or millions), USING PERIODIC COMMIT can be used to instruct Neo4j to perform a commit after a number of rows. This reduces the memory overhead of the transaction state. By default, the commit will happen every 1000 rows.

Der Import Basis Skript wird wie folgt aussieht:

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}) 

Wenn das Speicherproblem die periodische verringern bleibt versuchen Rate auf einen niedrigeren Wert begehen.

0

Wie starten Sie Ihre Anwendung? Ist dies eingebettet oder eine Servererweiterung oder Prozedur?

Wenn es das letztere ist, gibt es eine äußere Neo4j-Transaktion, die Ihre internen Stapel nicht funktioniert.

Was ist Ihre Heap-Konfiguration und Seiten-Cache-Konfiguration für die Anwendung?

Was passiert, wenn Sie die Abfrage ohne das Neo4j-Bit ausführen?

Sie können DETACH DELETE hier verwenden, aber Sie sollten das Ergebnis zu freien Ressourcen schließen. Abhängig davon, wie viele Beziehungen Sie hier haben, kann die Anzahl der Datensätze in Ihrer Transaktion erheblich höher sein. Daher müssen Sie möglicherweise Ihre Batchgröße reduzieren.

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