Uploaded image for project: 'Jira Data Center'
  1. Jira Data Center
  2. JRASERVER-71235

Batch updates using DiagnosticPreparedStatement may increase memory pressure

    XMLWordPrintable

Details

    Description

      Issue Summary

      com.atlassian.jira.diagnostic.connection.DiagnosticPreparedStatement which is a PreparedStatement wrapper, maintains its own list of batch queries for reporting purposes. If the same preparedStatement object is used for multiple executeBatch() calls without calling clearBatch(), this list accumulates batch queries. For very large updates which use multiple batch steps (like restoring an XML backup) this may cause memory pressure and in extreme cases {{OutOfMemoryError}}s.

      Steps to Reproduce

      restoring an XML backup with large AO tables and using a debugger;

      1. install Jira
      2. create a table in JIRA database to test purposes named "AO_111111_TEST"
      3. insert 6,000 rows to this test table (number must be greater than 5,000 as imported is using batches with the size of 5,000 i.e. 6,000 rows will use to batch inserts, first will insert 5,000 and second will insert remaining 1,000)
      4. create an XML backup (gg > Backup System)
      5. connect the server with a debugger and put breakpoints in method com.atlassian.dbexporter.importer.DataImporter$BatchInserter.flush()
      6. import the XML backup generated above (gg > Restore System)
      7. observe in the debugger that ps.batchQueries is not reset after the first batch is executed and size exceeds 5,000 on second batch step

      Expected Results

      calling executeBatch shall clear the batch, also on DiagnosticPreparedStatement because underlying Statement does that automatically without requiring explicit calls to clearBatch(). Hence calling clearBatch() between batch steps does not seem to be a requirement.
      Quoting JDBC 3.0 Specification 15.1.2

      Calling the method executeBatch closes the calling Statement object’s current
      result set if one is open. The statement’s batch is reset to empty once
      executeBatch returns.

      Actual Results

      executeBatch() does not clear the batch on DiagnosticPreparedStatement
      Even if SQL works just fine, batchQueries list accumulates all statements for logging purposes.

      In extreme cases like an XML backup importing millions of rows per table, this may lead to an OOME. Below exception is a stack trace of an OOME resulting from above on a production system importing 9 million rows in a single table. Since we accumulate the statements, on each batch step, Jira prepares a longer string concatenating them for logging.

      2020-05-08 19:52:26,888-0400 JiraImportTaskExecutionThread-1 ERROR doqinfraadmin 913x183x1 1mk8hdt 10.36.79.146 /secure/admin/XmlRestore.jspa [c.a.j.bc.dataimport.DefaultDataImportService] Error importing data: java.lang.OutOfMemoryError
      java.lang.OutOfMemoryError
      	at java.lang.AbstractStringBuilder.hugeCapacity(AbstractStringBuilder.java:161)
      	at java.lang.AbstractStringBuilder.newCapacity(AbstractStringBuilder.java:155)
      	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:125)
      	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
      	at java.lang.StringBuilder.append(StringBuilder.java:136)
      	at java.lang.StringBuilder.append(StringBuilder.java:76)
      	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:484)
      	at java.lang.StringBuilder.append(StringBuilder.java:166)
      	at java.util.StringJoiner.add(StringJoiner.java:185)
      	at java.lang.String.join(String.java:2504)
      	at com.atlassian.jira.diagnostic.connection.DiagnosticPreparedStatement.getBatchQuery(DiagnosticPreparedStatement.java:146)
      	at com.atlassian.jira.diagnostic.connection.DiagnosticPreparedStatement.executeBatch(DiagnosticPreparedStatement.java:137)
      	at com.atlassian.dbexporter.importer.DataImporter$BatchInserter.flush(DataImporter.java:422)
      	at com.atlassian.dbexporter.importer.DataImporter$BatchInserter.close(DataImporter.java:434)
      	at com.atlassian.dbexporter.importer.DataImporter.importTable(DataImporter.java:120)
      	at com.atlassian.dbexporter.importer.DataImporter.access$000(DataImporter.java:41)
      	at com.atlassian.dbexporter.importer.DataImporter$1.call(DataImporter.java:71)
      	at com.atlassian.dbexporter.importer.DataImporter$1.call(DataImporter.java:64)
      	at com.atlassian.dbexporter.jdbc.JdbcUtils.withConnection(JdbcUtils.java:29)
      	at com.atlassian.dbexporter.importer.DataImporter.doImportNode(DataImporter.java:64)
      	at com.atlassian.dbexporter.importer.AbstractImporter.importNode(AbstractImporter.java:44)
      	at com.atlassian.dbexporter.DbImporter.importData(DbImporter.java:70)
      	at com.atlassian.activeobjects.backup.ActiveObjectsBackup.restore(ActiveObjectsBackup.java:151)
      	at com.atlassian.jira.bc.dataimport.DefaultDataImportService.restoreActiveObjects(DefaultDataImportService.java:574)
      	at com.atlassian.jira.bc.dataimport.DefaultDataImportService.performImport(DefaultDataImportService.java:748)
      	at com.atlassian.jira.bc.dataimport.DefaultDataImportService.doImport(DefaultDataImportService.java:323)
      	at com.atlassian.jira.web.action.setup.DataImportAsyncCommand.unsafeCall(DataImportAsyncCommand.java:82)
      	at com.atlassian.jira.web.action.setup.DataImportAsyncCommand.call(DataImportAsyncCommand.java:64)
      	at com.atlassian.jira.web.action.setup.DataImportAsyncCommand.call(DataImportAsyncCommand.java:30)
      	at com.atlassian.jira.task.ImportTaskManagerImpl$TaskCallableDecorator.call(ImportTaskManagerImpl.java:176)
      	at com.atlassian.jira.task.ImportTaskManagerImpl$TaskCallableDecorator.call(ImportTaskManagerImpl.java:148)
      	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
      	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      	at java.lang.Thread.run(Thread.java:748)
      

      Workaround

      Contact Support

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              keroglu Kurtcebe Eroglu
              Votes:
              2 Vote for this issue
              Watchers:
              14 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: