「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > スロットリングの説明: API リクエスト制限の管理ガイド

スロットリングの説明: API リクエスト制限の管理ガイド

2024 年 12 月 22 日に公開
ブラウズ:952

コードにスロットリングを実装する必要があるのはどのような場合ですか?

大規模なプロジェクトの場合は、通常、Cloudflare Rate Limiting や HAProxy などのツールを使用するのが最善です。これらは強力で信頼性が高く、面倒な作業を代わりに行ってくれます。

ただし、小規模なプロジェクトの場合、または物事がどのように機能するかを知りたい場合は、コード内に独自のレート リミッターを直接作成できます。なぜ?

  • シンプルです: 理解しやすい、単純なものを構築します。
  • 予算に優しい: サーバーのホスティング以外に追加費用はかかりません。
  • 小規模プロジェクトに適しています: トラフィックが少ない限り、作業を高速かつ効率的に維持できます。
  • 再利用可能: 新しいツールやサービスをセットアップせずに、他のプロジェクトにコピーできます。

何を学ぶか

このガイドを最後まで読むと、API が過剰になるのを防ぐために TypeScript で基本的なスロットラーを構築する方法がわかります。ここで取り上げる内容は次のとおりです:

  • 設定可能な時間制限: 試行がブロックされるたびに、悪用を防ぐためにロックアウト期間が長くなります。
  • リクエストの上限: 許可されるリクエストの最大数を設定します。これは、OpenAI などの有料サービスを伴う API に特に役立ちます。
  • インメモリ ストレージ: Redis などの外部ツールなしで動作するシンプルなソリューション。小規模なプロジェクトやプロトタイプに最適です。
  • ユーザーごとの制限: IPv4 アドレスを使用してユーザーごとにリクエストを追跡します。 SvelteKit を利用して、組み込みメソッドでクライアント IP を簡単に取得します。

このガイドは実践的な出発点となるように設計されており、不必要に複雑にすることなく基本を学びたい開発者に最適です。 しかし、本番環境に対応していません

始める前に、Lucia のレート制限セクションを正しく評価したいと思います。


スロットラーの実装

スロットラー クラスを定義しましょう:

export class Throttler {
    private storage = new Map();

    constructor(private timeoutSeconds: number[]) {}
}

Throttler コンストラクターは、タイムアウト期間のリスト (timeoutSeconds) を受け入れます。ユーザーがブロックされるたびに、このリストに基づいて期間が徐々に長くなります。最終的に、最後のタイムアウトに達すると、コールバックをトリガーしてユーザーの IP を永久に禁止することもできます。ただし、それはこのガイドの範囲を超えています。

これは、間隔の増加に対してユーザーをブロックするスロットラー インスタンスを作成する例です:

const throttler = new Throttler([1, 2, 4, 8, 16]);

このインスタンスは、初めてユーザーを 1 秒間ブロックします。 2 回目は 2 回目、というように。

IP アドレスとそれに対応するデータを保存するためにマップを使用します。マップは頻繁な追加と削除を効率的に処理できるため、理想的です。

プロのヒント: 頻繁に変更される動的データにはマップを使用します。静的で変化しないデータの場合は、オブジェクトの方が適しています。 (ウサギの穴 1)


エンドポイントがリクエストを受信すると、ユーザーの IP アドレスを抽出し、スロットラーに問い合わせてリクエストを許可するかどうかを判断します。

仕組み

  • ケース A: 新規または非アクティブなユーザー

    IP がスロットラーで見つからない場合、それはユーザーの最初のリクエストであるか、ユーザーが十分な期間非アクティブであったかのいずれかです。この場合:

    • アクションを許可します。
    • 初期タイムアウトを使用して IP を保存することでユーザーを追跡します。
  • ケース B: アクティブ ユーザー

    IP が見つかった場合は、ユーザーが以前にリクエストを行ったことを意味します。ここ:

    • 最後のブロックから必要な待ち時間 (timeoutSeconds 配列に基づく) が経過したかどうかを確認します。
    • 十分な時間が経過した場合:
    • タイムスタンプを更新します。
    • タイムアウト インデックスをインクリメントします (オーバーフローを防ぐために最後のインデックスに制限されます)。
    • そうでない場合は、リクエストを拒否します。

後者の場合、最後のブロックから十分な時間が経過したかどうかを確認する必要があります。インデックスのおかげで、どの timeoutSeconds を参照する必要があるかがわかります。そうでない場合は、単にリバウンドしてください。それ以外の場合は、タイムスタンプを更新します。

export class Throttler {
    // ...

    public consume(key: string): boolean {
        const counter = this.storage.get(key) ?? null;
        const now = Date.now();

        // Case A
        if (counter === null) {
            // At next request, will be found.
            // The index 0 of [1, 2, 4, 8, 16] returns 1.
            // That's the amount of seconds it will have to wait.
            this.storage.set(key, {
                index: 0,
                updatedAt: now
            });
            return true; // allowed
        }

        // Case B
        const timeoutMs = this.timeoutSeconds[counter.index] * 1000;
        const allowed = now - counter.updatedAt >= timeoutMs;
        if (!allowed) {
            return false; // denied
        }

        // Allow the call, but increment timeout for following requests.
        counter.updatedAt = now;
        counter.index = Math.min(counter.index   1, this.timeoutSeconds.length - 1);
        this.storage.set(key, counter);

        return true; // allowed
    }
}

インデックスを更新するときは、timeoutSeconds の最後のインデックスに制限されます。これがないと、counter.index 1 がオーバーフローし、次に this.timeoutSeconds[counter.index] にアクセスすると実行時エラーが発生します。

エンドポイントの例

この例では、スロットラーを使用してユーザーが API を呼び出す頻度を制限する方法を示します。ユーザーがあまりにも多くのリクエストを行うと、メイン ロジックが実行されずにエラーが発生します。

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]);

export async function GET({ getClientAddress }) {
    const IP = getClientAddress();

    if (!throttler.consume(IP)) {
        throw error(429, { message: 'Too Many Requests' });
    }

    // Read from DB, call OpenAI - do the thing.

    return new Response(null, { status: 200 });
}

Throttling Explained: A Guide to Managing API Request Limits

認証に関する注意事項

ログイン システムでレート制限を使用すると、次の問題が発生する可能性があります:

  1. ユーザーがログインすると、スロットラーがタイムアウトを IP に関連付けます。
  2. ユーザーがログアウトするか、セッションが終了します(例: すぐにログアウトする、セッションとともに Cookie が期限切れになり、ブラウザがクラッシュするなど)。
  3. その直後に再度ログインしようとすると、スロットラーが依然としてブロックし、429 Too Many Requests エラーを返す可能性があります。

これを防ぐには、レート制限に IP の代わりにユーザーの一意の userID を使用します。また、不必要なブロックを避けるために、ログインに成功した後にスロットラーの状態をリセットする必要があります。

Throttler クラスにリセット メソッドを追加します:

export class Throttler {
    // ...

    public reset(key: string): void {
        this.storage.delete(key);
    }
}

ログインに成功した後にそれを使用します:

const user = db.get(email);

if (!throttler.consume(user.ID)) {
    throw error(429);
}

const validPassword = verifyPassword(user.password, providedPassword);
if (!validPassword) {
    throw error(401);
}

throttler.reset(user.id); // Clear throttling for the user

定期的なクリーンアップによる古い IP レコードの管理

スロットラーは IP とレート制限を追跡するため、不要になった IP レコードをいつどのように削除するかを考えることが重要です。クリーンアップ メカニズムがないと、スロットラーはレコードをメモリに保存し続けるため、データが増大するにつれてパフォーマンスの問題が発生する可能性があります。

これを防ぐために、一定期間非アクティブになった後に古いレコードを定期的に削除するクリーンアップ機能を実装できます。ここでは、スロットラーから古いエントリを削除する簡単なクリーンアップ メソッドを追加する方法の例を示します。

export class Throttler {
    // ...

    public cleanup(): void {
        const now = Date.now()

        // Capture the keys first to avoid issues during iteration (we use .delete)
        const keys = Array.from(this.storage.keys())

        for (const key of keys) {
            const counter = this.storage.get(key)
            if (!counter) {
                // Skip if the counter is already deleted (handles concurrency issues)
                return
            }

            // If the IP is at the first timeout, remove it from storage
            if (counter.index == 0) {
                this.storage.delete(key)
                continue
            }

            // Otherwise, reduce the timeout index and update the timestamp
            counter.index -= 1
            counter.updatedAt = now
            this.storage.set(key, counter)
        }
    }
}

クリーンアップをスケジュールする非常に簡単な方法 (ただし、おそらく最良ではない) は setInterval:
を使用することです。

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300])
const oneMinute = 60_000
setInterval(() => throttler.cleanup(), oneMinute)

このクリーンアップ メカニズムは、スロットラーが古いレコードを無期限に保持しないようにし、アプリケーションの効率を維持するのに役立ちます。このアプローチはシンプルで実装が簡単ですが、より複雑なユースケース (より高度なスケジューリングの使用や高い同時実行性の処理など) では、さらに改良する必要がある場合があります。

定期的なクリーンアップにより、メモリの肥大化を防ぎ、しばらくリクエストを試行していないユーザーが追跡されないようにすることができます。これは、レート制限システムをスケーラブルでリソース効率の高いものにするための第一歩です。


  1. 冒険心があれば、プロパティがどのように割り当てられ、それがどのように変化するかを読むことに興味があるかもしれません。また、モノモーフィズムで特に好まれるインライン キャッシュなどの VM の最適化についても考えてみましょう。楽しむ。 ↩

リリースステートメント この記事は次の場所に転載されています: https://dev.to/didof/throttling-explained-a-guide-to-managing-api-request-limits-102a?1 侵害がある場合は、[email protected] までご連絡ください。それを削除するには
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3