운영중인 서비스에서 응답시간이 느려진 부분이 있어서, 이를 개선하고 싶었다.
마침 유저 구분이 필요 없고, 수정이 거의 없는 페이지라서 캐싱 전략을 사용하기 적합해 보였다!
해당 페이지에 대해 간략하게 설명하자면,
당일 새벽시간의 뉴스데이터를 전부 분석을 해서 아침 8시에 그날의 뉴스를 한눈에 정리해서 보여주는 페이지다.
따라서 유저별로 개인화가 들어가는 페이지도 아니고, 웬만해서는 수정이 이뤄지지 않는 API 이다.
그럼 바로 캐싱작업을 시작해보자.
설치
해당 프로젝트로 이동해서 필요한 패키지를 몇가지 설치해주자.
npm install cache-manager
cache-manager: 캐싱을 담당할 라이브러리다.
npm install @nestjs/common
nestjs의 CACHE_MANAGER: NestJS에서 캐싱을 쉽게 관리하기 위한 것이니까 이것도 설치해줘야된다.
(일반적으로 NestJS 프로젝트를 시작할 때 이미 @nestjs/common은 설치되어 있을것이다.)
npm install @types/cache-manager
TypeScript: 만약 TypeScript를 사용하고 있다면, @types 패키지도 설치해야 할 수도 있다.
이제 전부 설치를 했으니 코드로 가보자.
캐싱 처리 코드
해당 @Get 요청을 하는 API의 Service부로 이동하자.
먼저 캐싱 처리 코드는 이런식으로 구현을 했는데, 이제 하나씩 코드를 살펴보자.
import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Injectable()
export class TodayService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
1. @Inject(CACHE_MANAGER)
NestJS에서 Dependency Injection을 사용하면서 @Inject 데코레이터를 사용한다.
여기서 CACHE_MANAGER는 특정 서비스나 객체를 주입할 때 사용되는 토큰이라고 생각하면 된다.
이 토큰으로 어떤 객체를 주입할지 NestJS가 알게 된다고 보면된다.
2. private cacheManager Cache
여기서 cacheManager는 주입받은 객체를 사용할 수 있는 클래스 내부의 변수이다.
그리고 이 변수의 타입은 Cache 이다.
이 코드의 주 목적은 Cache 타입의 객체를 cacheManager라는 이름으로 클래스에 주입하는 것이다.
이를 위해 CACHE_MANAGER라는 토큰을 사용한다.
cache-manager에서 캐싱을 쉽게 관리할 수 있게 몇가지 메서드를 제공하는데 간단히 살펴보자.
1 .get(key)
- 주어진 키에 해당하는 값을 가져온다.
- "key" : 찾을 값의 키
const value = await this.cacheManager.get('key');
2. .set(key, value, [options])
- 캐시에 키-값 쌍을 저장한다.
- "key" : 저장할 키
- "value" : 저장할 값
- "options" : 옵션 객체. 보통 { ttl: 시간(초) } 이런 식으로 시간을 설정한다.
await this.cacheManager.set('key', 'value', { ttl: 60 });
이제 이 두가지 메서드로 캐싱처리를 할 수 있다.
async getData(key: string): Promise<any> {
// 캐시에서 데이터 확인
const cachedData = await this.cacheManager.get('temp');
if (cachedData) {
// 캐시에 데이터가 있으면 반환
return cachedData;
} else {
// 캐시에 데이터가 없으면 새로 데이터를 생성 (DB 조회 등)
const newData = await this.~~~~
// 새로 생성한 데이터를 캐시에 저장
await this.cacheManager.set('temp', newData, 360000 }); // ttl : 1시간 설정
return newData;
}
}
간단하게 IF - ELSE문 으로 구현이 가능하다.
저장한 캐시데이터가 있는지 찾고,
캐시 데이터가 있으면 그대로 바로 값을 return하고,
캐시 데이터가 없으면 ELSE 쪽 로직을 돌면서 return 하기 직전에
cacheManager.set() 으로 ttl시간과 함께 캐시데이터를 저장한다.
이렇게 캐싱 구현을 마치고 성능 테스트를 해봤는데 결과가 놀라웠다.
처음 로딩할 때는 캐싱데이터 .set() 을 하느라 400ms 가량 걸렸는데,
그 후 캐싱 적용이 된 같은 페이지를 로딩 했을때엔 10ms 미만으로 엄청난 성능을 보여줬다.
회사 직원분들도 빨라진 서비스에 만족을 하셨다 ㅎㅎ
장점, 단점, 주의사항
캐싱의 장점
빠른 데이터 접근: 캐싱된 데이터는 빠르게 접근할 수 있다.
서버 부하 감소: 데이터베이스나 다른 서비스에 덜 접근하므로 서버 부하가 감소한다.
캐싱의 단점
최신 데이터 지연: 캐싱된 데이터가 최신이 아닐 수 있다.
캐시 무효화 전략 필요: 언제 캐시를 지울지, 언제 새로운 데이터로 갱신할지에 대한 전략이 필요하다.
주의 사항
캐시 사이즈: 캐시 사이즈가 너무 크면 메모리 이슈가 생길 수 있다.
캐시 유효 시간 (TTL): 너무 짧으면 캐싱의 의미가 없고, 너무 길면 데이터가 최신이 아니게 될 수 있다.
이렇게 장단점과 주의사항을 보면 추후에 개선해줘야 할 게 바로 떠오를 것이다.
해당 캐싱 처리 쪽 API 의 내용을 혹시나 수정하게되면,
수정한 내용이 TTL 시간이 지날때 까지 반영이 되지 않을 것이다.
따라서 @Update API쪽에 캐싱 초기화를 넣으면 좋을것 같다.
캐싱 초기화는 두가지 방법이 있다.
- del : 특정 키로 저장된 캐시 삭제
this.cacheManager.del('키', (err) => {
if (err) {
// 에러 처리
}
});
- reset: 모든 캐시 삭제
this.cacheManager.reset((err) => {
if (err) {
// 에러 처리
}
});
이 두가지중에 Update API에 넣기 적합한 건 "del" 이므로
추후에 작업을 할 때, 해당 Update 로직에 캐싱초기화를 추가해야겠다.
참조 : https://docs.nestjs.com/techniques/caching
'Web > NestJS' 카테고리의 다른 글
[Nest.JS] 구글, 네이버, 카카오 소셜로그인 구현 - 2 (1) | 2023.11.04 |
---|---|
[Nest.JS] 구글, 네이버, 카카오 소셜로그인 구현 - 1 (1) | 2023.10.16 |
[Nest.JS] Slack에 다양한 통계데이터 알림 자동화 (0) | 2023.07.02 |
[Nest.JS] TypeOrm에서 페이지네이션 구현 (last_id, size) (0) | 2023.05.05 |
[Nest.JS] DTO vs Interface (0) | 2023.04.23 |