← Назад к вопросам

Опишите код эндпоинта для загрузки большого файла на S3

3.0 Senior🔥 171 комментариев
#API и сетевые протоколы#DevOps и инфраструктура

Условие

Опишите словами (или в псевдокоде) эндпоинт, который должен сохранить файл размером 4 гигабайта, загруженный с клиента, и положить его на S3 или другой CDN.

Ключевые аспекты для обсуждения:

  1. Почему нельзя просто загрузить весь файл в память?
  2. Как использовать streams для обработки больших файлов?
  3. Какой middleware использовать для multipart uploads?
  4. Как реализовать progress tracking?
  5. Как обработать ошибки и retry?

Что проверяется

  • Понимание streams в Node.js
  • Работа с большими файлами
  • Знание AWS S3 SDK или аналогов
  • Понимание back pressure

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Загрузка 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, не буферизируй весь файл в памяти