Опишите код эндпоинта для загрузки большого файла на S3
Условие
Опишите словами (или в псевдокоде) эндпоинт, который должен сохранить файл размером 4 гигабайта, загруженный с клиента, и положить его на S3 или другой CDN.
Ключевые аспекты для обсуждения:
- Почему нельзя просто загрузить весь файл в память?
- Как использовать streams для обработки больших файлов?
- Какой middleware использовать для multipart uploads?
- Как реализовать progress tracking?
- Как обработать ошибки и retry?
Что проверяется
- Понимание streams в Node.js
- Работа с большими файлами
- Знание AWS S3 SDK или аналогов
- Понимание back pressure
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Загрузка 4 ГБ файла требует архитектурного подхода. Нельзя загружать всё в память.
Почему не в память?
Node.js может использовать максимум ~1.4 ГБ памяти (V8 heap). Загрузка 4 ГБ файла приведёт к OutOfMemory error и падению процесса. Весь сервер зависнет.
Правильный подход: Streams + S3 Multipart Upload
1. Используем AWS SDK с lib-storage
import { Upload } from '@aws-sdk/lib-storage';
import { S3Client } from '@aws-sdk/client-s3';
const s3Client = new S3Client({ region: 'us-east-1' });
app.post('/upload', async (req, res) => {
const bucketName = process.env.AWS_BUCKET;
const key = `uploads/${Date.now()}`;
const totalSize = parseInt(req.headers['content-length'] || '0', 10);
try {
const upload = new Upload({
client: s3Client,
params: {
Bucket: bucketName,
Key: key,
Body: req, // Поток напрямую
ContentType: req.headers['content-type'],
},
partSize: 5 * 1024 * 1024,
});
upload.on('httpUploadProgress', (progress) => {
const percentage = Math.round((progress.loaded / totalSize) * 100);
console.log(`Progress: ${percentage}%`);
});
const result = await upload.done();
res.json({ success: true, url: result.Location });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
2. Back Pressure - управление буфером
Когда читаем быстрее, чем S3 принимает, буфер переполняется:
if (!writable.write(chunk)) {
readable.pause();
}
writable.on('drain', () => {
readable.resume();
});
3. Multipart Upload S3
- Каждый part: минимум 5 МБ (кроме последнего)
- Максимум 10000 parts
- Каждый part имеет ETag для проверки целостности
4. Прогресс через WebSocket
upload.on('httpUploadProgress', (progress) => {
socket.emit('progress', {
percentage: Math.round((progress.loaded / totalSize) * 100),
loadedMB: (progress.loaded / 1024 / 1024).toFixed(2)
});
});
5. Retry логика
async function uploadWithRetry(params, attempt = 0) {
try {
return await new Upload({ client: s3Client, params }).done();
} catch (error) {
if (attempt < 3) {
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
return uploadWithRetry(params, attempt + 1);
}
throw error;
}
}
Почему этот подход правильный?
Память: O(partSize) вместо O(fileSize) - только текущая часть в буфере
Надёжность: Если upload сбился на part #500, не нужно загружать всё заново
Масштабируемость: Несколько users загружают одновременно
Прогресс: Отслеживаем в реальном времени через WebSocket
Альтернативы
Busboy middleware: упрощает обработку multipart form-data
Multer-S3: готовая интеграция между multipart и S3
Chunked upload: клиент разбивает файл, загружает chunks параллельно
Основное правило: используй streams, не буферизируй весь файл в памяти