[Optimization] Redis 도입기: 프론트엔드 캐시를 넘어 백엔드 전역 캐싱으로

그동안 다양한 프로젝트를 진행하며 프론트엔드에서는 react-query를 활용해 효율적으로 캐시를 관리해 왔습니다. 하지만 문득 이런 생각이 들었습니다. "프론트엔드가 매번 캐시 키를 관리하고 설정하는 대신, 백엔드에서 알아서 최적화된 데이터를 주면 더 깔끔하지 않을까?"

이 고민을 해결하기 위해 NestJS와 Redis를 활용하여 백엔드 캐시 시스템을 구축한 과정을 공유합니다.

1. 왜 백엔드 캐싱(Redis)인가?

기존의 프론트엔드 캐싱(react-query)은 클라이언트 사이드에서 개별적으로 관리해야 한다는 번거로움이 있었습니다.

  • 프론트엔드의 부담: 모든 API 요청마다 쿼리 키(Query Key)를 정의하고 관리해야 함.

  • 서버 부하 감소: 백엔드에서 캐싱을 처리하면 여러 사용자가 동일한 데이터를 요청할 때 DB 쿼리를 생략하고 즉시 응답할 수 있음.

  • 일관성: 어떤 클라이언트(웹, 모바일 등)가 요청하더라도 서버에서 동일한 캐시 정책을 적용받음.

2. 인프라 구축: Docker로 1분 만에 Redis 띄우기

이미 프로젝트가 Docker 환경으로 설계되어 있어 Redis 도입은 매우 간단했습니다. docker-compose.yml에 설정 한 줄을 추가하는 것으로 인프라 준비는 끝났습니다.

redis:
  image: redis:latest
  container_name: redis-server
  ports:
    - "${REDIS_PORT}:${REDIS_PORT}"
  restart: unless-stopped

배포 환경에서는 GitHub Secrets와 워크플로우를 통해 .env에 환경 변수를 주입하여 보안과 편의성을 모두 챙겼습니다.


3. NestJS 구현: 선언적이고 유연한 캐시 모듈

단순히 기능을 구현하는 것을 넘어, Prisma Transaction처럼 필요할 때 언제든 쉽게 켜고 끌 수 있는 구조를 목표로 삼았습니다.

🛠️ RedisModule과 Config의 분리

CacheModule을 비동기로 설정하고, 설정을 전담하는 RedisConfigService를 분리하여 모듈 간 의존성을 깔끔하게 정리했습니다. 특히 최신 cache-managerKeyv를 조합하여 확장성을 확보했습니다.

🛠️ CustomCacheInterceptor: 똑똑하게 캐싱하기

모든 요청을 캐싱하는 것이 아니라, 데이터 조희(GET) 요청만 선택적으로 캐싱하도록 커스텀 인터셉터를 설계했습니다. 특히 로그인 유저와 비로그인 유저(guest)를 구분하여 캐시 키를 생성함으로써 데이터 보안까지 고려했습니다.

trackBy(context: ExecutionContext): string | undefined {
  const request = context.switchToHttp().getRequest<Request>();

  if (request.method !== 'GET') return undefined;

  const userId = request.user?.userId ?? 'guest';
  return `http-cache:${request.originalUrl}-${userId}`;
}

4. 실전 적용: 데코레이터 한 줄의 마법

이제 복잡한 로직 필요 없이, 캐싱이 필요한 컨트롤러나 메서드 위에 @UseInterceptors(CustomCacheInterceptor) 한 줄만 붙여주면 끝입니다.

@UseInterceptors(CustomCacheInterceptor)
async findOne(id: number, userId?: number) {
  return this.postService.findOne(id, userId);
}

5. 결과 및 회고: "백엔드 개발의 희열"

이번 Redis 도입을 통해 다음과 같은 이득을 얻었습니다.

  1. DB 부하 개선: 빈번하게 조회되는 게시글 상세 페이지 요청이 DB까지 가지 않고 메모리(Redis)에서 즉시 처리됩니다.

  2. 프론트엔드 코드 간소화: 프론트엔드는 이제 react-query의 복잡한 설정 없이도 최적화된 응답 속도를 누릴 수 있습니다.

  3. 전역적 제어: 백엔드 한곳에서 모든 API 응답 캐싱을 제어할 수 있다는 점이 가장 짜릿한 성취감을 주었습니다.

Redis는 캐시 외에도 세션 관리, 메시지 큐 등 활용도가 무궁무진합니다. 앞으로 블로그를 운영하며 Redis의 다양한 기능을 하나씩 정복해 나갈 예정입니다.