TIL - 20260305

juni·2026년 3월 5일

TIL

목록 보기
285/316

0305 NestJS 심화 (5/N): 캐싱(Caching)과 큐(Queue)


✅ 1. 캐싱 (Caching)

  • 캐싱은 자주 사용되지만 잘 변하지 않는 데이터나, 계산 비용이 비싼 작업의 결과를 메모리와 같은 빠른 저장소에 임시로 저장해두고, 다음 요청 시에는 실제 작업을 다시 수행하는 대신 저장된 결과를 즉시 반환하는 기술입니다.

  • 핵심 목표: 데이터베이스나 외부 API에 대한 반복적인 호출을 줄여, 애플리케이션의 응답 속도를 향상시키고 시스템 부하를 감소시킵니다.

➕ NestJS의 캐시 추상화 (@nestjs/cache-manager)

  • NestJS는 @nestjs/cache-manager 모듈을 통해, 기반 기술(인메모리, Redis 등)에 상관없이 일관된 방식으로 캐싱을 적용할 수 있도록 캐시 추상화를 제공합니다.

  • 사용법:

    1. 패키지 설치: npm install @nestjs/cache-manager (Redis를 사용하려면 cache-manager-redis-store 등 추가 설치)

    2. 루트 모듈(app.module.ts)에 CacheModule 등록:

      import { CacheModule } from '@nestjs/cache-manager';
      
      @Module({
        imports: [CacheModule.register({ isGlobal: true })], // isGlobal: true로 전역 설정
      })
      export class AppModule {}
    3. CacheInterceptor 또는 @UseInterceptors 사용:

      • 자동 캐싱: 특정 컨트롤러나 메서드에 CacheInterceptor를 적용하면, 해당 엔드포인트의 GET 요청에 대한 응답이 자동으로 캐싱됩니다.
      • @CacheKey(): 캐시를 구분할 키를 지정.
      • @CacheTTL(): 캐시의 유효 시간(Time-To-Live)을 밀리초 단위로 지정.
    // products.controller.ts
    @Controller('products')
    @UseInterceptors(CacheInterceptor) // 이 컨트롤러의 모든 GET 요청에 캐싱 적용
    export class ProductsController {
      
      @Get(':id')
      @CacheTTL(30 * 1000) // 이 엔드포인트의 캐시는 30초 동안 유효
      findProduct(@Param('id') id: string) {
        // 이 로직은 30초에 한 번만 실행됨
        return this.productsService.findOne(id);
      }
    }
  • 프로그래매틱 캐싱: 인터셉터 방식 외에도, Cache 객체를 직접 주입(@Inject(CACHE_MANAGER))받아, 서비스 로직 내에서 cacheManager.get()이나 cacheManager.set()을 사용하여 수동으로 캐시를 제어할 수도 있습니다.


✅ 2. 큐 (Queue)를 이용한 비동기 작업 처리

  • 문제점: 이메일 발송, 이미지 리사이징, 동영상 인코딩 등 시간이 오래 걸리거나 즉각적인 결과가 필요 없는 작업을 동기(Synchronous) 방식으로 처리하면, 사용자는 작업이 끝날 때까지 기다려야 하므로 사용자 경험(UX)이 저하됩니다.

  • 큐 (Queue): 이러한 작업을 처리하기 위한 "작업 대기열"을 제공하는 시스템입니다. 요청을 받은 즉시 "작업을 등록했습니다"라고 빠르게 응답하고, 실제 오래 걸리는 작업은 백그라운드에서 별도의 워커(Worker) 프로세스가 처리하도록 위임합니다.

➕ NestJS와 Bull

  • Bull: Node.js 생태계에서 널리 사용되는, Redis를 기반으로 동작하는 강력하고 안정적인 큐 라이브러리입니다.
  • @nestjs/bull: Bull을 NestJS에서 쉽게 사용할 수 있도록 통합해주는 모듈입니다.

➕ Bull의 동작 흐름 (Producer-Consumer)

  1. Producer (생산자):

    • 역할: 처리해야 할 작업을 큐에 추가(add)하는 주체. (주로 컨트롤러나 서비스)
    • @InjectQueue() 데코레이터를 사용하여 특정 큐를 주입받고, queue.add('jobName', data) 메서드를 호출하여 작업을 등록합니다.
  2. Consumer (소비자/워커):

    • 역할: 큐를 계속 지켜보고 있다가, 새로운 작업이 들어오면 이를 가져와서 처리하는 주체.
    • @Processor() 데코레이터가 붙은 클래스로, 실제 작업 로직을 담고 있습니다.
    • @Process('jobName') 데코레이터가 붙은 메서드에서, 해당 이름의 작업을 어떻게 처리할지를 구현합니다.
  • 사용법:

    1. 패키지 설치: npm install @nestjs/bull bull
    2. 모듈 설정: BullModule.forRoot()로 Redis 연결 정보를, BullModule.registerQueue()로 사용할 큐의 이름을 등록합니다.
    3. Producer와 Consumer 구현:
    // image.service.ts (Producer)
    import { InjectQueue } from '@nestjs/bull';
    import { Queue } from 'bull';
    
    @Injectable()
    export class ImageService {
      constructor(@InjectQueue('image-processing') private imageQueue: Queue) {}
    
      async processImage(file) {
        // 작업을 큐에 등록하고 즉시 반환
        await this.imageQueue.add('resize', {
          file: file.path,
        });
        return { message: '이미지 처리 작업이 등록되었습니다.' };
      }
    }
    
    // image.processor.ts (Consumer)
    import { Process, Processor } from '@nestjs/bull';
    import { Job } from 'bull';
    
    @Processor('image-processing') // 'image-processing' 큐를 처리
    export class ImageProcessor {
      @Process('resize') // 'resize' 이름의 작업을 처리
      async handleResize(job: Job) {
        console.log('이미지 리사이징 시작:', job.data.file);
        // ... 시간이 오래 걸리는 실제 이미지 처리 로직 ...
        console.log('이미지 리사이징 완료');
      }
    }

📌 요약

  • 캐싱 (@nestjs/cache-manager)은 반복적인 데이터 조회나 계산 결과를 임시 저장하여, 애플리케이션의 응답 속도를 향상시키고 DB 부하를 줄이는 핵심적인 성능 최적화 기법입니다.
  • 큐 (@nestjs/bull)는 시간이 오래 걸리는 작업을 백그라운드에서 비동기적으로 처리하기 위한 시스템입니다.
  • 큐를 사용하면, 생산자(Producer)는 작업을 등록하고 즉시 응답할 수 있어 사용자 경험(UX)을 향상시키고, 소비자(Consumer)는 자신의 속도에 맞춰 안정적으로 작업을 처리하여 시스템의 탄력성(Resilience)을 높일 수 있습니다.

0개의 댓글