「リトライと冪等性」と「サーキットブレーカー」に引き続きマイクロサービスを運用するにあたって有用なレートリミットについてです。
レートリミット
レートリミット(rate limit)とは、リクエストの量が一定以上の閾値を超えた場合に、リクエスト数を制限するテクニックです。
例えば、あるサービスがハンドルすることのできるTPS(transaction per second)に上限値がある場合、そのサービスを呼び出すサービスはそのTPSを超えないようレートリミットを設置することが有用です。
上限値を超えてしまうとそのサービスはCPUやメモリ使用率が限界まで上がってしまい、本来捌くことができるリクエストすらもハンドルする余裕がなくなってしまう可能性があります。
レートリミットを使用することで、事前に十分ハンドルできるとわかっている量のリクエストをきちんと処理し、それ以上のリクエストはdownstreamのサービスに送らずにエラーを返すといったことが可能になります。
また、外部サービスに接続している場合、万が一、バグや攻撃によって大量のリクエストを生成してしまった場合でも、外部への影響を抑えることができます。
さらに、レートリミットにより返されるエラーは、「リトライと冪等性」で説明したようにリトライ可能なエラーとしてハンドルすることで、実際にはユーザーにエラーを返すことなく処理できる場合もあります。
レートリミットを設置する場所
レートリミットは一般に、マイクロサービスやAPIゲートウェイに設置することができます。
APIゲートウェイに設置する場合、その実装方法にもよりますが、エンドポイントレベルや全体的な負荷に対する制限をかけることが一般的かと思います。
対してマイクロサービス内に設置する場合、アプリケーションのロジックによってより細かい粒度での制限が可能になります。例えば、特定の条件でリクエストするサービスに対して、レートリミットをかけるといったことができます。
一方で、リクエストをサービスまで一旦全て伝達する必要があるというデメリットもあります。
レートリミットの上限値
レートリミットを設置するにあたって、各サービスがどのくらいのTPSを問題なくハンドルすることができるのかを事前に知っておくことは大切です。
そのためにはパフォーマンステストを実施することが最も効果的かと思います。実際に本番環境と同じ設定の高負荷環境を用意し、本番を模したワークロードを生成することでどのくらいまで可用性を担保したままリクエストをハンドルできるかをテストします。
高負荷環境は本番環境と同じリソースを必要とするため、コストがかかる場合がありますが、実際に本番環境でパフォーマンスに問題が起きサービスがダウンするリスクを考えると有用な場合が多いかと思います。
実装例
JavaやKotlinなどのJVM言語においては、Resilience4というOSSにレートリミッターが含まれています。前回のサーキットブレーカーと同じOSSです。

application.yaml
に設定を追加し、@RateLimiter
アノテーションを付与することで簡単にレートリミットを実現することができます。
@RateLimiter(name="foo")
fun order(items: List[Item]) {
// ... do something
}
まとめ
レートリミットは一定以上のリクエストが来た際にリクエスト数を制限するテクニックです。サービスが過負荷になることを防ぎ、本来捌ける量のリクエストのハンドルに集中させることができます。
コメント