はじめに
NestJSでは、NestJS公式のThrottleパッケージを使用すれば設定以上の頻度で来るアクセスをブロックできますが、その場合溢れたアクセスはエラーでクライアント側に返ってしまいます。
あくまでも並行実行数を絞りつつ、すべてのアクセスを正常に捌きたいというようなケースについては公式の方法は用意されていませんが、独自にインターセプターを作ることで対応することが可能です。
実装
カスタムインターセプターの作成
まず、並行処理を制限するカスタムインターセプターを作成します。
このインターセプターでは p-limit
ライブラリを使用して、並行処理の数を制限しています。
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import pLimit from 'p-limit';
@Injectable()
export class ConcurrencyInterceptor implements NestInterceptor {
private limit;
constructor(concurrency: number) {
this.limit = pLimit(concurrency);
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return new Observable((observer) => {
this.limit(() => next.handle().toPromise())
.then((result) => {
observer.next(result);
observer.complete();
})
.catch((err) => {
observer.error(err);
});
});
}
}
コントローラメソッドにインターセプターを適用
次に、作成したConcurrencyInterceptorを、 @UseInterceptors
デコレーターを使ってコントローラの特定のメソッドに適用します。
Copy code
import { Controller, Post, UseInterceptors } from '@nestjs/common';
// fromは自分がインターセプターをおいた場所にする
import { ConcurrencyInterceptor } from './concurrency.interceptor';
@Controller('your-endpoint')
export class AppController {
@Post()
@UseInterceptors(new ConcurrencyInterceptor(3)) // ★例えば並行処理を3に制限
async yourEndpointHandler() {
// ハンドラーのロジック
}
}
終わりに
このように、インターセプターを使うことでエンドポイントごとに柔軟にconcurrencyを設定できるようになります。
Throttlingと違い、同時実行制限をしつつリクエストの失敗を防げるため便利です。
※別の言い方をすると、ConcurrencyInterceptorはエンドポイントに対するキューイングのようなイメージに近い、ということです。
ただ、言うまでもないですが消化スピードより速いペースで永遠にリクエストが入り続けるとこの方法は破綻するので、そのようなケースではThrottlingによる制限の方を検討したほうがいいと思います。