Есть приложение на Kotlin + Spring Boot, в нем есть метод, который перекачивает файл из одного хранилища (допустим, S3) в другое (через REST-метод). Логика простая - сначала загружаем контент файла в хип, потом выгружаем его в целевое хранилище, то есть какое-то время весь файл находится в хипе. Проблема возникает, когда метод одновременно вызывается для множества файлов, тогда в хип одновременно загружается большое количество контента и возникает Out Of Memory Error.
Как бы вы решали данную проблему? Может есть какие-то бест практисы?
Я с такими проблемами ещё не сталкивался, но если бы решал такую задачу, то делал бы через ByteBuffer. Делил бы файл на части и делал бы очередь.
какое-то время весь файл находится в хипе
В вашем случае и делить файл не нужно... Просто очередь (multithreading pool). Считаете максимально допустимое количество файлов в памяти и делаете пулл запросов.
Не очень понимаю как поможет реактор в контексте данной задачи, когда условно 1000 потоков одновременно пойдут выкачивать файлы из S3 и забьют память. Кажется, что тут больше напрашиваются корутины
Clean Coder, у нас один из сервисов, как раз отвечающих за скачивание и загрузку клиентских файлов, хранящихся в S3, в лёгкую обрабатывает несколько тысяч параллельных скачиваний и загрузок одним инстансом, используя всего 512 хипа. Одна из основных фишек реактивности - это чрезвычайно эффективная утилизация ресурсов, включая память. Множество неблокирующихся сокетов уведомляют асинхронный код о поступлении блоков данных или о готовности блок данных передать, а тот эти блоки перекладывает из одних сокетов в другие, ни один файл никогда полностью в память не вычитывается. В целом можно и без реактора такое написать, но придётся попопеть. В теории можно даже затрахаться настолько, чтобы обеспечить zero copy между сокетами и тогда хип вообще использоваться не будет. Впрочем, у нас и без этого границы масштабирования лимитируются не возможностями софта и не ресурсами серваков, а пропускной способностью каналов, раньше коммутаторы в ЦОДе ложатся, чем сервис под нагрузкой падает.
Мне кажется что проблема в AWS S3 API. Я кодил на нем лет 5 назад но тогда он не поддерживал
работу с файлами в streamable-манере. Кажется чтоб записать файл в тебе надо было явно указать
его length в методе. Это усложняло upload.
В случае автора - сложно. Тут надо придумать какое-то оценочный средний размер качаемого
файла и создать пул из процессов штук 10-20 которые будут иметь достаточно памяти чтобы
всегда вмещать в себя тело файла. И использовать этот пул.
Расширение ресурсов - это конечно одно из решений, но если эта ситуация не частая, а допустим возникает раз в неделю/месяц, а в остальное время более чем хватает четверти от этого максимума, то кажется что это будет не совсем рациональное использование
Вообще, для данного процесса правильнее сразу же отправлять файл в другое хранилище по мере получения или получать файл маленькими блоками и сразу же их отправлять. Ну или хотя бы сохранять файл на диск, если памяти мало. Ну и конечно, перед окончательным сохранением файла проверять его контрольную сумму и если не совпадает с исходным - повторять закачку.