NestJS/Typescript : 캐시 메모리와 레디스(Redis)

NestJS/Typescript : 캐시 메모리와 레디스(Redis)

캐시 메모리(Cache Memory)

  • 캐시 메모리(이하 캐시) : 데이터를 임시로 저장하는 물리적 혹은 논리적 장소
    • 어플리케이션(application)의 경우 사용자들이 서버(server)에서 받는 요청에 따라 데이터베이스(database)에 접근하기 때문에, 사용자가 많아질 수록 발생하는 부하를 줄이기 위해 사용
    • 메모리 상 유지되는 것으로, 서버가 종료될 시 캐시 메모리에 저장되어 있는 데이터도 휘발되는 특징이 있음
  • 캐시 적중(cache hit) : 액세스하려는 데이터가 캐시에 적재되어 있는 상태
  • 캐시 미스(cache miss) : 액세스하려는 데이터가 캐시에 없어 기억장치(Database)에서 가져와야하는 상태
  • 캐시 적중률(cache hit rate) : 원하는 데이터가 캐시에 있을 확률
    • 캐시 적중 횟수 / 전체 기억장치 액세스 횟수
    • 캐시 적중률은 데이터의 지역성에 크게 의존함

지역성(locality)

  • 지역성 : 기억장치 안의 특정 부분을 빈번하게 또는 집중적으로 액세스하는 현상
    • 시간적 지역성(temporal locality) : 최근 액세스된 데이터가 가까운 미래에 다시 액세스 될 가능성이 높아지는 특성
    • 공간적 지역성(spatial locality) : 기억장치 내 서로 인접하여 저장되어 있는 데이터들이 연속적으로 액세스될 가능성이 높아지는 특성

캐싱 전략

읽기 전략

  • Look Aside(Lazy Loading) : 데이터를 찾을 때 캐시에 데이터가 존재하는 지 확인① 하고 없을 경우 데이터베이스로 확인해② 가져온 데이터를 캐시에 저장③ 하는 전략
    • 장점 : 캐시에 장애가 어플리케이션에 치명적인 영향을 주지 않음
    • 단점 : 캐시에 없는 데이터를 쿼리할 때 오랜 시간이 걸릴 수 있으며, 최신 데이터를 가지고 있다는 것을 보장할 수 없음

  • Read Through : 캐시에서만 데이터를 읽어오는 전략① 으로, 없는 경우 캐시가 데이터베이스로부터 데이터를 가져오는② 전략
    • 장점 : 캐시-데이터베이스 간 데이터 정합성을 보장
    • 단점 : 데이터 조회에 전체적인 속도가 느리며, 캐시가 죽어있을 경우, 어플리케이션에 문제가 발생
쓰기 전략

  • Write Around : 모든 데이터를 데이터베이스에 저장
    • 장점 : 캐시 쓰기 성능이 빠름
    • 단점 : 캐시-데이터베이스 간 데이터 정합성 유지 어려움

  • Write Through : 데이터를 캐시에 먼저 저장하고① 바로 데이터베이스에 저장하는② 전략
    • 장점 : 자주 사용하지 않는 리소스에 대한 쓰기 횟수 비용을 줄일 수 있음
    • 단점 : 두 번의 쓰기로 인해 서비스에서 성능 이슈가 발생할 수 있음

  • Write Back : 데이터를 캐시에 모아두었다가① 일정 주기마다 데이터베이스에 저장하는② 전략
    • 장점 : 자주 사용하지 않는 리소스에 대한 쓰기 횟수 비용을 줄일 수 있음
    • 단점 : 캐시에서 오류 등으로 인한 데이터가 유실될 수 있음
캐싱 전략의 조합
  • Look Aside + Write Around : 일반적으로 자주 사용되는 조합
  • Read Through + Write Around : 항상 데이터베이스에 쓰고 캐시에서 읽을 때 데이터베이스에서 먼저 읽어오기 때문에 데이터 정합성 문제를 대비할 수 있음
  • Read Through + Write Through : 데이터를 쓸 때 항상 캐시에 먼저 쓰고 데이터베이스로 보내기 때문에 캐시에서 읽을 때 데이터의 최신 상태와 캐시-데이터베이스 간 데이터 정합성을 보장

REDIS(REmote DIctionary Server)

  • Redis : 오픈 소스, 인 메모리 키-값 데이터 관리 서버, 데이터베이스, 캐시, 메시지 브로커(message broker) 등으로 사용합니다.
    • 키-값(key-value)로 저장하는 비관계형 구조 데이터베이스(NoSQL)
    • 쿼리 연산은 없지만 데이터 고속 읽기와 쓰기에 최적화 되어있음
    • 메모리(dram)에서 데이터를 처리하기 때문에 작업 속도가 빠름
      • NoSQL(not only SQL) : 전통적인 관계형 데이터베이스보다 덜 제한적인 일관성 모델을 이용하는 데이터의 저장 및 검색을 위한 매커니즘을 제공하는 데이터베이스

NestJS에서 Cache Module 사용

  • cache-managerio-redis store 관련 모듈 설치
npm install @nestjs/cache-manager cache-manager
npm install cache-manager-ioredis
npm install -D @types/cache-manager @types/cache-manager-ioredis
  • cache-manager 모듈 import
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-ioredis';

@Module({
  imports: [
    CacheModule.register({
      isGlobal: true,
      store: redisStore,
      host: 'localhost',
      port: 6379,
    }),
    ...
  ],
  providers: [],
})
export class AppModule {}
  • 캐시 데이터 쓰기(this.cacheManager.set('키 이름', 저장 할 데이터))
@Injectable()
export class ExternalApiLib {
  constructor(
    private readonly configService: ConfigService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {}
    ...
    
    async updateCities() {
            const filePath = path.join(__dirname, '../../../csv', 'sgg_lat_lon.csv');

            const data = [];
            fs.createReadStream(filePath)
                .on('error', (error) => {
                    Logger.error(error);
                })
                .pipe(csv())
                .on('data', (row) => {
                    data.push({
                        city: row['do-si'],
                        district: row['sgg'],
                        latitude: row['lat'],
                        longitude: row['lon'],
                    });
                })
                .on('end', () => {
                    this.cacheManager.set(`city`, data); // cacheManager 안에 `city`키 이름으로 데이터 저장
                });
        }
    }
  • 캐시 데이터 읽기(this.cacheManager.get('키 이름'))
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

@Injectable()
export class CityService {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache
  ) {}

  /** 시군구 조회
   * @return 시군구 목록 */
  async getCities() {
      // cacheManager 안에 `city`키 이름의 데이터 불러오기
    const cachedData = await this.cacheManager.get(`city`);
        ...

    return cachedData;
  }
}

관련 Repository

참고 자료

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.