Размер составной части загрузки Amazon S3 через lambda

#amazon-web-services #amazon-s3

Вопрос:

У меня есть несколько лямбда-функций, которые позволяют выполнять составную загрузку в корзину Amazon S3. Они отвечают за создание составной загрузки, затем еще одной для каждой загрузки части и последней для завершения загрузки.

Первые два, кажется, работают нормально (они отвечают кодом состояния 200), но последний не работает. В Cloudwatch я вижу сообщение об ошибке «Предлагаемая вами загрузка меньше минимально допустимого размера».

Это неправда, так как я загружаю файлы размером более 5 МБ минимального размера, указанного в документах. Тем не менее, я думаю, что проблема возникает при каждой загрузке отдельной детали.

Почему? Потому что каждая часть содержит только 2 Мб данных. В документах я вижу, что каждая часть, кроме последней, должна иметь размер не менее 5 МБ. Однако, когда я пытаюсь загрузить детали размером более 2 МБ, я получаю ошибку CORS, скорее всего, из-за того, что я превысил лимит полезной нагрузки 6 МБ лямбда.

Кто-нибудь может мне в этом помочь? Ниже я оставляю свой код на стороне клиента, на случай, если вы увидите в нем какую-либо ошибку.

 setLoading(true);
const file = files[0];
const size = 2000000;
const extension = file.name.substring(file.name.lastIndexOf('.'));
try {
  const multiStartResponse = await startMultiPartUpload({ fileType: extension });
  console.log(multiStartResponse);
  let part = 1;
  let parts = [];
  /* eslint-disable no-await-in-loop */
  for (let start = 0; start < file.size; start  = size) {
    const chunk = file.slice(start, start   size   1);
    const textChunk = await chunk.text();
    const partResponse = await uploadPart({
      file: textChunk,
      fileKey: multiStartResponse.data.Key,
      partNumber: part,
      uploadId: multiStartResponse.data.UploadId,
    });
    console.log(partResponse);
    parts.push({ ETag: partResponse.data.ETag, PartNumber: part });
    part  ;
  }
  /* eslint-enable no-await-in-loop */
  const completeResponse = await completeMultiPartUpload({
    fileKey: multiStartResponse.data.Key,
    uploadId: multiStartResponse.data.UploadId,
    parts,
  });
  console.log(completeResponse);
} catch (e) {
  console.log(e);
} finally {
  setLoading(false);
}
 

Ответ №1:

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

Теперь наша лямбда-функция startMultiPartUpload возвращает не только идентификатор загрузки, но и набор подписанных адресов, сгенерированных с помощью класса aws-sdk, с использованием метода getSignedUrlPromise и операции «uploadPart», как показано ниже:

 const getSignedPartURL = (bucket, fileKey, uploadId, partNumber) =>
   s3.getSignedUrlPromise('uploadPart', { Bucket: bucket, Key: fileKey, UploadId: 
   uploadId, PartNumber: partNumber })
 

Кроме того, поскольку загрузка части таким образом не возвращает ETag (или, может быть, возвращает, но я просто не смог этого добиться), нам нужно вызвать метод listParts в классе S3 после загрузки каждой части, чтобы получить эти ETag. Я оставлю свой код реакции ниже:

 const uploadPart = async (url, data) => {
try {
// return await uploadPartToS3(url, data);
return fetch(url, {
  method: 'PUT',
  body: data,
 }).then((e) => e.body);
} catch (e) {
 console.error(e);
 throw new Error('Unknown error');
 }
};

// If file is bigger than 50Mb then perform a multi part upload
const uploadMultiPart = async ({ name, size, originFileObj }, 
 updateUploadingMedia) => {
// chunk size determines each part size. This needs to be > 5Mb
const chunkSize = 60000000;
let chunkStart = 0;
const extension = name.substring(name.lastIndexOf('.'));
const partsQuan = Math.ceil(size / chunkSize);
// Start multi part upload. This returns both uploadId and signed urls for each 
part.
const startResponse = await startMultiPartUpload({
  fileType: extension,
  chunksQuan: partsQuan,
});
console.log('start response: ', startResponse);
const {
  signedURLs,
  startUploadResponse: { Key, UploadId },
} = startResponse.data;
try {
  let promises = [];
  /* eslint-disable no-await-in-loop */
  for (let i = 0; i < partsQuan; i  ) {
    // Split file into parts and upload each one to it's signed url
    const chunk = await originFileObj.slice(chunkStart, chunkStart   
   chunkSize).arrayBuffer();
  chunkStart  = chunkSize;
  promises.push(uploadPart(signedURLs[i], chunk));
  if (promises.length === 5) {
    await Promise.all(promises);
    promises = [];
  }
  console.log('UPLOAD PART RESPONSE', uploadResponse);
}
/* eslint-enable no-await-in-loop */
// wait until every part is uploaded
await allProgress({ promises, name }, (media) => {
  updateUploadingMedia(media);
});

// Get parts list to build complete request (each upload does not retrieve ETag)
const partsList = await listParts({
  fileKey: Key,
  uploadId: UploadId,
});
// build parts object for complete upload
const completeParts = partsList.data.Parts.map(({ PartNumber, ETag }) => ({
  ETag,
  PartNumber,
}));
// Complete multi part upload
completeMultiPartUpload({
  fileKey: Key,
  uploadId: UploadId,
  parts: completeParts,
});
return Key;
} catch (e) {
  console.error('ERROR', e);
  const abortResponse = await abortUpload({
    fileKey: Key,
    uploadId: UploadId,
  });
  console.error(abortResponse);
}
};
 

Извините за идентификацию, я исправил ее строчку за строчкой, как мог :).

Некоторые соображения:

-Мы используем блоки размером 60 МБ, потому что наш сервер слишком долго создавал все эти подписанные URL-адреса для больших файлов.

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

Тем не менее, мы все еще сталкиваемся с проблемами при загрузке огромных файлов (около 35 ГБ), так как после загрузки 100/120 деталей запросы на выборку внезапно начинают сбоить, и детали больше не загружаются. Если бы кто-то знал, что происходит, это было бы потрясающе. Я публикую это как ответ, потому что думаю, что большинство людей сочтут это очень полезным.