Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Step 6: Design a Rate Limiter - Using Sorted Set in Redis

2025年09月18日に「Dev.to」が公開したITニュース「Step 6: Design a Rate Limiter - Using Sorted Set in Redis」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

RedisのSorted Setを使うと、システムへのアクセス集中を防ぐレートリミッターを効率的に作れる。リクエストのタイムスタンプを管理し、特定の時間枠内のアクセス数を正確に制限可能。Javaでの実装例と分散環境での注意点も紹介する。

ITニュース解説

WebサービスやAPIは、システムを過剰なリクエストから守るために「レートリミッター」という仕組みを利用する。これは、一定時間内に受け付けるリクエストの数を制限し、サーバーへの負担軽減や不正利用の防止に役立つものだ。ここでは、RedisのSorted Setという強力なデータ構造を使って、このレートリミッターをどのように実現するかを、システムエンジニアを目指す初心者の皆さんにも分かりやすく解説する。

Redisは、高速なデータアクセスを特徴とするインメモリデータストアで、様々なデータ構造を提供している。その一つであるSorted Setは、要素(文字列)とそれに紐づくスコア(数値)のペアを格納する。このスコアによって要素が自動的にソートされるため、時間順にイベントを管理したり、ランキングを作成したりするのに非常に適している。レートリミッターでは、リクエストの発生時刻をスコアとして利用することで、時間ベースの制限を効率的に管理できる。

Sorted Setは、リクエストを正確に数える「Sliding Window Log(スライディングウィンドウログ)」という方式のレートリミッターを構築するのに最適だ。この方式は、常に現在時刻から過去X秒間という「動く時間枠」内のリクエスト数を数えるため、固定された時間枠の境界で一時的にリクエストが集中してしまう問題を回避できる。これにより、より高い精度でのレート制限が可能となり、時間枠のサイズやリクエスト上限も柔軟に調整できるという利点がある。

レートリミッターの具体的な仕組みは次のようになる。まず、各ユーザーやクライアント(IPアドレスなど)ごとに、Redis内に一つSorted Setを用意する。リクエストが来るたびに、その発生時刻(Unixタイムスタンプを秒やミリ秒で表したもの)をスコアとして、リクエストを特定するユニークな識別子を要素としてSorted Setに追加する。

新しいリクエストが来た時、システムはまず現在の時刻から設定された時間窓(例えば過去10秒間)の範囲内にあるリクエストの数をSorted Setから数える。これはRedisのZCOUNTコマンドを使って、指定されたスコア範囲内の要素数を取得することで行われる。もしその数が設定された上限値未満であれば、リクエストを許可し、現在のタイムスタンプをスコアとしてZADDコマンドで新しいエントリをSorted Setに追加する。この際、要素は重複しないようにタイムスタンプとユニークな識別子を組み合わせた文字列にするのが一般的だ。

もしカウントされたリクエスト数が上限を超えていれば、そのリクエストは拒否(スロットル)される。また、同時に、時間窓から外れた古いタイムスタンプのエントリは、RedisのZREMRANGEBYSCOREコマンドを使って効率的に一括削除し、メモリの消費を抑える。これらの追加、カウント、削除の操作はRedisがアトミックに実行するため、複数の処理が同時に行われてもデータの一貫性が保たれやすい。

大規模なシステムでは、複数のサーバーからRedisにアクセスする分散環境が一般的だ。このような環境では、メモリ使用量、高負荷時のパフォーマンス、そして複数のサーバー間でのデータ整合性が課題となることがある。 大量のリクエストを個別に記録するため、Sorted Setが大きくなり、多くのメモリを消費する可能性がある。これに対しては、短い時間窓を設定し、ZREMRANGEBYSCOREコマンドを使って古いデータを積極的に削除することで、Sorted Setのサイズを管理する。 また、毎秒大量のリクエストがある場合、Redisへのアクセスがボトルネックになることも考えられる。この対策として、Redis Clusterを導入し、ユーザーデータを複数のRedisサーバー(ノード)に分散(シャード化)させることで、負荷を均等に分け、処理能力を高める。 複数のデータセンター(AZs)にまたがるような環境では、一時的なデータ同期の遅延(レプリケーションラグ)が生じ、カウントが一時的にずれる可能性もある。しかし、Redisのアトミック操作を活用し、特定ユーザーのリクエストを常に同じRedisノードにルーティングする「Consistent Hashing」などの手法と組み合わせることで、ほとんどの場合で高い整合性を確保できる。

JavaでSorted Setを使ったレートリミッターを実装するには、「Jedis」のような人気のRedisクライアントライブラリを使う。このライブラリを使ってRedisに接続し、Sorted Setを操作する。コードでは、まずRedisへの接続を効率的に管理する「JedisPool」を設定する。そして、リクエストを処理するメソッドでは、現在の時間窓に基づいて、まずjedis.zremrangeByScoreメソッドで古いリクエストの記録を削除する。次にjedis.zcountメソッドで現在の時間窓内のリクエスト数を数え、設定された上限と比較する。上限を下回っていればjedis.zaddメソッドで現在のタイムスタンプを追加し、リクエストを許可する。そうでなければ拒否するという流れになる。

このレートリミッターを実際に構築するには、いくつかのステップを踏む。まず、Redisサーバーを用意する。本番レベルの大規模環境では、Redis Clusterをデプロイすることが推奨される。次に、JavaプロジェクトにJedisクライアントライブラリを追加する。その後、レート制限のロジックを設計し、ユーザーごとのキーの命名規則、タイムスタンプをスコアとして記録する方法、ユニークなメンバーIDの生成、時間窓のサイズとリクエスト上限を定義する。 設計が固まったら、古いデータを削除し、リクエスト数をカウントし、新規リクエストを追加するコア機能を実装する。分散環境での利用を想定し、JedisPoolによる接続管理や、Consistent Hashingによる一貫性確保の仕組みも考慮する必要がある。実装後は、様々なリクエストパターンでテストを行い、機能とパフォーマンスを検証する。本番環境にデプロイした後は、継続的に監視を行い、必要に応じてRedisの構成やレート制限の設定を最適化していくことが重要だ。例えば、メモリ使用量が多い場合は時間窓を短くしたり、トラフィックが増加した場合はRedisノードを追加したりといった対応が考えられる。

まとめると、RedisのSorted Setを使うことで、Sliding Window Log方式のレートリミッターを非常に精度が高く、柔軟かつ効率的に実装できる。これはRedis Clusterと組み合わせることで、大規模な分散システムにも対応可能だ。メモリ管理や高負荷時のパフォーマンスといった課題も、適切な設計と運用戦略で克服できるため、安定したWebサービスやAPI提供に貢献する重要な技術と言える。

関連コンテンツ

関連IT用語