안녕하세요, 백엔드 개발자입니다.
사용자가 이미지를 업로드하고 서비스는 썸네일을 생성하는 기능은 매우 흔합니다. 하지만 이미지 리사이징처럼 CPU를 많이 사용하는 작업을 어떻게 처리하느냐에 따라 서버의 성능은 크게 달라질 수 있습니다.
이번 글에서는 Node.js 서버의 이벤트 루프를 막지 않으면서, 이미지 업로드를 효율적으로 처리하는 전략에 대해 먼저 이야기해 보려 합니다.
1. 일반적인 접근 방식의 잠재적 위험
가장 먼저 떠올릴 수 있는 방법은 서버가 모든 것을 직접 처리하는 것입니다.
- 클라이언트로부터 이미지 파일 전체(예: 10MB)를 서버 메모리에 올립니다.
- 메모리에 올라온 파일을 이미지 처리 라이브러리로 리사이징합니다.
- 결과물을 스토리지(S3 등)에 업로드합니다.
- 업로드된 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 라이브러리의 구체적인 설정과 동작 원리에 대해 자세히 알아보겠습니다.
긴 글 읽어주셔서 감사합니다.
'Backend' 카테고리의 다른 글
| 나만의 데코레이터 만들기: 실전 기능 구현 (0) | 2025.08.29 |
|---|---|
| 나만의 데코레이터 만들기: NestJS에서 반복적인 API 설정 줄이기 (0) | 2025.08.27 |
| 서버 부하를 줄이는 이미지 업로드 전략 (3편): 유연한 썸네일 URL 관리 패턴 (0) | 2025.08.24 |
| 서버 부하를 줄이는 이미지 업로드 전략 (2편): Multer 스트리밍 파이프라인 구현 (0) | 2025.08.21 |
| 전화번호 OTP 인증, 안전하게 구현하기 (0) | 2025.08.16 |
