AWS の API スロットリングの仕組み - トークンバケットアルゴリズムと 429 エラーの正体

AWS API のレート制限がトークンバケットアルゴリズムで実装されている仕組み、バーストキャパシティの概念、サービスごとの制限値の違い、スロットリング回避の実践的な対策を解説します。

なぜ AWS はすべての API にレート制限をかけるのか

AWS のすべての API には、アカウントごと・リージョンごとのレート制限 (スロットリング) が設定されています。制限を超えたリクエストには HTTP 429 (Too Many Requests) または 503 (Service Unavailable) エラーが返されます。レート制限の目的は 2 つあります。第 1 に、マルチテナント環境の公平性の確保です。1 つのアカウントが API を大量に呼び出すと、同じインフラを共有する他のアカウントのパフォーマンスに影響します。レート制限は「ノイジーネイバー問題」を防ぐガードレールです。第 2 に、顧客自身の保護です。アプリケーションのバグで無限ループが発生し、API を毎秒数万回呼び出してしまうケースがあります。レート制限がなければ、この暴走が高額な請求につながります。レート制限は、意図しない暴走を早期に検出するセーフティネットとして機能します。各サービスのレート制限値は Service Quotas で確認でき、多くの制限は上限緩和リクエストで引き上げ可能です。

トークンバケットアルゴリズムの仕組み

AWS の API スロットリングは、トークンバケットアルゴリズムで実装されています。このアルゴリズムは、バケツ (容器) にトークン (許可証) が一定速度で補充され、API リクエストごとにトークンを 1 つ消費する仕組みです。バケツが空になるとリクエストは拒否されます。具体例で説明します。EC2 の DescribeInstances API のレート制限が秒間 100 リクエスト、バーストキャパシティが 200 だとします。バケツには毎秒 100 個のトークンが補充され、バケツの最大容量は 200 個です。通常時はバケツが満杯 (200 トークン) なので、瞬間的に 200 リクエストを送信できます (バースト)。その後は毎秒 100 リクエストのペースでしか処理できません。バーストキャパシティは、短時間の急増を吸収するバッファです。アプリケーションの起動時に一斉に API を呼び出すようなパターンでは、バーストキャパシティが重要になります。バーストを使い切った後は、定常レート (秒間 100 リクエスト) に制限されます。

サービスごとに異なるスロットリングの粒度

スロットリングの粒度はサービスによって大きく異なります。EC2 の API は、API アクションごとに個別のレート制限が設定されています。DescribeInstances と RunInstances は別々のバケットで管理されるため、DescribeInstances のスロットリングが RunInstances に影響することはありません。一方、DynamoDB のスロットリングはテーブルレベルで適用されます。テーブルのプロビジョンドキャパシティ (RCU/WCU) を超えるリクエストがスロットリングされます。これは API レベルのスロットリングとは異なり、データアクセスのスループット制限です。Lambda の同時実行数制限もスロットリングの一種です。アカウントのデフォルト同時実行数 1,000 を超える関数呼び出しは、429 エラーでスロットリングされます。API Gateway は、アカウントレベルで秒間 10,000 リクエスト (デフォルト) のレート制限があり、さらに API ごと、ステージごと、メソッドごとに個別のスロットリング設定が可能です。この多層的なスロットリングにより、特定の API エンドポイントへの集中アクセスが他のエンドポイントに影響しないよう制御できます。

指数バックオフとジッター - SDK が自動で行うリトライ戦略

スロットリングエラー (429) を受け取った場合の正しい対処は、指数バックオフ (Exponential Backoff) とジッター (Jitter) を組み合わせたリトライです。指数バックオフは、リトライ間隔を 1 秒、2 秒、4 秒、8 秒と指数的に増加させる戦略です。これにより、スロットリング中のサービスに対するリクエスト圧力を段階的に緩和します。ジッターは、リトライ間隔にランダムな揺らぎを加える手法です。指数バックオフだけでは、同時にスロットリングされた複数のクライアントが同じタイミングでリトライし、再びスロットリングされる「サンダリングハード」問題が発生します。ジッターを加えることで、リトライのタイミングが分散されます。AWS SDK はこのリトライ戦略を内部で自動的に実装しています。SDK v3 (JavaScript) のデフォルトは最大 3 回のリトライで、指数バックオフとフルジッターが適用されます。boto3 (Python) も同様のリトライ戦略を持っています。SDK を使わずに直接 API を呼び出す場合は、このリトライロジックを自前で実装する必要があります。

スロットリングを事前に回避する設計パターン

スロットリングが発生してからリトライするよりも、そもそもスロットリングを発生させない設計が理想です。第 1 のパターンは、API 呼び出しの削減です。EC2 の DescribeInstances を毎秒呼び出してインスタンスの状態を監視するのではなく、EventBridge のイベント (EC2 Instance State-change Notification) を使えば、状態変化があったときだけ通知を受け取れます。ポーリングからイベント駆動への転換は、API 呼び出し数を劇的に削減します。第 2 のパターンは、キャッシュの活用です。頻繁に変わらない情報 (アカウントの設定、リージョンの一覧など) は、ローカルにキャッシュして API 呼び出しを減らします。第 3 のパターンは、バッチ API の活用です。DynamoDB の BatchGetItem は、最大 100 個のアイテムを 1 回の API 呼び出しで取得できます。個別に GetItem を 100 回呼ぶよりも、API 呼び出し数を 99% 削減できます。S3 の ListObjectsV2 も、MaxKeys パラメータで 1 回のリクエストで最大 1,000 オブジェクトを取得できます。API 設計とスロットリング対策を体系的に学ぶには、専門書籍 (Amazon)が参考になります。