때는 4주차 데모데이 분명 어제까지만 해도 텅 비어있던 캠퍼들의 배포 페이지가 가득 채워지는 마법의 날짜..
저희 팀 역시 데모데이를 거쳤고, 배포 링크를 데모 발표 그룹원들에게 제공하였습니다. 발표는 성공적으로 마쳤으나, 걱정되는 점이 있었습니다.
시험삼아 넣어둔 수많은 로그에서 Top5 트래픽, 응답 성공률 등의 메트릭 계산 API를 캐싱 없이 제공하였는데.. 우리의 DB서버가 잘 버티고 있을까?
Prometheus, Grafana, Node-Exporter로 시스템 모니터링을 도입해 두었기에thanks to 병우님, 떨리는 마음으로 저희의 DB서버 metric을 확인해보았습니다.
역시나 발표 중 데모를 선보인 2분 동안으로 좁혀서 확인해본 결과 문제가 될만한 지표들을 볼 수 있었습니다 😨
특히 Sys Load(모든 CPU 코어에 걸친 시스템 부하의 합계)
가 400%를 넘어갔으나 저희가 실제 DB 서버에 사용중인 NCP Server는 두개 코어입니다. 이는 실행 중인 프로세스가 두개 코어의 계산 능력을 초과해, CPU 스케줄러가 처리해야 할 대기 작업이 생기고 있음을 의미합니다. ClickHouse DB를 사용하며 트래픽 데이터에 대한 I/O와 연산 작업이 많이 발생하니 당연한 결과였습니다.
이를 해결하기 위해, NestJS로 구현된 콘솔 API 서버에 Redis로 캐싱을 도입하기로 결심하였습니다. 단순 맵 객체로 캐싱할 수도 있겠지만 Blue-Green 컨테이너로 무중단 배포를 도입해두어 캐시 동기화가 필요했고, 당장 다양한 데이터 구조를 저장하기에도 편리하여 Redis를 선택했습니다.
다양한 방법으로 도입할 수 있겠지만, NestJS 공식 문서에서 소개하는 Cache-Manager를 사용하기로 결정하였습니다. 이미 NestJS에서 관리되고 있는 모듈과 특정한 버전이 설치되어 있어 호환성을 유지하기 위함이었습니다. NestJS 모듈로서 구현되었고, 다양한 어노테이션을 제공한다는 편의성도 선택의 이유가 되었습니다.
참고: "@nestjs/core": "^10.0.0"
, node v22.11.0
, npm v10.9.0
사용
npm install @nestjs/cache-manager cache-manager
npm install cache-manager-redis-yet
위와 같이 필요한 모듈을 설치해줍니다. cache-manager 버전이 v6라면 cache-manager-redis-yet이 호환되지 않는다는 warning이 표시되지만, nestjs에서 제공하는 cache-manager 모듈이 v5이며 현재는 문제가 없고, 추후 유지/보수하기에도 공식 문서를 따르는 편이 낫지 않을까 싶었습니다. (다른 버전/다른 라이브러리를 설치하면 더 많은 warning이 표시되었습니다..)
본격적으로 cache-manager 모듈을 import하고 config를 넣어주며 세팅해줍시다. configuration 코드를 다른 파일로 분리하고, 모듈 선언부를 깔끔하게 유지하기 위해서 registerAs()
와 asProvider()
를 활용하고 있습니다.
import { registerAs } from '@nestjs/config';
import { redisStore } from 'cache-manager-redis-yet';
export default registerAs('redisConfig', async () => {
const store = await redisStore({
socket: {
host: process.env.REDIS_HOST || 'localhost',
port: Number(process.env.REDIS_PORT) || 6379,
},
});
return {
store: store,
ttl: 3 * 60 * 1000,
};
});
configuaration을 provider로 사용할 수 있게 등록하고 아래와 같이 사용합니다.