-
Bug
-
Resolution: Fixed
-
High
-
8.5.0, 8.5.1, 8.5.3, 8.5.4, 8.7.1, 7.13.14, 8.8.1, 8.9.1
-
7.13
-
13
-
Severity 2 - Major
-
18
-
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;
- install Jira
- create a table in JIRA database to test purposes named "AO_111111_TEST"
- 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)
- create an XML backup (gg > Backup System)
- connect the server with a debugger and put breakpoints in method com.atlassian.dbexporter.importer.DataImporter$BatchInserter.flush()
- import the XML backup generated above (gg > Restore System)
- 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