Когда я попытался запустить свой код в таблицах с тысячами строк, он работал нормально, но в качестве тестирования производительности я попробовал его с таблицами, содержащими миллионы записей, и затем столкнулся с этой проблемой.
Я пробовал вышеуказанный подход. Я могу попробовать альтернативный подход без копименеджера, но было бы здорово, если бы я мог использовать этот фрагмент кода. Любые предложения приветствуются.
OFFSET is 0;
LIMIT is 3 million
Вот код:
Connection connection = null;
try(StringWriter out = new StringWriter(); Writer printWriter = new PrintWriter(out, true)) {
connection = dataSource.getConnection();
OFFSET = 0;
// totalRecords is the total rows in the table
while (OFFSET <= totalRecords && totalRecords > 0) {
if (connection != null && connection.isWrapperFor(PGConnection.class)) {
PGConnection pgConnection = connection.unwrap(PGConnection.class);
CopyManager copyManager = null;
copyManager = pgConnection.getCopyAPI();
String sql = null;
sql = "SELECT " + table.getAttributeList() + " FROM " + table.getSchemaName().trim() + "."
+ table.getTableName().trim() + " WHERE " + "modified_ts" + " > " + "'" + dateTime + "'"
+ " OFFSET " + OFFSET + " LIMIT " + LIMIT;
LOGGER.info(sql);
long i;
// Here I am trying to copy the Result into the printwriter
i = copyManager.copyOut("COPY (" + sql + " ) TO STDOUT WITH (FORMAT CSV)", printWriter);
LOGGER.info("Total no of records in {} : {}", table.getTableName(), i);
printWriter.flush();
OFFSET = OFFSET + LIMIT;
}
}
String now = LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")).replace("/", "");
String localFileName = table.getTableName() + "_" + now + ".csv";
InputStream inputStream = new ByteArrayInputStream(out.toString().getBytes(UTF8));
postObjectToS3.uploadFile(saveFilePath + localFileName, inputStream);
inputStream.close();
}
и вот трассировка стека:
Исключение в потоке "основной" java.lang.reflect.InvocationTargetException в java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (собственный Метод) в java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) в java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) в java.base/java.lang.reflect.Method.invoke(Method.java:566) в org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) в org.springframework.boot.loader.Launcher.launch(Launcher.java:108) в org.springframework.boot.loader.Launcher.launch(Launcher.java:58) в org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88) Вызвано: java.lang.OutOfMemoryError: пространство кучи Java в java.base/java.util.Arrays.copyOf(Arrays.java:3745) в java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:172) в java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:633) в java.base/java.lang.StringBuffer.append(StringBuffer.java:397) в java.base/java.io.StringWriter.write(StringWriter.java:122) в java.base/java.io.PrintWriter.write(PrintWriter.java:542) в java.base/java.io.PrintWriter.write(PrintWriter.java:559) в org.postgresql.copy.CopyManager.copyOut(CopyManager.java:92)
отредактировано. Извинения за это
Вам нужно создать строку sql внутри цикла? Строка SQL = ноль; sql = "SELECT" + table.getAttributeList() + "FROM" + table.getSchemaName().trim() + "." + table.getTableName().trim() + " WHERE " + "modified_ts" + " > " + "'" + dateTime + "'" + " OFFSET " + OFFSET + " LIMIT " + LIMIT; ?
Я могу удалить это точно.
В Spring/hibernate/postgresql я создал временное представление с идентификаторами сущностей. После этого я мог получить их все сразу и разделить огромный список на подсписки. И после этого обработайте каждый подсписок, освободив память.
Никакой магии с управлением памятью нет, и похоже нужно найти баланс: либо расширить Xmx кучи, либо уменьшить LIMIT (а он довольно большой). Предполагая, что вы можете оставить CopyManager, выполнить итерацию ResultSet и удалить элемент после его преобразования в текст (иначе у вас есть данные дважды в памяти). Этот подход позволяет сборщику мусора освобождать память, когда вы перемещаете результаты из sql в текст (и когда данные хранятся в памяти один раз). Итак, основная идея
List<Object[]> resultValues = query.getResultList();
Iterator<Object[]> it = resultValues.iterator();
while (it.hasNext()) {
Object[] result = it.next();
... // the conversion to text
it.remove();
}
Проблема была со следующей строкой:
while (OFFSET <= totalRecords && totalRecords > 0) {
// some code
i = copyManager.copyOut("COPY (" + sql + " ) TO STDOUT WITH (FORMAT CSV)", printWriter);
}
Я повторял цикл и копировал набор результатов запроса в принтер. Он хранит как можно больше записей, но в конце концов ему не хватило памяти.
Чтобы решить эту проблему, я просто использовал данные внутри принтера в каждой итерации. Основная идея, которая решила это, заключалась в следующем:
while (OFFSET <= totalRecords && totalRecords > 0) {
printwriter = new PrintWriter(out);
// some code
i = copyManager.copyOut("COPY (" + sql + " ) TO STDOUT WITH (FORMAT CSV)", printWriter);
/* Used printwriter data to create a file or append the data if file exists. Closed the printwriter.*/
}
Надеюсь, это проясняет проблему и решение. Дайте мне знать, если нужны дополнительные разъяснения.
Что такое лакхи? Пожалуйста, используйте названия метрик