レート制限パターン:スロットルされたサービスの効率的な管理

  1. コンテキストと問題
  2. ソリューション
  3. 問題と考慮事項
  4. このパターンを使用する場合
  5. アーキテクチャの例
  6. 関連パターン
  7. 参考文献

多くのサービスは、リソース消費を制御するためにスロットリングを使用し、アプリケーションがアクセスできるレートに制限を課しています。レート制限パターンは、スロットルエラーを回避し、特にバッチ処理のような大規模な反復タスクでスループットを正確に予測するのに役立ちます。

コンテキストと問題

スロットルされたサービスに対して大量の操作を実行すると、トラフィックが増加し、効率が低下する可能性があります。拒否されたリクエストを追跡し、操作を再試行する必要があり、作業を完了するために複数のパスが必要になる可能性があります。

データベースへのデータ取り込みの例を考えてみましょう:

  • アプリケーションは10,000件のレコードを取り込む必要があります。各レコードは10リクエストユニット(RU)を消費し、合計100,000 RUが必要です。
  • データベースインスタンスには20,000 RUのプロビジョニング容量があります。
  • 10,000件のレコードをすべて送信します。2,000件が成功し、8,000件が拒否されます。
  • 8,000件のレコードで再試行します。2,000件が成功し、6,000件が拒否されます。
  • 6,000件のレコードで再試行します。2,000件が成功し、4,000件が拒否されます。
  • 4,000件のレコードで再試行します。2,000件が成功し、2,000件が拒否されます。
  • 2,000件のレコードで再試行します。すべて成功します。

ジョブは完了しましたが、実際のデータセットサイズの3倍である30,000件のレコードを送信した後でした。

この単純なアプローチには追加の問題があります:

  • エラー処理のオーバーヘッド:20,000件のエラーをログに記録して処理する必要があり、メモリとストレージを消費します。
  • 予測不可能な完了時間:スロットリング制限を知らなければ、処理にかかる時間を見積もることができません。

ソリューション

レート制限は、時間の経過とともにサービスに送信されるレコード数を制御することで、トラフィックを削減し、スループットを向上させます。

サービスは、さまざまなメトリックに基づいてスロットルします:

  • 操作の数(例:1秒あたり20リクエスト)
  • データ量(例:1分あたり2 GiB)
  • 操作の相対コスト(例:1秒あたり20,000 RU)

レート制限の実装は、容量を超えることなく使用を最適化し、サービスに送信される操作を制御する必要があります。

永続的なメッセージングシステムの使用

APIがスロットルされたサービスが許可するよりも速くリクエストを処理できる場合、取り込み速度を管理する必要があります。単にリクエストをバッファリングするのはリスクがあります。アプリケーションがクラッシュすると、バッファリングされたデータが失われます。

代わりに、完全な取り込みレートを処理できる永続的なメッセージングシステムにレコードを送信します。ジョブプロセッサを使用して、スロットルされたサービスの制限内で制御されたレートでレコードを読み取ります。

永続的なメッセージングオプションには次のものがあります:

  • メッセージキュー(例:RabbitMQ、ActiveMQ)
  • イベントストリーミングプラットフォーム(例:Apache Kafka)
  • クラウドベースのキューサービス
graph LR A["API
(高レート)"] --> B["永続的
メッセージキュー"] B --> C["ジョブプロセッサ 1"] B --> D["ジョブプロセッサ 2"] B --> E["ジョブプロセッサ 3"] C --> F["スロットルされたサービス
(制限レート)"] D --> F E --> F style A fill:#e1f5ff style B fill:#fff4e1 style F fill:#ffe1e1

細かい時間間隔

サービスは、理解しやすい時間範囲(秒単位または分単位)に基づいてスロットルすることがよくありますが、コンピュータははるかに高速に処理します。1秒に1回バッチでリリースするのではなく、より頻繁に少量を送信することで:

  • リソース消費(メモリ、CPU、ネットワーク)を均等に流し続ける
  • 突然のリクエストバーストによるボトルネックを防ぐ

たとえば、サービスが1秒あたり100操作を許可する場合、200ミリ秒ごとに20操作をリリースします:

複数の非協調プロセスの管理

複数のプロセスがスロットルされたサービスを共有する場合、サービスの容量を論理的に分割し、分散相互排他システムを使用してそれらのパーティションのロックを管理します。

例:

スロットルされたシステムが1秒あたり500リクエストを許可する場合:

  1. 1秒あたり25リクエストの価値がある20のパーティションを作成します
  2. 100リクエストを必要とするプロセスは4つのパーティションを要求します
  3. システムは10秒間2つのパーティションを付与します
  4. プロセスは1秒あたり50リクエストにレート制限し、2秒で完了し、ロックを解放します

実装アプローチ:

ブロブストレージを使用して、論理パーティションごとに1つの小さなファイルを作成します。アプリケーションは、短期間(例:15秒)これらのファイルの排他的リースを取得します。付与された各リースに対して、アプリケーションはそのパーティションの容量を使用できます。

block-beta columns 3 block:processes:3 columns 3 P1["プロセス 1"] P2["プロセス 2"] P3["プロセス 3"] end space:3 block:leases:3 columns 3 L1["リース 1
25 req/s"] L2["リース 2
25 req/s"] L3["リース 3
25 req/s"] end space:3 block:service:3 S["スロットルされたサービス
合計500 req/s"] end P1 --> L1 P2 --> L2 P3 --> L3 L1 --> S L2 --> S L3 --> S style processes fill:#e1f5ff style leases fill:#fff4e1 style service fill:#ffe1e1

レイテンシを削減するために、各プロセスに少量の排他的容量を割り当てます。プロセスは、予約容量を超えた場合にのみ共有容量リースを求めます。

リース管理の代替技術には、Zookeeper、Consul、etcd、Redis/Redsyncがあります。

問題と考慮事項

💡 主要な考慮事項

スロットルエラーの処理:レート制限はエラーを削減しますが、完全には排除しません。アプリケーションは、発生するスロットルエラーを処理する必要があります。

複数のワークストリーム:アプリケーションに同じスロットルされたサービスにアクセスする複数のワークストリーム(例:一括ロードとクエリ)がある場合、すべてをレート制限戦略に統合するか、それぞれに個別の容量プールを予約します。

マルチアプリケーション使用:複数のアプリケーションが同じスロットルされたサービスを使用する場合、スロットルエラーの増加は競合を示している可能性があります。他のアプリケーションからの使用が減少するまで、一時的にスループットを削減することを検討してください。

!!!

このパターンを使用する場合

このパターンを使用して:

  • スロットル制限されたサービスからのスロットルエラーを削減する
  • 単純な再試行エラーアプローチと比較してトラフィックを削減する
  • 処理する容量がある場合にのみレコードをデキューすることでメモリ消費を削減する
  • バッチ処理の完了時間の予測可能性を向上させる

アーキテクチャの例

ユーザーがさまざまなタイプのレコードをAPIに送信するアプリケーションを考えてみましょう。各レコードタイプには、検証、エンリッチメント、データベース挿入を実行する固有のジョブプロセッサがあります。

すべてのコンポーネント(API、ジョブプロセッサ)は、独立してスケールし、直接通信しない別々のプロセスです。

graph TB U1["ユーザー"] --> API["API"] U2["ユーザー"] --> API API --> QA["キュー A
(タイプAレコード)"] API --> QB["キュー B
(タイプBレコード)"] QA --> JPA["ジョブプロセッサ A"] QB --> JPB["ジョブプロセッサ B"] JPA --> LS["リースストレージ
(ブロブ 0-9)"] JPB --> LS JPA --> DB["データベース
(1000 req/s制限)"] JPB --> DB style API fill:#e1f5ff style QA fill:#fff4e1 style QB fill:#fff4e1 style LS fill:#f0e1ff style DB fill:#ffe1e1

ワークフロー:

  1. ユーザーがタイプAの10,000件のレコードをAPIに送信します
  2. APIはキューAにレコードをエンキューします
  3. ユーザーがタイプBの5,000件のレコードをAPIに送信します
  4. APIはキューBにレコードをエンキューします
  5. ジョブプロセッサAがブロブ2のリースを試みます
  6. ジョブプロセッサBがブロブ2のリースを試みます
  7. ジョブプロセッサAは失敗します。ジョブプロセッサBは15秒間リースを取得します(100 req/s容量)
  8. ジョブプロセッサBは100件のレコードをデキューして書き込みます
  9. 1秒後、両方のプロセッサが追加のリースを試みます
  10. ジョブプロセッサAはブロブ6を取得します(100 req/s)。ジョブプロセッサBはブロブ3を取得します(現在合計200 req/s)
  11. プロセッサはリースを競い合い、付与されたレートでレコードを処理し続けます
  12. リースが期限切れになると(15秒後)、プロセッサはそれに応じてリクエストレートを削減します

関連パターン

スロットリング:レート制限は通常、スロットルされたサービスに応答して実装されます。

再試行:リクエストがスロットルエラーになった場合、適切な間隔の後に再試行します。

キューベースの負荷平準化:レート制限に似ていますが、より広範です。主な違い:

  • レート制限は必ずしもキューを必要としませんが、永続的なメッセージングが必要です
  • レート制限は、非協調プロセス間で容量を管理するためのパーティションの分散相互排他を導入します
  • キューベースの負荷平準化は、サービス間のパフォーマンスの不一致に適用されます。レート制限は特にスロットルされたサービスに対処します

参考文献

シェア