ユーザーアイコン

mizuko

約1か月前

0
0

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

Nest.js
速度改善

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

import { AsyncLocalStorage } from 'async_hooks'; export class RequestCacheStore { private static asyncLocalStorage = new AsyncLocalStorage< Map<string, unknown> >(); static getStore(): Map<string, unknown> | null { const store = this.asyncLocalStorage.getStore(); // RequestCacheInterceptorを使っておらず、cacheを使用とした場合はnullを返す if (!store) { return null; } return store; } 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'; @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; } const key = this.getCacheKey(userSession); return ( (store.get(key) as UserEntity) || null ); } set( userSession: UserSession, data: UserEntity, ): void { const store = RequestCacheStore.getStore(); if (!store) { return; } const key = this.getCacheKey(userSession); store.set(key, data); } 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; } const user = await this.fetchUser(userSession); this.userCacheService.set(userSession, user); return user; }