들어가며
사이드 프로젝트에서 푸시 알림을 활용한 서비스를 개발하고 있습니다. 그 과정에서 생각하고 배웠던 점들을 하나씩 작성하고자 합니다. Queue를 활용한 푸시 알림 서버 구축을 하면서, Redis 자료구조를 활용해서, 캐시 활용 서비스를 구축해야 했습니다. 이를 위해 NestJS에서는 유용한 cache module을 제공하고 있었습니다. 하지만 이 모듈에서는 Redis를 활용하기 위한 기능상의 제약이 있었습니다. 그래서 Redis를 더 잘 활용하기 위해 cache module이 아닌, redis 클라이언트를 활용하는 방식에 대해 알아보겠습니다.
Cache Module 설정하기
프로젝트를 진행하면서, Redis로 캐시를 잘 활용할 수 있도록, NestJS 공식 문서에서는 cache-manager를 활용하는 방법을 추천하고 있습니다. CacheModule을 활용하면 redis와 같은 외부 cache-storage 뿐 아니라 빌트인 된 in-memory 스토리지 기능도 제공합니다.
간단하게 CacheModule을 설정하고, 동작하는 방법에 대해 알아보겠습니다. 먼저 in-memory 스토리지 사용을 위해 아래 패키지들을 설치해 줍니다.
$ npm install cache-manager
$ npm install -D @types/cache-manager
그 후 AppModule에 CacheModule을 Import 합니다. CacheModule의 register()를 사용하여 손쉽게 in-memory 설정이 가능합니다.
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [CacheModule.register()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
그 후 in-memory가 정상적으로 설정되었는지 확인하기 위해 간단한 Controller를 작성합니다. AppController는 메모리에 데이터가 존재한다면 저장된 값을 출력하고, 데이터가 없다면 새롭게 데이터를 저장해줍니다.
import { CACHE_MANAGER, Controller, Get, Inject } from '@nestjs/common';
import { Cache } from 'cache-manager'
@Controller()
export class AppController {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache
) {}
@Get("/cache")
async getCache(): Promise<string> {
const savedTime = await this.cacheManager.get<number>('time')
if( savedTime ){
return "saved time : " + savedTime
}
const now = new Date().getTime()
await this.cacheManager.set<number>('time',now);
return "save new time : " + now
}
}
이때, 주의하셔야 할 점은 cacheManager의 type인 Cache를 ‘cache-manager’에서 import 하셔야 합니다.
구성을 완료하고 localhost:3000/cache로 테스트를 진행합니다. 처음 요청 시에는 캐시에 데이터가 없기 때문에 새로운 데이터가 추가됩니다. 이후, 재요청 시 저장되어있는 데이터가 표시됩니다. 현재 CacheModule의 설정은 in-memory 상태이기 때문에 서비스가 종료되면 데이터는 삭제됩니다.
그럼 여기서 in-memory 상태가 아닌, redis 연결을 통해 캐시를 활용해 보겠습니다. redis연결을 위해 redisStore를 설치합니다. CacheModule에서는 다양한 redisStore들을 지원합니다. 자세한 내용은 여기를 확인해주세요. 우리는 io-redis를 사용해봅니다.
$ npm install cache-manager-ioredis --save
$ npm install -D @types/cache-manager-ioredis
Cachmodule에 io-redis store를 등록하고, local 환경설정을 진행합니다.
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as redisStore from 'cache-manager-ioredis';
@Module({
imports: [CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
})],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
이전에 만들어 두었던 AppController를 활용하여, 테스트를 진행해봅시다. 우리는 CachModule에서 제공하는 cacheManager를 Controller에서 injecting 하여 사용하였기 때문에 Controller 레벨의 수정은 필요 없습니다.
이번 테스트는 in-memory가 아니라 외부 레디스 연동 여부를 확인하기 위해 redis client로 접속하여 데이터를 확인해봅니다.
저는 위와 같이 단순하게 캐시를 이용할 목적으로 공식 문서에 따라 캐시를 구축했습니다. 하지만 구축하다 보니, Redis의 다양한 자료구조를 활용하고 싶은데, cache-manager를 활용하면 기능상에 제약이 있었습니다.
특히 cache manage를 inject 해서 활용하는 경우, 단순하게 키를 가져오고, 추가하고, 삭제하는 부분만 가능했습니다. 하지만 프로젝트의 특성상 Redis의 List와 같은 자료구조를 활용해야 하는데, cache-manager를 활용하면 다양한 자료구조를 활용할 수 없었습니다. 결론적으로, cache-manager를 사용하는 것이 아닌, node.js에서 redis를 더 잘 활용할 수 있도록 Redis 클라이언트를 선택해야 했습니다.
그럼 Redis 클라이언트는 무엇이고, Redis 클라이언트는 어떤 역할을 하는지 알아보겠습니다.
Redis Client 알아보기
Node.js에서 Redis를 활용하기 위해서는 Redis Client를 선택해야 합니다. 이때 Redis Client로 활용할 수 있는 도구가 상당히 많았는데, NestJS를 활용하면서, nestjs-redis라는 라이브러리를 가장 많이 활용한다는 것을 알 수 있었습니다.
하지만 nestjs-redis를 활용하면, 라이브러리 자체에서 순환 참조 문제가 발생한다는 것을 알았습니다. 그럼, 라이브러리를 사용하는 것이 아니라, Redis Client를 직접적으로 활용하는 방법을 선택해야겠다고 생각했습니다. 여기서 Node.js에서 Redis Client로 활용할 수 있는 것은 무엇이 있는지 먼저 알아보고 싶었습니다.
위의 결과를 보면, Node.js에서 가장 많이 사용하는 Redis Clinet는 node-redis, ioredis였습니다. 그럼 이 둘을 비교해보고, 프로젝트에 적합한 클라이언트를 사용해야겠다고 생각했습니다.
위의 자료를 읽어보면 node-redis의 경우 CPU 리소스를 많이 사용한다고 나옵니다.
서버 CPU 사용량의 14%(20대 프로파일 중 2.7초)는 Redis에 명령을 보내는 과정에서 소켓을 활용하는 데 사용됐습니다. 하지만 ioredis의 경우 버퍼로 보낼 명령을 준비하고 소켓 쓰기를 한 번만 수행하고 있었습니다. 이 부분을 봤을 때, node-redis보다 ioredis를 활용하는 것이 더 성능이 좋을 것이라고 생각했습니다.
그럼 ioredis를 활용해서 Redis를 어떻게 프로젝트에 활용했는지에 대해 계속해서 글을 이어가 보도록 하겠습니다.
마치며
앞으로도 팀의 발전을 돕는 개발자가 되기 위해 노력하려 합니다. 팀에 필요한 부분이 무엇일지 고민하면서, 팀에 도움이 된다면, 열심히 공부해서 실무에 적용할 수 있는 개발자가 되기 위해 노력하고 싶습니다. 팀의 성장에 기여할 수 있는 개발자가 되겠습니다.
참고 및 출처
'Project > 서버 개발' 카테고리의 다른 글
[Project] 프로젝트 삽질기28 (feat 푸시 메시지 구성) (0) | 2022.05.16 |
---|---|
[Project] 프로젝트 삽질기27 (feat Redis 활용) (1) | 2022.05.11 |
[Project] 프로젝트 삽질기25 (feat Transaction) (0) | 2022.05.05 |
[Project] 프로젝트 삽질기24 (feat Sentry Slack 연동) (0) | 2022.04.29 |
[Project] 프로젝트 삽질기23 (feat Enum) (0) | 2022.04.22 |