#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 деталей запросы на выборку внезапно начинают сбоить, и детали больше не загружаются. Если бы кто-то знал, что происходит, это было бы потрясающе. Я публикую это как ответ, потому что думаю, что большинство людей сочтут это очень полезным.