ユーザーアイコン

mizuko

4か月前

0
0

Nest.jsにてリクエストで共通のキャッシュを持つ

Nest.js
速度改善

速度改善のためクエリの調査を行なっていたところ、割と重いデータ取得を1リクエスト内で重複して取得していた。 redisなどでcacheするのも良いが、導入にはそれなりのコストが発生するため、Nest.jsのInterceptorとasyncLocalStorageを使ってリクエスト毎にキャッシュを持つようにした。 要件によっては逆にcacheが悪さする可能性があるため、運用は要注意。  

import { AsyncLocalStorage } from 'async_hooks'; &nbsp; export class RequestCacheStore { private static asyncLocalStorage = new AsyncLocalStorage< Map<string, unknown> >(); &nbsp; static getStore(): Map<string, unknown> | null { const store = this.asyncLocalStorage.getStore(); // RequestCacheInterceptorを使っておらず、cacheを使用とした場合はnullを返す if (!store) { return null; } return store; } &nbsp; static run<T>(callback: () => Promise<T>): Promise<T> { return this.asyncLocalStorage.run(new Map(), callback); } }
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; import { RequestCacheStore } from '../cache/RequestCacheStore'; &nbsp; @Injectable() export class RequestCacheInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return new Observable((subscriber) => { RequestCacheStore.run(async () => { try { const result = await firstValueFrom(next.handle()); subscriber.next(result); subscriber.complete(); } catch (error) { subscriber.error(error); } }); }); } }

  上記をcontrollerで指定  

@Controller('users') @UseInterceptors(RequestCacheInterceptor) export class UserController { ~ 略 ~

  cacheServiceを定義  

@Injectable() export class UserCacheService { get( userSession: UserSession, ): UserEntity | null { const store = RequestCacheStore.getStore(); if (!store) { return null; } &nbsp; const key = this.getCacheKey(userSession); return ( (store.get(key) as UserEntity) || null ); } &nbsp; set( userSession: UserSession, data: UserEntity, ): void { const store = RequestCacheStore.getStore(); if (!store) { return; } &nbsp; const key = this.getCacheKey(userSession); store.set(key, data); } &nbsp; private getCacheKey( userSession: UserSession, ): string { return `user:${userSession.organizationId}:${userSession.userId}`; } }

  service等でcacheを見るように追加する  

async findById( userSession: UserSession, ): Promise<UserEntity> { const cache = this.userCacheService.get(userSession); if (cache) { return cache as ObjectConfigEntity; } &nbsp; const user = await this.fetchUser(userSession); &nbsp; this.userCacheService.set(userSession, user); &nbsp; return user; }