Получить миллионы записей с помощью CopyManager, выдающего OutOfMemoryError

Когда я попытался запустить свой код в таблицах с тысячами строк, он работал нормально, но в качестве тестирования производительности я попробовал его с таблицами, содержащими миллионы записей, и затем столкнулся с этой проблемой.

Я пробовал вышеуказанный подход. Я могу попробовать альтернативный подход без копименеджера, но было бы здорово, если бы я мог использовать этот фрагмент кода. Любые предложения приветствуются.

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)

Что такое лакхи? Пожалуйста, используйте названия метрик

Frank Heikens 10.02.2023 15:54

отредактировано. Извинения за это

Himanshu Arya 10.02.2023 15:56

Вам нужно создать строку sql внутри цикла? Строка SQL = ноль; sql = "SELECT" + table.getAttributeList() + "FROM" + table.getSchemaName().trim() + "." + table.getTableName().trim() + " WHERE " + "modified_ts" + " > " + "'" + dateTime + "'" + " OFFSET " + OFFSET + " LIMIT " + LIMIT; ?

Avishek Bhattacharya 10.02.2023 16:53

Я могу удалить это точно.

Himanshu Arya 10.02.2023 20:28

В Spring/hibernate/postgresql я создал временное представление с идентификаторами сущностей. После этого я мог получить их все сразу и разделить огромный список на подсписки. И после этого обработайте каждый подсписок, освободив память.

dimirsen Z 15.02.2023 17:41
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Никакой магии с управлением памятью нет, и похоже нужно найти баланс: либо расширить 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.*/
}

Надеюсь, это проясняет проблему и решение. Дайте мне знать, если нужны дополнительные разъяснения.

Другие вопросы по теме