[Nest.js] 성능 최적화와 인증 간소화 하는 캐싱의 모든 것

궁금하면 500원·2024년 8월 18일
1

1.Nest.js에서 캐싱이란?

캐싱(Cache)은 자주 사용되거나 반복되는 데이터를 빠르게 제공하기 위해, 해당 데이터를 메모리나 빠른 저장소에 저장해 두는 기술입니다.
데이터를 요청할 때마다 데이터베이스(DB)나 외부 API로부터 가져오는 대신, 캐시에 저장된 데이터를 활용하여 성능을 향상시키는 방법입니다.

1. 캐싱이 왜 필요한가?

캐싱은 다음과 같은 이유로 필요합니다

  • 성능 향상: DB나 외부 API에 매번 접근하는 대신, 캐시에 저장된 데이터를 바로 제공하여 요청 처리 시간을 크게 단축할 수 있습니다.

  • 부하 감소: 캐싱을 통해 DB나 외부 시스템의 부하를 줄일 수 있습니다.
    특히 자주 사용되는 데이터나 반복적인 요청에서 큰 효과가 있습니다.

  • 네트워크 트래픽 감소: 외부 API나 원격 서버로의 네트워크 호출을 줄여 네트워크 사용량을 줄이고, 응답 속도를 향상시킬 수 있습니다.

2. 캐싱의 사용처는?

  • 데이터베이스 조회 결과: 복잡한 쿼리 결과를 캐싱하여, 동일한 쿼리가 반복될 때 빠르게 결과를 반환합니다.

  • API 응답: 외부 API 호출 결과를 캐싱하여, 동일한 요청에 대해 빠르게 응답을 제공 합니다.

  • 사용자 세션: 사용자 로그인 정보나 인증 토큰 등을 캐싱해 빠르게 인증 처리 합니다.

  • 정적 파일: 자주 사용하는 정적 자산(이미지, CSS, JS 파일)을 캐싱해 빠르게 제공 합니다.

3. 캐싱의 장점은?

  • 빠른 응답: 캐시에 저장된 데이터를 사용해 DB나 API 호출 없이 빠른 응답이 가능 합니다.
  • 성능 최적화: 리소스 사용을 줄여 시스템 성능을 개선 합니다.
  • 부하 감소: 데이터베이스나 외부 API 서버의 부하를 감소시킴으로써 안정성 향상 합니다.

4.캐싱의 단점은?

  • 데이터 동기화 문제: 원본 데이터가 변경되었을 때 캐시된 데이터가 오래된 정보일 수 있으며, 이를 해결하기 위한 캐시 무효화 전략이 필요합니다.

  • 메모리 사용량 증가: 캐시는 메모리에 저장되기 때문에, 메모리 사용량이 증가할 수 있습니다.

  • 복잡성 증가: 캐시 관리, 무효화 전략, 동기화 문제를 해결하기 위해 시스템이 복잡해질 수 있습니다.

2.CacheInterceptor 사용 예제

Nest.js에서 CacheInterceptor를 사용하면 간단하게 캐싱을 적용할 수 있습니다.

기본적으로 Nest.js는 @nestjs/common 패키지에서 캐싱을 제공하며,
Redis 같은 외부 스토리지를 캐시로 사용할 수도 있습니다.

설치
우선, 캐싱을 사용할 수 있도록 필요한 패키지를 설치합니다.

npm install @nestjs/common cache-manager

코드 예제
다음은 CacheInterceptor를 사용하여 간단하게 캐시를 적용하는 방법입니다.

import { CacheInterceptor, Controller, Get, UseInterceptors } from '@nestjs/common';

@Controller('example')
@UseInterceptors(CacheInterceptor)  // 이 컨트롤러에 대해 캐시 적용
export class ExampleController {
  @Get()
  async findAll() {
    // 캐시된 결과가 있으면, 그 결과를 반환
    return { message: '이 데이터는 캐싱됩니다!' };
  }
}

이렇게 @UseInterceptors(CacheInterceptor)를 사용하면 캐싱이 적용된 상태로 응답을 반환합니다.
기본적으로, 캐싱된 데이터는 5초 동안 유지되며 이 시간 이후에는 자동으로 무효화됩니다.

CacheInterceptor 설정

캐시 TTL(time-to-live)을 조정하거나 더 구체적인 캐시 동작을 원할 경우, 글로벌 설정으로 캐시를 설정할 수 있습니다.

import { CacheModule, Module } from '@nestjs/common';

@Module({
  imports: [
    CacheModule.register({
      ttl: 10, // 캐시의 유효 시간 (초 단위)
      max: 100, // 최대 캐시 항목 수
    }),
  ],
})
export class AppModule {}

3. JWT 토큰 캐싱과 인증 프로세스 간소화

JWT 토큰 캐싱을 활용하면 인증 프로세스를 더 빠르게 처리할 수 있습니다.

예를 들어, 사용자 로그인 시 발급된 JWT를 Redis 같은 캐시 스토리지에 저장하고,
추후 요청에서 빠르게 토큰을 검증하는 방법을 사용할 수 있습니다.

캐시를 사용한 토큰 차단 (Token Blacklisting)

로그아웃 시나 특정 상황에서 JWT 토큰을 블락해야 할 경우, 캐시에 해당 토큰을 등록하고 유효성을 검증할 때 캐시에서 확인하는 방법을 사용할 수 있습니다.

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { RedisCacheService } from './redis-cache.service'; // Redis 캐시 서비스

@Injectable()
export class AuthService {
  constructor(private readonly cacheService: RedisCacheService) {}

  async validateToken(token: string): Promise<boolean> {
    const isBlacklisted = await this.cacheService.get(token);
    if (isBlacklisted) {
      throw new UnauthorizedException('블락된 토큰입니다.');
    }
    return true;  // 토큰 검증 로직
  }

  async blockToken(token: string): Promise<void> {
    // 토큰을 블랙리스트에 추가 (예: 1시간 동안 유효)
    await this.cacheService.set(token, true, { ttl: 3600 });
  }
}

Throttling 구현

캐시를 활용한 요청 제한(Throttling)을 구현하여, 특정 시간 내에 과도한 요청을 차단할 수 있습니다.

@Injectable()
export class RateLimiterService {
  constructor(private readonly cacheService: RedisCacheService) {}

  async isRequestAllowed(userId: string): Promise<boolean> {
    const requestCount = await this.cacheService.get(userId);
    if (requestCount >= 5) {
      return false;  // 제한 초과
    }
    await this.cacheService.set(userId, (requestCount || 0) + 1, { ttl: 60 });
    return true;
  }
}

이 방식으로 사용자별로 일정 시간 동안 허용된 요청 수를 초과하면 요청을 차단할 수 있습니다.

4.Redis를 활용한 캐싱 방법

Redis는 메모리 기반의 데이터 저장소로, 캐싱 솔루션으로 자주 사용됩니다.

Nest.js에서 Redis를 사용하기 위해서는 cache-manager와 redis-store 패키지를 사용합니다.

설치

npm install cache-manager cache-manager-redis-store

Redis를 사용한 캐시 설정

CacheModule을 Redis와 함께 설정하여 사용합니다.

import { CacheModule, Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({
      store: redisStore,
      host: 'localhost',  // Redis 호스트
      port: 6379,         // Redis 포트
      ttl: 600,           // 기본 TTL (초)
    }),
  ],
})
export class AppModule {}

Redis를 사용한 캐싱 예제

Redis를 사용하여 데이터 조회 결과를 캐싱하는 예제입니다.

import { CacheService } from '@nestjs/common';

@Injectable()
export class ExampleService {
  constructor(private readonly cacheService: CacheService) {}

  async getCachedData(key: string): Promise<string> {
    const cachedData = await this.cacheService.get(key);
    if (cachedData) {
      return cachedData;  // 캐시된 데이터 반환
    }

    const freshData = '새로운 데이터';
    await this.cacheService.set(key, freshData, { ttl: 300 });  // 5분간 캐싱
    return freshData;
  }
}

Redis를 통해 5분 동안 데이터를 캐싱하고, 그 기간 동안 같은 요청이 들어오면 캐시된 데이터를 반환합니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글