캐싱(Cache)은 자주 사용되거나 반복되는 데이터를 빠르게 제공하기 위해, 해당 데이터를 메모리나 빠른 저장소에 저장해 두는 기술입니다.
데이터를 요청할 때마다 데이터베이스(DB)나 외부 API로부터 가져오는 대신, 캐시에 저장된 데이터를 활용하여 성능을 향상시키는 방법입니다.
캐싱은 다음과 같은 이유로 필요합니다
성능 향상: DB나 외부 API에 매번 접근하는 대신, 캐시에 저장된 데이터를 바로 제공하여 요청 처리 시간을 크게 단축할 수 있습니다.
부하 감소: 캐싱을 통해 DB나 외부 시스템의 부하를 줄일 수 있습니다.
특히 자주 사용되는 데이터나 반복적인 요청에서 큰 효과가 있습니다.
네트워크 트래픽 감소: 외부 API나 원격 서버로의 네트워크 호출을 줄여 네트워크 사용량을 줄이고, 응답 속도를 향상시킬 수 있습니다.
데이터베이스 조회 결과: 복잡한 쿼리 결과를 캐싱하여, 동일한 쿼리가 반복될 때 빠르게 결과를 반환합니다.
API 응답: 외부 API 호출 결과를 캐싱하여, 동일한 요청에 대해 빠르게 응답을 제공 합니다.
사용자 세션: 사용자 로그인 정보나 인증 토큰 등을 캐싱해 빠르게 인증 처리 합니다.
정적 파일: 자주 사용하는 정적 자산(이미지, CSS, JS 파일)을 캐싱해 빠르게 제공 합니다.
데이터 동기화 문제: 원본 데이터가 변경되었을 때 캐시된 데이터가 오래된 정보일 수 있으며, 이를 해결하기 위한 캐시 무효화 전략이 필요합니다.
메모리 사용량 증가: 캐시는 메모리에 저장되기 때문에, 메모리 사용량이 증가할 수 있습니다.
복잡성 증가: 캐시 관리, 무효화 전략, 동기화 문제를 해결하기 위해 시스템이 복잡해질 수 있습니다.
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 {}
JWT 토큰 캐싱을 활용하면 인증 프로세스를 더 빠르게 처리할 수 있습니다.
예를 들어, 사용자 로그인 시 발급된 JWT를 Redis 같은 캐시 스토리지에 저장하고,
추후 요청에서 빠르게 토큰을 검증하는 방법을 사용할 수 있습니다.
로그아웃 시나 특정 상황에서 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)을 구현하여, 특정 시간 내에 과도한 요청을 차단할 수 있습니다.
@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;
}
}
이 방식으로 사용자별로 일정 시간 동안 허용된 요청 수를 초과하면 요청을 차단할 수 있습니다.
Redis는 메모리 기반의 데이터 저장소로, 캐싱 솔루션으로 자주 사용됩니다.
Nest.js에서 Redis를 사용하기 위해서는 cache-manager와 redis-store 패키지를 사용합니다.
설치
npm install cache-manager cache-manager-redis-store
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를 사용하여 데이터 조회 결과를 캐싱하는 예제입니다.
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분 동안 데이터를 캐싱하고, 그 기간 동안 같은 요청이 들어오면 캐시된 데이터를 반환합니다.