Сжатие больших файлов (> 15 ГБ) и загрузка на S3 без OOM

У меня есть одна проблема с памятью при архивировании больших файлов/папок (результат zip>15 ГБ) и загрузке их в хранилище S3. Я могу создать zip-файл на диске и добавить файлы/папки, загрузить этот файл с частями на S3. Но по моему опыту это не лучший способ решить эту проблему. Знаете ли вы какие-нибудь хорошие шаблоны для сжатия больших файлов/папок и загрузки их на S3 без проблем с памятью (например, OOM)? Было бы хорошо, если бы я мог добавить эти файлы/папки в S3 напрямую к какому-нибудь загруженному zip-архиву.

Заархивируйте файлы/папки на диск и загрузите этот zip-файл по частям на S3.

3 ответа

Вы можете использовать AWS Lambda для архивирования файлов перед их загрузкой в ​​корзину S3. Вы даже можете настроить запуск Lambda и заархивировать файлы при загрузке . Вот пример Java -функции Lambda для сжатия больших файлов. Эта библиотека ограничена 10 ГБ, но это можно преодолеть с помощью EFS.

Временное хранилище Lambda ограничено 10 ГБ , но вы можете подключить хранилище EFS для обработки больших файлов. Стоимость должна быть близка к нулю, если вы удалите файлы после использования.

Кроме того, не забудьте использовать многокомпонентную загрузку при загрузке файла размером более 100 МБ на S3. Если вы используете SDK, он должен справиться с этим за вас.

Основная причина, по которой вы получаете OOM, заключается в том, как работает алгоритм deflate zlib.

Представьте себе эту установку:

  1. Он начинает читать весь файл, открывая доступный для чтения поток.
  2. Он создает временный выходной файл размером 0 байт с самого начала.
  3. Затем он считывает данные порциями, называемымиdictionary size, затем он отправляет его в ЦП для дальнейшей обработки и сжатия, которые распространяются обратно в ОЗУ.
  4. Когда он закончил с определенным словарем фиксированного размера, он переходит к следующему и так далее, пока не достигнет терминатора END OF FILE.
  5. После этого он захватывает все эти дефлированные (сжатые) байты из ОЗУ и записывает их в реальный файл.

Вы можете наблюдать и делать выводы об этом поведении, инициировав операцию выкачивания, пример ниже.
(Файл создан, обработано 372 МБ, но ничего не записывается в файл до последнего обработанного байта.)

Технически вы можете взять все части, СНОВА заархивировать их в tar.gz, а затем загрузить в AWS как один файл, но вы можете столкнуться с той же проблемой с памятью, но теперь в части загрузки.

Вот ограничения на размер файла:
https://docs.aws.amazon.com/AmazonS3/latest/userguide/upload-objects.html .

Если вы используете CLI, технически вы можете это сделать, если вам нужно или нужно использовать REST API, это не вариант для вас, поскольку ограничение составляет всего 5 ГБ на запрос.

Кроме того, вы не указали максимальный размер, поэтому, если он даже больше 160 ГБ, это не вариант ДАЖЕ с использованием интерфейса командной строки AWS (который заботится об освобождении памяти после каждого загруженного фрагмента). Так что лучше всего будет многостраничная загрузка.

https://docs.aws.amazon.com/cli/latest/reference/s3api/create-multipart-upload.html

Всего наилучшего!

Сжатие файла за 1 раз - не совсем правильный способ. Подумайте, что лучший способ - разбить проблему таким образом, чтобы вы не загружали все данные за один раз, а считывали их байт за байтом и отправляли в пункт назначения байт за байтом. Таким образом, вы не только получите скорость (~x10), но и устраните эти OOM.

Пунктом назначения может быть веб-конечная точка экземпляра EC2 или веб-служба с интерфейсом API-шлюза в зависимости от выбранного вами варианта архитектуры.

Таким образом, по сути, часть 1 решения заключается в ПОТОКЕ - заархивируйте его байт за байтом и отправьте в конечную точку http. Часть 2 может заключаться в использовании многокомпонентных интерфейсов загрузки из AWS SDK (в вашем месте назначения) и отправке их параллельно S3.

Путь в = Paths.get("abc.huge"); Выходной путь = Paths.get("abc.huge.gz");

      try (InputStream in = Files.newInputStream(in);
    OutputStream fout = Files.newOutputStream(out);) {
    GZipCompressorOutputStream out2 = new GZipCompressorOutputStream(
     new BufferedOutputStream(fout));

    // Read and write byte by byte
    final byte[] buffer = new byte[buffersize];
     int n = 0;
    while (-1 != (n = in.read(buffer))) {
     out2.write(buffer, 0, n);
    }
}
Другие вопросы по тегам