Проблема с API многокомпонентной загрузки AWS: размер предлагаемой загрузки меньше минимально допустимого - PullRequest
0 голосов
/ 09 июля 2020

Я строю строку XML при чтении файла паркета в Java. Эта строка xml должна быть загружена в корзину S3.

Файл parquet может содержать до 2 миллионов записей.

Один из способов загрузить файл XML в S3 - использовать API составной загрузки AWS следующим образом:

После построения строки XML преобразуйте эту строку во входной поток и используйте ее в объекте UploadPartRequest для вызова метода s3Client.uploadPart ().

Обращено:

  1. https://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
  2. https://docs.aws.amazon.com/AmazonS3/latest/dev/llJavaUploadFile.html

Код будет выглядеть примерно так:

long partSize = 5242880L;
 AmazonS3 s3Client = (AmazonS3)((AmazonS3ClientBuilder)AmazonS3ClientBuilder.standard().withRegion(clientRegion)).build(); 
List<PartETag> partETags = new ArrayList();
long filePosition = 0L;

        for(int i = 1; filePosition < contentLength; ++i) {
            partSize = Math.min(partSize, contentLength - filePosition);
            DLFUtil.logInfo(" Part Size - " + partSize);
            UploadPartRequest uploadRequest = (new UploadPartRequest()).withBucketName(objectDetails.getBucketName()).withKey(objectDetails.getS3Key()).withUploadId(objectDetails.getUploadId()).withPartNumber(i).withInputStream(body).withPartSize(partSize);
            UploadPartResult uploadResult = s3Client.uploadPart(uploadRequest);
            DLFUtil.logInfo("Uploaded Part " + i);
            partETags.add(uploadResult.getPartETag());
            filePosition += partSize;
            DLFUtil.logInfo("filePosition " + filePosition);
        }

        CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(objectDetails.getBucketName(), objectDetails.getS3Key(), objectDetails.getUploadId(), partETags);
        s3Client.completeMultipartUpload(compRequest);

Проблема в том, что, поскольку файл parquet может иметь 2 миллиона записей, при построении строки xml это будет одна огромная строка в памяти ( размер строки может достигать гигабайт). Чтобы избежать одной огромной строки в памяти, я подумал о построении строки в кусках -> когда размер строки превышает, скажем, 10 МБ, я хотел бы взять этот кусок и выполнить загрузку из нескольких частей. Затем создайте следующий фрагмент и снова выполните для него составную загрузку с тем же идентификатором uploadId. Это позволило бы иметь в памяти только небольшую часть строки.

Однако при этом я столкнулся с ошибкой: «Предлагаемая вами загрузка меньше минимально допустимого размера»

Причина, по которой я получаю эту ошибку, заключается в том, что, когда я выполняю многократную загрузку фрагмента строки, последняя часть будет меньше 5 МБ. Для второй многокомпонентной загрузки другого фрагмента последняя часть снова будет меньше 5 МБ. Когда AWS пытается собрать все части с помощью completeMultipartUpload (), он ожидает, что только одна часть будет меньше 5 МБ, потому что в типичных случаях входной поток будет иметь всю строку или файл, и только последняя часть будет меньше 5 МБ .

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

1 Ответ

0 голосов
/ 27 июля 2020

Я побеседовал со службой поддержки AWS, и они сообщили, что API многокомпонентной загрузки спроектирован таким образом, что может принимать только одну последнюю часть размером менее 5 МБ. Следовательно, нам необходимо загрузить части таким образом, чтобы они соответствовали этим требованиям.

Чтобы исправить проблему, мы сделали следующее: всякий раз, когда оставшийся размер части был меньше 5 МБ, мы преобразовали входной поток обратно в строка и вернул строку обратно. Функция, создающая строку, получит эту строку и продолжит строить строку дальше оттуда.

Например, допустим, что вся строка будет иметь размер около 701 МБ. Также допустим, что у нас есть функция buildString (), которая создает строку, которую необходимо загрузить. Функция начинает построение строки с нуля, и когда строка пересекает 100 МБ, мы выполняем загрузку нескольких частей для этой строки (AWS рекомендует загружать объект в нескольких частях, если размер объекта превышает 100 МБ). Допустим, размер встроенной строки составляет 101 МБ, и мы выполняем многостраничную загрузку для этой строки. API составной загрузки AWS предполагает, что составной объект должен быть загружен частями по 5 МБ. Следовательно, для строки размером 101 МБ будут загружены 20 частей по 5 МБ, а для оставшегося 1 МБ - вместо загрузки мы преобразуем ее обратно в строку и отправим обратно в функцию buildString (). BuildString () возьмет эту строку и продолжит построение строки оттуда. Когда длина строки снова превысит 100 МБ, те же шаги будут повторены. Таким образом, каждая загружаемая часть будет иметь размер ровно 5 МБ. Единственная оставшаяся часть этой головоломки заключается в том, что прямо в конце, когда buildString () завершит построение строки и загрузит оставшуюся строку, нам не нужно возвращать строку меньше 5 МБ. Вместо этого он должен быть загружен, поскольку это действительно последняя часть строки размером 701 МБ, которая будет меньше 5 МБ.

Следующая таблица дает лучшее представление: Таблица, показывающая длину строки при каждой загрузке нескольких частей

В этом решении метод загрузки составной части будет выглядеть следующим образом:

public String uploadMultipart(boolean isLastPart, InputStream body, long contentLength) throws IOException {
        long partSize = 5242880L;

        long filePosition = 0L;

        String remainderString = null;
        int i;
        for (i = partNumber; filePosition < contentLength; ++i) {

            if ((contentLength - filePosition) < partSize && !isLastPart) {

                /**
                 * Do not publish the part which is less than 5 MB. Instead, convert it to string and return it back.
                 * The reason for that is - AWS only wants one last part to be less than 5 MB.
                 * However, with our custom-built solution to upload the parts while building the string, we would end up with multiple parts less than 5 MB.
                 * To avoid that, we will not publish the part which is less than 5 MB. We will return the string and continue building the string from there.
                 * Only if isLastPart is true (which indicates that we have completed building the string), we will not return the remaining string and rather upload it as
                 * that will indeed be the last part less than 5 MB as expected by AWS.
                 */
                remainderString = getStringFromInputStream(body);
                break;

            } else {

                partSize = Math.min(partSize, (contentLength - filePosition));

                UploadPartRequest uploadRequest = (new UploadPartRequest()).withBucketName(getBucketName()).withKey(getS3Key()).
                        withUploadId(getUploadId()).withPartNumber(i).withInputStream(body).withPartSize(partSize);
                UploadPartResult uploadResult = s3Client.uploadPart(uploadRequest);
                partETags.add(uploadResult.getPartETag());
                filePosition += partSize;
            }
        }
        partNumber = i;
        return remainderString;
    }

Обратите внимание, что имя сегмента, ключ S3 и идентификатор загрузки, заданные в объекте UploadPartRequest, должны в этом случае будет одинаковым для каждой составной загрузки. Только тогда AWS узнает, что эти части связаны с одним и тем же объектом, и в конечном итоге соберет их вместе, чтобы получить наш единственный объект размером 701 МБ, когда в конце мы вызовем функцию completeMultipartUpload () AWS.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...