프로젝트를 진행하면서, 서버의 케시 적용은 오픈 일정에 맞추기 위해 우선 순위에서 밀려 항상 뒷전으로 밀리는 경우가 많았고, 적용하지 못하고 오픈하는 경우가 다반사 였습니다. 프로젝트 후반에 적용 하려면 테스트 중 변수도 많고, 잘 작동하지 않게되면 캐시적용 포기하고 오픈하게 됩니다.
서버 캐시는 노력대비 비용 절감 효과가 매우 크기 때문에, 필요한 서버에는 꼭 적용하고, 그리고 서버 설정 초반에 구성 하는것을 추천 드립니다.
프로젝트 셋업 부분은 복잡하지 않기 때문에, 설정 절차와 제가 사용하는 설정 부분 중심으로 기술하겠습니다. 좀더 다양한 설정은configuration 설정부분과 caching 설정부분을 참고하세요.
주의
Nest의 caching 설정 문서에는 ttl 설정이 sec(초) 단위인 것처럼 기술 되어있는데, cache manager에서는 밀리세컨드(milliseconds)로 처리되는것 같습니다.
$ npx @nestjs/cli new nest-cache
$ cd new nest-cache
$ yarn add @nestjs/config
$ yarn add cache-manager
$ yarn add class-validator class-transformer
#
# .env
#
# (로컬)실행 포트 설정
PORT=3000
# 캐시 TTL 설정
CACHE_TTL=30000
캐싱이 적용되지 않는 path를 지정하거나, query parameter에 noCache=true 요청이 오면, 캐싱하지 않도록 처리 합니다.
//
// http-cache.interceptor.ts
//
import { CacheInterceptor, ExecutionContext, Injectable } from '@nestjs/common';
const excludePaths = [
// 캐시가 적용되지 않아야 할 path 목록 ()
/(\/sample2\/)(.*)/i,
];
@Injectable()
export class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
const request = context.switchToHttp().getRequest();
const { query } = request;
const { httpAdapter } = this.httpAdapterHost;
// Get Request가 아닌 request 처리
const isGetRequest = httpAdapter.getRequestMethod(request) === 'GET';
if (!isGetRequest) {
return undefined;
}
// noCache=true query parameter 처리
const noCache = query.noCache && query.noCache.toLowerCase() === 'true';
if (noCache) {
return undefined;
}
// 설정된 캐시 예외 path 처리
const requestPath = httpAdapter.getRequestUrl(request).split('?')[0];
const exclude = excludePaths.find((path) => {
return path.test(requestPath);
});
if (exclude) {
return undefined;
}
return httpAdapter.getRequestUrl(request);
}
}
//
// app.module.ts
//
import { CacheModule, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HttpCacheInterceptor } from './http-cache.interceptor';
@Module({
imports: [
// config module 설정
ConfigModule.forRoot({
isGlobal: true,
}),
// cache module 설정
CacheModule.register({
isGlobal: true,
ttl: +(process.env.CACHE_TTL ?? 3000),
}),
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: HttpCacheInterceptor,
},
],
})
export class AppModule {}
app.module에 global 캐시 설정으로, 전체 NestJS app에서 캐시 기능을 사용할 수 있습니다.
필요에 따라 app.module에 global 설정을 적용하지 않고 하위 모듈에 부분적으로 캐시를 적용할 수 있습니다.
'providers' 부분은, 기본적으로 제공되는 CacheInterceptor를 사용하지 않고, CacheInterceptor를 상속받아 커스터마이징 한 HttpCacheInterceptor를 적용하도록 하였습니다.
위의 설정은 앱 전반에 걸쳐 공통으로 캐시 인터셉터를 사용할때 설정 입니다.
캐시 ttl은 환경변수로 적용하여 개발 테스트시와 서비스 적용시 필요한 값이 적용 될 수 있도록 하였습니다.
캐시 설정 관련 추가 옵션들의 설명은 공식문서를 참고하세요.
//
// app.controller.ts
//
import {
CACHE_MANAGER,
Controller,
Get,
Inject,
Param,
Query,
UseInterceptors,
} from '@nestjs/common';
import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { AppService } from './app.service';
import { HttpCacheInterceptor } from './http-cache.interceptor';
import { Cache } from 'cache-manager';
// 샘플 query dto
export class QueryDto {
@IsBoolean()
@Transform(({ value }) => value.toLowerCase() === 'true')
readonly noCache: boolean;
}
@Controller()
@UseInterceptors(HttpCacheInterceptor) // 캐시 인터셉터 설정
export class AppController {
constructor(
private readonly appService: AppService,
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
) {}
@Get()
getHello(): string {
console.log('hello called');
return this.appService.getHello();
}
@Get('sample1')
getSample1(): string {
console.log('sample1 called');
return this.appService.getSample1();
}
@Get('sample2/:key')
async getSample2(
@Param() key: string,
@Query() query: QueryDto,
): Promise<any> {
console.log('sample2 called');
// no cache 일 경우
if (query.noCache) {
return this.appService.getSample2();
}
// cache manager에서 데이터 조회
let value = await this.cacheManager.get(key);
if (!value) {
value = this.appService.getSample2();
this.cacheManager.set(key, value);
}
//
// 다른 처리들 ,,,
//
return value;
}
}
'/sample2/:key' 요청은 HttpCacheInterceptor에서 캐시 예외 path로 지정하였기 때문에, 클라이언트의 모든 요청에대해 함수가 실행 됩니다. 모든 요청을 받아 직접 query parmater를 처리하고, cache-manager를 사용하여 캐싱 처리를 합니다. 캐싱은 필요 하지만, 요청에대한 후속처리 (통계처리 같은..)가 필요할때 사용하는 코드 샘플 입니다.
케싱 데이터가 크지 않다면 max 설정 정도로 과도한 메모리 사용의 방지 처리와 함께 사용해도 좋지만, 데이터가 크고 많다면 Redis 와 연동하여 사용하는것을 추천합니다. 설정 방법은 공식문서를 참고하세요.
또한 cache-manager를 이용하는 상세한 방법은 cache-manager 패키지 공식 사이트를 참고 하세요.
https://docs.nestjs.com/techniques/caching
https://github.com/nestjs/nest/blob/master/sample/20-cache/src/common/http-cache.interceptor.ts
https://dev.to/secmohammed/nestjs-caching-globally-neatly-1e17