본문으로 바로가기
2222

안녕하세요, 백엔드 개발자입니다.

사용자가 이미지를 업로드하고 서비스는 썸네일을 생성하는 기능은 매우 흔합니다. 하지만 이미지 리사이징처럼 CPU를 많이 사용하는 작업을 어떻게 처리하느냐에 따라 서버의 성능은 크게 달라질 수 있습니다.

이번 글에서는 Node.js 서버의 이벤트 루프를 막지 않으면서, 이미지 업로드를 효율적으로 처리하는 전략에 대해 먼저 이야기해 보려 합니다.

1. 일반적인 접근 방식의 잠재적 위험

가장 먼저 떠올릴 수 있는 방법은 서버가 모든 것을 직접 처리하는 것입니다.

  1. 클라이언트로부터 이미지 파일 전체(예: 10MB)를 서버 메모리에 올립니다.
  2. 메모리에 올라온 파일을 이미지 처리 라이브러리로 리사이징합니다.
  3. 결과물을 스토리지(S3 등)에 업로드합니다.
  4. 업로드된 URL을 DB에 저장합니다.

이 방식은 Node.js 환경에서 큰 약점을 가집니다. 이미지 리사이징 같은 무거운 작업은 싱글 스레드 기반인 Node.js의 이벤트 루프를 막아, 다른 모든 요청의 처리를 지연시킬 수 있습니다. 사용자가 몰리는 시간에는 서버 전체가 느려지는 원인이 됩니다.

2. 해결 전략: 스트리밍 파이프라인과 역할 분리

이 문제를 해결하는 핵심은 파일을 통째로 메모리에 올려 동기적으로 처리하는 대신, 작은 조각의 흐름(Stream)으로 다루는 것입니다. Node.js의 비동기적 장점을 극대화하는 이 방식을 통해, 무거운 파일 처리 중에도 서버는 다른 요청에 응답할 수 있는 여유를 갖게 됩니다.

NestJS에서는 미들웨어나 인터셉터를 사용하여 이러한 스트리밍 파이프라인을 비즈니스 로직과 분리할 수 있습니다. multer와 같은 라이브러리를 활용하면, 파일 스트림이 컨트롤러에 도달하기 전에 S3로 바로 전송되며, 그 과정에서 이미지 변환(리사이징)까지 함께 처리됩니다.

이 전략을 사용하면, 컨트롤러가 실행되는 시점에는 이미 모든 파일 처리가 스트리밍 방식으로 완료된 상태가 됩니다.

3. 컨트롤러의 책임: 비즈니스 로직과 메타데이터 저장

파일 처리가 분리되면, 컨트롤러의 코드는 비즈니스 로직에만 집중하여 매우 간결해집니다.

// resource.controller.ts (역할 분리 후)

import { Controller, Post, UploadedFile } from '@nestjs/common';
import { ResourceService } from '../services/resource.service';

@Controller('resources')
export class ResourceController {
  constructor(private readonly resourceService: ResourceService) {}

  @Post('/upload/image')
  async uploadImageFile(@UploadedFile() file: any) {
    // 이 시점에서 'file' 객체에는 스트리밍 파이프라인을 거친 결과,
    // 즉 S3 주소, 파일 크기 등의 메타데이터가 모두 담겨 있습니다.

    const resource = await this.resourceService.create({
      resource_type: 'image',
      resource_key: file.key,
      resource_url: file.location,
      filesize: file.size,
      // ... 기타 파일 메타데이터 ...
    });

    return this.resourceService.mapData(resource);
  }
}

이제 컨트롤러는 무거운 이미지 처리 작업에서 벗어나, "업로드된 파일의 메타데이터를 DB에 저장한다"는 자신의 핵심 책임에만 집중할 수 있습니다.

마치며

이번 1편에서는 이미지 업로드 시 발생할 수 있는 서버 부하 문제를 해결하기 위해, 스트리밍 파이프라인을 구성하고 비즈니스 로직과 역할을 분리하는 전략에 대해 알아보았습니다.

다음 2편에서는 이 스트리밍 파이프라인을 실제로 구현해 주는 multer와 multer-s3-transform 라이브러리의 구체적인 설정과 동작 원리에 대해 자세히 알아보겠습니다.

긴 글 읽어주셔서 감사합니다.