アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

リクエスト制限導入を導入しました (2年前)

プロダクト開発チームの将棋愛好家です。将棋愛好家のアイリッジへの入社を心よりお待ちしております。

2年ほど前に担当した、FANSHIPの管理者向けAPI (以下MAPIと書きます) のリクエスト制限について書きます。
当時、ちょうど入社1年経過した頃あたりで、初めて自分が中心となって実施した思い出深いプロジェクトです。

経緯

制限導入以前のリクエスト制限は、MAPIへのアクセスは1並列である旨を公開ドキュメントに記載し、運用の中でお願いするのみで、システム的な制限はありませんでした。
その運用でも、大量リクエストによる大きなパフォーマンス遅延などは発生していませんでした。
年々お客様の利用が増え活発に活用されることで、MAPIへのリクエストが増えておりました。
その中で、大量リクエストによって、FANSHIPの中心的なDBが高負荷になって、ユーザーにストレスを与えるようなパフォーマンス遅延が発生しました。
このような背景をきっかけに、FANSHIPの更なる安定稼働を実現するために、MAPIにシステム的な制限をすることになりました。

制限検討

制限が満たすべき条件を検討した結果、以下のような制限をすることにしました。

  • 一定時間内にリクエスト数が上限を超えたら、一定時間リクエストを遮断する
  • 契約毎に集計する
  • 契約毎に上限設定できる
  • DBアクセスをしない

(契約という用語を使っていますが、企業と読み替えても大丈夫です)

1つ目は、リクエスト制限でよくやられているので、説明する必要はないと思います。

契約毎に集計する

利用者側で上限緩和が出来ないようにするための条件です。
集計単位は、IP毎・ユーザー毎・契約毎の3つが考えられると思います。
IP・ユーザー毎だと、IPを複数用意したりユーザーを追加したりすることで、
実質的にリクエスト可能な数が増えてしまうという問題が有ります。
契約毎の集計だと、利用者側で上限を緩和できないので、望ましいと判断しました。

契約毎に上限設定できる

上限を増やしたいという要望があると想定されることと、
予定の上限をすでに超えている契約が存在していたためです。

すでに超えている契約を基準として、共通の上限を設定してしまうと、
上限が高くなりすぎてしまいます。
そのため、すでに超えている契約は個別の上限を設定する必要が有りました。

DBアクセスをしない

機能自体が障害の原因にならないようにするためです。
制限の処理にDBアクセスがあると、大量リクエストされた場合に、DBが高負荷になってしまう可能性が有ります。
障害を防ぐ機能のせいで障害になったら笑えません。

設計・実装

リクエスト数の集計は、Redisを使ってやることにしました。
FANSHIPの他のシステムでも利用実績が有り信頼感があります。

しかし、リクエスト数の集計は、RedisのINCRコマンドでやるだけですが、
リクエストと契約の紐付け方法をどうするかという問題があります。

リクエストに契約を直接識別可能なデータは、含まれていません。
認証用のトークンは含まれていますが、ランダム文字列であるため、単体では識別できません。
DBアクセスすれば紐付けられますが、「DBアクセスをしない」に記載した通り、DBアクセスはNGです。

上記の問題もRedisで解決することにしました。
初回リクエスト時のみDBアクセスして、トークンと契約を紐付けるキャッシュと、
上限値を保持するキャッシュを生成して、2回目以降のリクエストは、Redisだけで完結するようにしました。
これで、DBアクセス (ほぼ) なしで契約毎の制限を実現できました。

キャッシュの関係を図にすると下になります。
トークンと契約がN:1で対応していて、契約とリクエストが1:1で対応しています。
トークンが異なっても契約が同一であれば、上限値とリクエスト数も同一になることがわかると思います。

f:id:iridge-tech:20220127153817p:plain

おわりに

リクエスト制限導入以降、大量リクエストに起因する障害は起きていないので、一定の効果があったと思っています。
直接的に障害を防ぐ意味もありますが、無制限ではないことを明示したことが大きいように思います。

ただし、これで全ての障害を防げるわけではありません。少ないリクエストで高負荷になるようなケースには無力です。
量の対策はできているが、質の対策は出来ていないといった感じです。

引き続き高負荷処理に対しての性能改善や質にも対応した制限などを検討しながら、より安心安全なFANSHIPにすべく計画していこうと思います。