Amazon API Gatewayのスロットリングについて調査してみた

2023.04.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS事業本部木村です。

API GatewayからLambdaを呼び出す際のスロットリングに関して調べる機会があったので、まとめてみようと思います。

初めに

今回はREST APIを対象として、調査を行なっています。HTTP APIではAPIキーと使用量プランが対象外などの差分がありますので参考にされる場合はご注意ください。

利用できる機能の差分に関しては以下をご参照ください。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-vs-rest.html

API Gatewayで設定できるスロットリングに関して

API Gatewayでは、以下の3つの観点からスロットリングの設定が可能です。

・アカウント全体のリージョンに対するスロットリング 

・APIキーと使用量プランに基づくスロットリング 

・アクセス全体に対するメソッドレベルでのスロットリング 

使用量プランを用いたクォータを除いては1秒あたりのリクエストを基準にスロットリングを行います。

1秒間あたりの設定値を超えたら、超えたリクエストがただちに制限されるわけではなくバーストに設定された値に応じて制限が緩和されます。

詳細の説明に関しては以下の記事がわかりやすかったので、ご参照ください。

API Gateway の API Key を調べてみた

アカウント全体のリージョンに対するスロットリング

こちらはデフォルトで設定されている内容になります。

10000回/s、バースト5000で設定されておりこの値より小さな値に変更することはできません。

リクエスト数の上限の緩和に関しては、クォータの引き上げが可能です。

バーストに関しては変更を要請することはできません。

詳細に関しては以下をご参照ください。

https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html

APIキーと使用量プランに基づくスロットリング

こちらが一般的によく知られたスロットリングかと思います。

APIキーと使用量プランを組み合わせることにより、特定のAPIキーに対してスロットリングを行うことができます。

こちらの設定では、APIキーを使ったアクセス全体に対するスロットリングとメソッドに対して個別でスロットリングを行うことができます。

アクセス全体に対しては秒間のスロットリングと日・週・月単位でのクォーターと

各メソッド個別のスロットリングを設定できます。

アクセス全体に対するメソッドレベルでのスロットリング

ステージングされているメソッドごとに各自でスロットリングの制限を持つことができます。

またステージ単位でデフォルトのスロットリングの値を設定することができ、個別の値を設定しない際はこちらの値が適用されるようになっています。

こちらの設定もあくまでメソッドのスロットリングのデフォルト値となります。

メソッド単位ではスロットリングの設定が可能ですが、あくまでメソッド個別での設定となるため作成したステージ全体を単位としたスロットリングの設定を行うことはできません。

やってみた

では実際にこれらを設定して試していきたいと思います。 今回はAPIキーと使用量プランに基づくスロットリングとアクセス全体に対するメソッドレベルでのスロットリングについて検証してみたいと思います。

今回負荷ツールとしてvegetaを利用しています。

APIキーと使用量プランに基づくスロットリング

アクセス全体に対する設定

まずは全体のアクセスに対するスロットリングを設定します。2種類の設定を作成して設定通りの挙動となるか確認していきます。

無料用を想定したAPIキーには、5回/sのスロットリング・バースト値10を設定した使用量プランと紐付け、

有料用を想定したAPIキーには。50回/sのスロットリング・バースト値10を設定した使用プランと紐付けを行います。

使用量プランの設定以外は同じ条件で、10回/sのアクセスを10秒間行なって、スロットリングが正しく行われているか確認します。

無料プランではスロットリングの倍のアクセスを行うため、半分ほどがエラーとなる想定です。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=10s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         100, 10.10, 5.24
Duration      [total, attack, wait]             9.917s, 9.899s, 17.875ms
Latencies     [min, mean, 50, 90, 95, 99, max]  13.904ms, 34.313ms, 30.077ms, 45.479ms, 49.662ms, 249.894ms, 306.968ms
Bytes In      [total, mean]                     4244, 42.44
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           52.00%
Status Codes  [code:count]                      200:52  429:48  
Error Set:
429 Too Many Requests

想定通り、約半数が429エラーとなりました。

有料プランではスロットリングの設定内のアクセスであるため、エラーは発生しない想定です。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=10s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         100, 10.10, 10.06
Duration      [total, attack, wait]             9.944s, 9.9s, 44.145ms
Latencies     [min, mean, 50, 90, 95, 99, max]  29.189ms, 77.836ms, 41.584ms, 58.214ms, 370.434ms, 779.06ms, 820.243ms
Bytes In      [total, mean]                     5300, 53.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:100

想定通り、エラーの発生することなく実行をすることができました。

使用量プランの設定によって、APIキー毎にスロットリングの制御ができることを確認できました。

では続けてクォータについても確認していきます。

無料用を想定したAPIキーには、100回/日を設定した使用量プランと紐付け、

有料用を想定したAPIキーには、1000回/日を設定した使用プランと紐付けを行います。

使用量プランのAPIキー作成し直しそれぞれの実行回数が0となっていることを確認します。

使用量プランの設定以外は同じ条件でそれぞれに150回ずつアクセスを行います。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         150, 10.07, 6.70
Duration      [total, attack, wait]             14.918s, 14.899s, 18.283ms
Latencies     [min, mean, 50, 90, 95, 99, max]  15.85ms, 43.774ms, 42.913ms, 53.689ms, 67.567ms, 272.053ms, 313.907ms
Bytes In      [total, mean]                     6700, 44.67
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           66.67%
Status Codes  [code:count]                      200:100  429:50  
Error Set:
429 Too Many Requests

100回目以降は想定通りアクセスできないことを確認しました。

先ほどのカウンターを確認すると100件のアクセスがあったことを確認できました。

続いて有料プランです。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         150, 10.07, 10.03
Duration      [total, attack, wait]             14.96s, 14.9s, 59.922ms
Latencies     [min, mean, 50, 90, 95, 99, max]  28.09ms, 43.096ms, 39.922ms, 47.795ms, 57.832ms, 166.984ms, 258.424ms
Bytes In      [total, mean]                     7950, 53.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:150  
Error Set:

こちらはエラーなくアクセスできていたことを確認できました。

クォータに関しても、APIキーによって個別で制限を行うことができることを確認しました。

クォータに関してですが、APIキーと同一のステージを含んだ使用量プランが一つしか結び付けられないため、1000回/日と5000回/週の組み合わせといった複数のクォータを設定することはできませんでした。

日・週・月のいずれか1つのみしか設定が行えませんので、クォータで制限を考えている際には注意が必要になります。

最後にAPIキーに対するアクセス全体に対して、スロットリングができているか検証していきます。

まず先ほどと同様に単一のメソッドに対して、アクセスを行なってエラーが発生しないことを確認します。

使用量プランの設定がこちらになります。

以下の値で実行して、エラーが発生していないことを確認します。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         150, 10.07, 10.04
Duration      [total, attack, wait]             14.938s, 14.9s, 38.405ms
Latencies     [min, mean, 50, 90, 95, 99, max]  28.681ms, 41.134ms, 39.423ms, 46.707ms, 51.042ms, 116.831ms, 208.676ms
Bytes In      [total, mean]                     7950, 53.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:150  
Error Set:

単一で実行した場合問題ないことが確認できました。

次は以下の同じAPIキーを利用した別のメソッドへのアクセスを二つを同時に実行して、スロットリングするかを確認します。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report
% echo "GET https://XXX.example.com/test/second" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report

結果として多少のバラツキは出ましたが、スロットリングを行なってくれることを確認できました。

Requests      [total, rate, throughput]         150, 10.07, 6.69
Duration      [total, attack, wait]             14.941s, 14.9s, 41.148ms
Latencies     [min, mean, 50, 90, 95, 99, max]  13.523ms, 33.091ms, 33.492ms, 43.191ms, 47.117ms, 112.638ms, 224.009ms
Bytes In      [total, mean]                     6850, 45.67
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           66.67%
Status Codes  [code:count]                      200:100  429:50  
Error Set:
429 Too Many Requests
Requests      [total, rate, throughput]         150, 10.07, 7.37
Duration      [total, attack, wait]             14.922s, 14.899s, 23.229ms
Latencies     [min, mean, 50, 90, 95, 99, max]  14.19ms, 36.211ms, 35.712ms, 46.535ms, 50.789ms, 113.324ms, 204.914ms
Bytes In      [total, mean]                     7070, 47.13
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           73.33%
Status Codes  [code:count]                      200:110  429:40  
Error Set:
429 Too Many Requests

これで同じAPIキーを利用して、別々のメソッドにアクセスしてもAPIキー全体でスロットリングを行えることを確認することができました。

メソッドごとの個別の設定

ここまでは全体に対してのスロットリングの設定を確認してきました。

ここからはメソッドで個別で行う設定を確認していきたいと思います。

個別の設定は以下画像の赤枠内から行います。なお全体の設定が今回の検証に影響しないように値の引き上げをおこなっています。

今回は以下の個別設定を行ないました。

先ほどまでと同様に同じ条件で/firstと/secondに負荷を与えていきたいと思います。

まずは/firstを試していきます。こちらは低い値を設定しているので想定通りならスロットリングされる想定です。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=5 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         75, 5.07, 3.24
Duration      [total, attack, wait]             14.837s, 14.8s, 37.012ms
Latencies     [min, mean, 50, 90, 95, 99, max]  13.329ms, 37.346ms, 35.312ms, 47.688ms, 56.009ms, 249.861ms, 297.975ms
Bytes In      [total, mean]                     3381, 45.08
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           64.00%
Status Codes  [code:count]                      200:48  429:27  
Error Set:
429 Too Many Requests

想定通り、スロットリングされることを確認しました。

続いて/secondを試していきます。こちらはエラーが発生しない想定です。

% echo "GET https://XXX.example.com/test/second" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=5 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         75, 5.07, 5.05
Duration      [total, attack, wait]             14.842s, 14.8s, 41.732ms
Latencies     [min, mean, 50, 90, 95, 99, max]  27.744ms, 41.795ms, 38.434ms, 45.006ms, 49.916ms, 211.367ms, 263.396ms
Bytes In      [total, mean]                     3975, 53.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:75  
Error Set:

こちらは想定通りエラーが発生しませんでした。

以上のようにメソッドで個別にスロットリングができることも確認できました。

最後に全体<個別となるようなスロットリングの設定を行なった際の挙動を確認したいと思います。

全体の設定を以下に。

メソッド個別の設定を以下にします。

メソッド個別の設定が適用される場合は、エラーが発生しない想定の値で負荷をかけてみます。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=5 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         150, 10.07, 5.29
Duration      [total, attack, wait]             14.945s, 14.9s, 44.966ms
Latencies     [min, mean, 50, 90, 95, 99, max]  11.182ms, 39.32ms, 32.33ms, 41.825ms, 45.243ms, 470.88ms, 587.716ms
Bytes In      [total, mean]                     6388, 42.59
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           52.67%
Status Codes  [code:count]                      200:79  429:71  
Error Set:
429 Too Many Requests

結果としては、メソッド個別の値よりも全体の設定が優先されることを確認することができました。

全体のアクセスの想定を上回る、メソッド個別のアクセスはスロットリングされるべきですので、当然といったところでしょうか。

個別の設定はあくまでも全体の設定の中で、メソッド個別に振り分けて作成したい際に利用するものといった感じかと思います。

このような設定となるケースはないかと思いますが、全体の設定を念頭に個別のメソッドの設定を行う必要がありそうです。

アクセス全体に対するメソッドレベルでのスロットリング

今までの検証は使用量プランに紐づいたスロットリングについて確認してきました。 ここからはステージングされているメソッド全体に対するスロットリングを検証していきます。

APIキーなしでのアクセスを許容するのは望ましくないため、引き続きAPIキーを設定し使用量プランを検証に影響ない値に変更して検証を行なっていきます。

まずは個別のスロットリングが機能するか確認していきたいと思います。

全体の設定を以下のようにして

設定を上回る負荷をかけます。

% echo "GET https://XXX.example.com/test/first" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         150, 10.07, 5.09
Duration      [total, attack, wait]             14.917s, 14.9s, 16.771ms
Latencies     [min, mean, 50, 90, 95, 99, max]  12.93ms, 53.716ms, 31.344ms, 42.794ms, 185.037ms, 792.709ms, 861.547ms
Bytes In      [total, mean]                     6322, 42.15
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           50.67%
Status Codes  [code:count]                      200:76  429:74  
Error Set:
429 Too Many Requests

無事スロットリングされたことを確認できました。

念の為別のメソッドに影響を与えていないか確認してみます。

デフォルトの設定を引き継いだ/secondに同様の負荷を同時にかけてみます。

% echo "GET https://XXX.example.com/test/second" | vegeta attack -header "X-API-KEY:XXXXXXXXXXXX" -rate=10 -duration=15s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         150, 10.07, 10.04
Duration      [total, attack, wait]             14.938s, 14.9s, 38.122ms
Latencies     [min, mean, 50, 90, 95, 99, max]  25.927ms, 40.175ms, 35.798ms, 41.688ms, 47.127ms, 223.799ms, 322.87ms
Bytes In      [total, mean]                     7950, 53.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:150  
Error Set:

影響が出ていないことを確認できました。

使用量プランを設定していなくても、このようにメソッド個別にスロットリングを設定することができました。

最後に

今回はAPI Gatewayのスロットリング機能の検証を行なってみました。複数箇所でスロットリングの設定ができますので組み合わせれば様々な要件に対応できそうなことがわかりました。この記事がどなたかのお役に立てたことを祈っています。