この記事でできること
公開ページにIP単位のレート制限を掛けているのに、上限の内側に収まったクローラー1体だけでサーバーが詰まることがあります。個人開発している技術記事キュレーションサービスDevPickでは、ページ描画の上限を240/分にしていたところ、毎分170〜320ページを取りに来たクローラーでバックエンドのワーカーが入れ替わり続け、他の閲覧者にも502が出ました。上限は90/分に下げています。
この記事は、そのときに見直した点を、ページ描画にレート制限を掛けるときの点検リストとしてまとめたものです。対象は、SSRのフロントエンドがバックエンドAPIを呼ぶ構成で、小さいVPS1台で動かしているサービスです。
前提:上限が前段と後段の二段になっている
DevPickでは、Next.jsのproxy(ページを描画する前に走る処理)がIPごとにページ数を数え、上限を超えた分には503とRetry-Afterを返しています。その後ろでは、FastAPIのバックエンドがAPIの呼び出し回数をIPごとに数えています。
前段のページ上限を置いているのは、検索クローラーへ正しいステータスを返すためです。SSRの途中でバックエンドの429に当たると、ページはエラー境界の500になってしまいます。前段で先に止めれば「過負荷なので後で来てほしい」という意味の503を返せます。
以下の点検項目は、この二段構成を前提にしています。
点検1:上限の根拠に「サーバーが捌ける量」が入っているか
上限の値を決めた理由を書き出してみます。「クローラーの通常の速度を止めないため」「普通の閲覧者が超えないため」のように、通したい相手の速度だけが根拠になっている値は要注意です。その値いっぱいでアクセスされたときにサーバーが耐えられるかを、誰も確かめていないからです。
レート制限は、上限の手前までは何も止めません。上限は「この速度までは来てもよい」という宣言でもあるので、上限いっぱいのアクセスが10分、20分と続いてもサーバーが処理しきれるかで見積もります。通したい相手の速度は下限として使い、上限はサーバーの処理能力から決めて、その間に値を置きます。両者が逆転していたら、上限の値を調整する前にサーバーの処理能力かページの重さを見直す必要があります。
点検2:1ページが裏で何回APIを呼ぶかを掛けているか
ページの上限とサーバーの負荷は1対1ではありません。DevPickの記事詳細ページは、SSRで記事本体・関連記事・画像の確認と、バックエンドAPIを3回呼びます。ページ上限が90/分でも、1つのIPからバックエンドへ最大で毎分270回の呼び出しが届きます。
ページ単位の上限を決めるときは、ページの種類ごとにSSRが呼ぶAPIの回数を数え、一番重いページで掛け算した数をサーバーの処理能力と比べます。ページ数だけを見ていると、実際の負荷を数分の一に見積もってしまいます。
点検3:前段と後段の上限の大小関係を崩していないか
前段で503を返す仕組みは、後段のSSR用の上限が、前段のページ上限で通したアクセスを受け止められるときだけ働きます。APIごとの上限なら「ページ上限 × そのページがそのAPIを呼ぶ回数」、バックエンド全体の上限なら「ページ上限 × 1ページの合計呼び出し回数」を下回らないようにします。これが逆転すると、前段で止まる前にバックエンドの429へ当たり、ページは500に戻ります。
- 前段の上限を下げる変更は、この関係を崩さないので、前段だけを変えれば済みます
- 前段の上限を上げるときは、後段の上限も一緒に見直します
- 2つの値は別々のファイル・別々の言語にあることが多いので、大小関係をテストで確かめておくと、片方だけ変えたときに気づけます
点検4:IP単位の上限で守れない巡回への備えがあるか
IP単位の上限は、1つのIPからの集中には効きますが、複数のIPから分散したアクセスには効きません。点検1の見積もりも「1つのIPが上限いっぱいで来た場合」までしか保証しないので、次も確かめます。
- IPに依らない上限(全体の同時描画数、全体の毎分のページ数)があるか。無いなら、それが無い状態で何本の巡回まで耐えられるかを把握しているか
- 同時描画数を数えるなら、数える場所で描画の開始と終了の両方を知れるか。リクエストを受けた時点で1回だけ走る処理では、開始は数えられても終了は数えられない
- 全体の上限を超えたときも、個別の上限と同じく
503とRetry-Afterを返すか
点検5:下げた上限が検索クローラーの巡回を減らしすぎないか
上限を下げると、クローラーに返す503は増えます。Googleのドキュメントでは、5xxや429を受け取ったクローラーはそのサイトのクロール速度を一時的に下げ、エラーが減れば徐々に戻す、とされています。一方で、同じURLが何日も5xxを返し続けると、インデックスから外れることがあるとも書かれています(HTTP ステータスコードの扱い、クロール頻度を下げる)。
一時的に503を返すのは、サーバーが詰まって502や500を返すよりはずっとましです。それでも、上限を下げたあとはSearch Consoleのクロールの統計情報で、クロール数やサーバーエラーの割合が大きく変わっていないかを見ておきます。
ワーカーが入れ替わり続けたときの切り分け方
同じ症状が出たときは、次の順で確かめると原因にたどり着きやすくなります。
- ワーカーが入れ替わった時刻を、アプリのログから拾う
- その時間帯にアクセスログを絞り、User-Agent別・IP別にリクエスト数を数える。静的ファイルを除いた、ページ描画の件数で見る
- 特定のIPやUser-Agentに偏っていれば、毎分のページ数を上限と比べる。上限で
503になった件数がほとんど無いのに詰まっていれば、上限の値そのものが大きすぎる - 偏りが無く、多くのIPに分散していれば、IP単位の上限では防げない種類の負荷なので、点検4に戻る
まとめ
- レート制限の上限は、通したい相手の速度だけでなく、上限いっぱいで来られてもサーバーが捌けるかで決める
- ページの上限は、1ページがSSRで呼ぶAPIの回数を掛けてからサーバーの処理能力と比べる
- 後段のSSR用の上限が、前段のページ上限 × API回数を下回らない関係を保つ。前段を上げるときは両方を見直す
- IP単位の上限は分散した巡回には効かない。全体の上限は別に用意する
- 上限を下げたら、検索クローラーの巡回が落ちすぎていないかをSearch Consoleで見る
240/分という値をなぜ選び、どう90/分へ下げたかの経緯は、Zenn版に書いています。