Caching

Caching

캐싱은 μ•±μ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 데 λ„μ›€μ΄λ˜λŠ” ν›Œλ₯­ν•˜κ³  κ°„λ‹¨ν•œ κΈ°μˆ μž…λ‹ˆλ‹€. κ³ μ„±λŠ₯ 데이터 μ•‘μ„ΈμŠ€λ₯Ό μ œκ³΅ν•˜λŠ” μž„μ‹œ 데이터 μ €μž₯μ†Œ μ—­ν• μ„ν•©λ‹ˆλ‹€.

Installation

λ¨Όμ € ν•„μš”ν•œ νŒ¨ν‚€μ§€λ₯Ό μ„€μΉ˜ν•˜μ‹­μ‹œμ˜€.

$ npm install --save cache-manager

In-memory cache

NestλŠ” λ‹€μ–‘ν•œ μΊμ‹œ μŠ€ν† λ¦¬μ§€ 제곡자λ₯Ό μœ„ν•œ 톡합 APIλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. λ‚΄μž₯된 것은 인 λ©”λͺ¨λ¦¬ 데이터 μ €μž₯μ†Œμž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ Redis와 같은 보닀 포괄적인 μ†”λ£¨μ…˜μœΌλ‘œ μ‰½κ²Œ μ „ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 캐싱을 ν™œμ„±ν™”ν•˜λ €λ©΄ λ¨Όμ € CacheModule을 κ°€μ Έ μ™€μ„œ register() λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•˜μ‹­μ‹œμ˜€.

import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';

@Module({
  imports: [CacheModule.register()],
  controllers: [AppController],
})
export class ApplicationModule {}

그런 λ‹€μŒ 데이터λ₯Ό μΊμ‹œν•  μœ„μΉ˜μ— CacheInterceptorλ₯Ό μ—°κ²°ν•˜μ‹­μ‹œμ˜€.

@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {
  @Get()
  findAll(): string[] {
    return [];
  }
}

warning κ²½κ³  GET μ—”λ“œ 포인트만 μΊμ‹œλ©λ‹ˆλ‹€. λ˜ν•œ μ›μ‹œ 응닡 였브젝트 (@Res())λ₯Ό μ£Όμž…ν•˜λŠ” HTTP μ„œλ²„ λΌμš°νŠΈλŠ” μΊμ‹œ 인터셉터λ₯Ό μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μžμ„Έν•œ λ‚΄μš©μ€ 응닡 맀핑을 μ°Έμ‘°ν•˜μ‹­μ‹œμ˜€.

Global cache

ν•„μš”ν•œ μƒμš©κ΅¬μ˜ 양을 쀄이렀면 CacheInterceptorλ₯Ό μ „μ—­μœΌλ‘œ λͺ¨λ“  μ—”λ“œ ν¬μΈνŠΈμ— 바인딩 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import { CacheModule, Module, CacheInterceptor } from '@nestjs/common';
import { AppController } from './app.controller';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  imports: [CacheModule.register()],
  controllers: [AppController],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: CacheInterceptor,
    },
  ],
})
export class ApplicationModule {}

WebSockets & Microservices

MicroService의 νŒ¨ν„΄λΏλ§Œ μ•„λ‹ˆλΌ WebSocket κ°€μž…μžμ—κ²Œ CacheInterceptorλ₯Ό μ μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€ (μ‚¬μš©μ€‘μΈ 전솑 방법에 관계없이).

@@filename()
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
  return [];
}
@@switch
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client, data) {
  return [];
}

info 힌트 @CacheKey()λ°μ½”λ ˆμ΄ν„°λŠ” @nestjs/common νŒ¨ν‚€μ§€μ—μ„œ κ°€μ Έμ˜΅λ‹ˆλ‹€.

κ·ΈλŸ¬λ‚˜ μΊμ‹œλœ 데이터λ₯Ό μ €μž₯ν•˜κ³  κ²€μƒ‰ν•˜λŠ” 데 μ‚¬μš©λ˜λŠ” ν‚€λ₯Ό μ§€μ •ν•˜λ €λ©΄ μΆ”κ°€@CacheKey()λ°μ½”λ ˆμ΄ν„°κ°€ ν•„μš”ν•©λ‹ˆλ‹€. λ˜ν•œ λͺ¨λ“  것을 μΊμ‹œν•˜μ§€ μ•Šμ•„μ•Όν•©λ‹ˆλ‹€. λ‹¨μˆœνžˆ 데이터λ₯Ό μΏΌλ¦¬ν•˜μ§€ μ•Šκ³  일뢀 λΉ„μ¦ˆλ‹ˆμŠ€ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” μž‘μ—…μ€ μΊμ‹œλ˜μ§€ μ•Šμ•„μ•Ό ν•©λ‹ˆλ‹€.

Customize caching

μΊμ‹œλœ λͺ¨λ“  λ°μ΄ν„°μ—λŠ” 자체 TTL (만료 μ‹œκ°„)이 μžˆμŠ΅λ‹ˆλ‹€. 기본값을 μ‚¬μš©μž μ •μ˜ν•˜λ €λ©΄ options 객체λ₯Ό register()λ©”μ„œλ“œμ— μ „λ‹¬ν•˜μ‹­μ‹œμ˜€.

CacheModule.register({
  ttl: 5, // seconds
  max: 10, // maximum number of items in cache
});

Different stores

이 μ„œλΉ„μŠ€λŠ” ν›„λ“œμ—μ„œ cache-managerλ₯Ό ν™œμš©ν•©λ‹ˆλ‹€. cache-manager νŒ¨ν‚€μ§€λŠ” Redis와 같은 λ‹€μ–‘ν•œ μœ μš©ν•œ μ €μž₯μ†Œλ₯Ό μ§€μ›ν•©λ‹ˆλ‹€. μ§€μ›λ˜λŠ” 전체 μ €μž₯μ†Œ λͺ©λ‘μ€ μ—¬κΈ°μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. Redis μŠ€ν† μ–΄λ₯Ό μ„€μ •ν•˜λ €λ©΄ ν•΄λ‹Ή μ˜΅μ…˜κ³Ό ν•¨κ»˜ νŒ¨ν‚€μ§€λ₯Ό register()λ©”μ†Œλ“œμ— μ „λ‹¬ν•˜λ©΄ λ©λ‹ˆλ‹€.

import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';

@Module({
  imports: [
    CacheModule.register({
      store: redisStore,
      host: 'localhost',
      port: 6379,
    }),
  ],
  controllers: [AppController],
})
export class ApplicationModule {}

Adjust tracking

기본적으둜 NestλŠ” μš”μ²­ URL (HTTP μ•±) λ˜λŠ” μΊμ‹œ ν‚€ (μ›Ή μ†ŒμΌ“ 및 마이크둜 μ„œλΉ„μŠ€ μ•±, @CacheKey()λ°μ½”λ ˆμ΄ν„°λ₯Ό 톡해 μ„€μ •)λ₯Ό μ‚¬μš©ν•˜μ—¬ μΊμ‹œ λ ˆμ½”λ“œλ₯Ό μ—”λ“œ ν¬μΈνŠΈμ™€ μ—°κ²°ν•©λ‹ˆλ‹€. κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³  λ•Œλ‘œλŠ” HTTP 헀더 (예: profile μ—”λ“œ 포인트λ₯Ό μ˜¬λ°”λ₯΄κ²Œ μ‹λ³„ν•˜κΈ°μœ„ν•œ Authorization)λ₯Ό μ‚¬μš©ν•˜λŠ” λ“± λ‹€μ–‘ν•œ μš”μ†Œλ₯Ό 기반으둜 좔적을 μ„€μ •ν•˜κ³ μž ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이λ₯Ό μœ„ν•΄ CacheInterceptor의 μ„œλΈŒ 클래슀λ₯Ό μž‘μ„±ν•˜κ³  trackBy()λ©”μ†Œλ“œλ₯Ό λŒ€μ²΄ν•˜μ‹­μ‹œμ˜€.

@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
  trackBy(context: ExecutionContext): string | undefined {
    return 'key';
  }
}

Async configuration

μ»΄νŒŒμΌμ‹œ μ •μ μœΌλ‘œ μ „λ‹¬ν•˜λŠ” λŒ€μ‹  λͺ¨λ“ˆ μ˜΅μ…˜μ„ λΉ„λ™κΈ°μ μœΌλ‘œ 전달할 수 μžˆμŠ΅λ‹ˆλ‹€. 이 경우 비동기 ꡬ성을 μ²˜λ¦¬ν•˜λŠ” λͺ‡ κ°€μ§€ 방법을 μ œκ³΅ν•˜λŠ” registerAsync() λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜μ‹­μ‹œμ˜€.

ν•œ κ°€μ§€ 방법은 νŒ©ν† λ¦¬ κΈ°λŠ₯을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

CacheModule.registerAsync({
  useFactory: () => ({
    ttl: 5,
  }),
});

Our factory behaves like all other asynchronous module factories (it can be async and is able to inject dependencies through inject).

CacheModule.registerAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    ttl: configService.getString('CACHE_TTL'),
  }),
  inject: [ConfigService],
});

Alternatively, you can use the useClass method:

CacheModule.registerAsync({
  useClass: CacheConfigService,
});

μœ„μ˜ ꡬ성은 CacheModuleλ‚΄μ—μ„œ CacheConfigServiceλ₯Ό μΈμŠ€ν„΄μŠ€ν™”ν•˜μ—¬ μ˜΅μ…˜ 객체λ₯Ό μ–»λŠ” 데 μ‚¬μš©ν•©λ‹ˆλ‹€. CacheConfigServiceλŠ” μ„€μ • μ˜΅μ…˜μ„ μ œκ³΅ν•˜κΈ° μœ„ν•΄ CacheOptionsFactory μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€:

@Injectable()
class CacheConfigService implements CacheOptionsFactory {
  createCacheOptions(): CacheModuleOptions {
    return {
      ttl: 5,
    };
  }
}

λ‹€λ₯Έ λͺ¨λ“ˆμ—μ„œ κ°€μ Έμ˜¨ κΈ°μ‘΄ ꡬ성 κ³΅κΈ‰μžλ₯Ό μ‚¬μš©ν•˜λ €λ©΄ useExisting ꡬ문을 μ‚¬μš©ν•˜μ‹­μ‹œμ˜€.

CacheModule.registerAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
});

이것은 useClass와 λ™μΌν•˜κ²Œ μž‘λ™ν•˜λ©° ν•œ κ°€μ§€ μ€‘μš”ν•œ 차이점이 μžˆμŠ΅λ‹ˆλ‹€. CacheModule은 κ°€μ Έμ˜¨ λͺ¨λ“ˆμ„ μ°Ύμ•„μ„œ 자체적으둜 μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” λŒ€μ‹  이미 μž‘μ„±λœ ConfigServiceλ₯Ό μž¬μ‚¬μš©ν•©λ‹ˆλ‹€.

Last updated

Was this helpful?