Процесс создания файла становится медленным сверхурочно

#java

Вопрос:

Поэтому мы создали процесс, который генерирует пакет кодов ваучеров некоторую информацию о каждом коде и записывает все это в файл CSV для интеграции через SFTP.

Процесс является динамичным, что означает, что он получает количество, которое он должен генерировать с помощью файла конфигурации. Теперь процесс пытается сгенерировать 55000 кодов. Это подразумевает, что в файле с 55000 строками в итоге получается всего ~8,5 МБ (совсем небольшой файл).

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

1 — Прочитайте файл конфигурации, чтобы узнать количество, которое будет сгенерировано, и узнать, для какого партнера этот файл.

2 — Запросите базу данных, чтобы получить некоторые конфигурации этого конкретного партнера

3 — Если количество больше 5k, разделите процесс на несколько циклов, чтобы GC мог собрать некоторые объекты и освободить часть памяти

Это основная логика, но происходит гораздо больше:

1 — Мы используем FileWriter для записи в файл (мы протестировали его, и write операции выполняются быстро, как flush и операции, по крайней мере, по-видимому).

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

3 — Когда процесс превышает 5 тыс., мы делимся на циклы, поэтому предположим, что партия составляет 12 тыс. Мы разделяем на 3 петли. 2 из 5 тысяч 1 из 2 тысяч. Это связано с тем, что у нас ограниченная память, поэтому мы хотим, чтобы сборщик мусора мог освобождать часть памяти на каждой итерации 5k (я знаю, что трудно определить, когда будет запущен GC, но, делая это, мы удостоверяемся, что объекты не имеют ссылок).

Демонстрация кода:

 quantity = readConfigFile();
partnetConfigs = getPartnerSpecificConfigsFromDB();
writer = new FileWriter(...);
if (quantity > 5000) {
    remaining = quantity
    while (remaining > 0) {
        currentBatchSize = remaining - 5000 < 0 ? remaining : 5000;
        vouchers = generate(partnerConfigs, currentBatchSize); // this method trigger the database 2 * currentBatchSize
        appendToFile(writer, vouchers);
        writer.flush(); // flushes the stream at the end of each 5000 to release any data from the buffer
        remaining -= currentBatchSize;
    }
} else {
    vouchers = generate(partnerConfigs, quantity); // this method trigger the database 2 * quantity
    appendToFile(writer, vouchers);
}

writer.close();

...
 

Процесс становится медленным сверхурочно. При генерации 50.000 кодов происходит следующее:

 1st 5.000 took 8 minutes
2nd 5.000 took 23 minutes
3rd 5.000 took 37 minutes
4th 5.000 took 50 minutes
5th 5.000 took 1 hour and 10 minutes
6th 5.000 took 1 hour and 24 minutes
7th 5.000 took 2 hours
 

Почему это происходит?

Обновить

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

Результаты, которые я получил, были каким-то образом ожидаемыми.

1 — Операции с БД были быстрыми (7 мс ~ 10 мс на выполняемую инструкцию)

2 — Операции записи были быстрыми (1 мс ~ 3 мс для добавления каждой партии из 5 кб)

3 — Этот сервер сгенерировал 55000 ваучеров за 22 минуты.

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

Комментарии:

1. Вы проверили время, необходимое базе данных для возврата?

2. @srdg да! на самом деле это возвращается довольно быстро..

3. Интересный. Я бы посоветовал немного поиграть с размером пакета, если вы еще этого не сделали. Мне любопытно узнать, в чем может быть причина, но у меня есть внутреннее предчувствие, подсказывающее мне, что время доступа к памяти является причиной узкого места.

4. Вам больше всего повезет, если вы посмотрите, на что тратит время ваше приложение. Попробуйте профилирование, в настоящее время любой профилировщик сделает это, хотя я рекомендую github.com/jvm-profiling-tools/async-profiler конечно. Если вы не можете этого сделать, по крайней мере, сделайте несколько дампов потоков через jcmd . Я уверен, что вы довольно быстро поймете, что происходит.

5. Вы проверили время, затрачиваемое операциями вставки на самую последнюю итерацию цикла? Например, каждый запрос может выполняться все медленнее и медленнее, когда БД переставляет индексированные столбцы. Поэтому, если вы протестируете его с пустой базой данных, он покажет вам гораздо лучшую производительность, чем это будет