
Lambda 関数の呼び出し(Invoke)で TooManyRequestsException(429)が返りました。再試行しても二重に実行されませんか。
困っていること
Lambda 関数を、AWS CLI や AWS SDK から同期呼び出し(Invoke)しています。
呼び出しが集中したときに、次のエラーが返りました。
このエラーの後に呼び出しを再試行すると、関数が二重に実行されないか教えてください。
aws: [ERROR]: An error occurred (TooManyRequestsException) when calling the Invoke operation (reached max retries: 0): Rate Exceeded.
Additional error details:
Type: User
Reason: ConcurrentInvocationLimitExceeded
どう対応すればいいの?
429 が返った呼び出しは、再試行しても二重には実行されません。関数を実行する前に、Lambda が拒否した呼び出しだからです。
ただし、クライアント側のタイムアウトの後に再試行した場合は、関数が二重に実行されることがあります。次の 3 つの対策を行ってください。
- 429 の再試行は、AWS CLI や SDK の自動の再試行に任せます。AWS CLI v2 のデフォルトの standard モードでは、
TooManyRequestsExceptionは再試行の対象で、指数バックオフで再試行されます。自前で再試行する場合は、指数バックオフとジッター(待ち時間のばらつき)を入れ、回数に上限を付けます。 - クライアント側のタイムアウト(AWS CLI なら
--cli-read-timeout。デフォルトは 60 秒)を、関数のタイムアウトより長くします。 - 関数のコードを冪等(同じイベントを何度処理しても結果が同じ)にします。リクエストごとの冪等性キー(注文の ID など)を、「処理中」「完了」の状態と一緒に記録します。完了済みのキーは処理せず、前の結果を返します。処理に失敗したときは、記録を消すか期限を付けて、再試行で処理し直せるようにします。
なぜ二重に実行されないのか
TooManyRequestsException(HTTP 429)は、同時実行数のクォータを超えたときに、Lambda が関数を実行せずに返すエラーです。
例えば、関数の実行がアカウントレベル (ConcurrentInvocationLimitExceeded) または関数レベル (ReservedFunctionConcurrentInvocationLimitExceeded) のいずれかで同時実行制限を超過する原因となる場合、Lambda は TooManyRequestsException を返します。
タイムアウトの後の再試行に注意
AWS CLI と AWS SDK は、スロットリングだけでなく、クライアント側のタイムアウトでも自動で再試行します。
また、AWS CLI と AWS SDK は、クライアントのタイムアウト、スロットル、およびサービスエラーで自動的に再試行します。
クライアント側のタイムアウトは、呼び出す側が、設定した時間内に応答を受け取れずに接続を切ることです。関数のタイムアウトとは別の設定です。このとき関数はすでに実行されている場合があるので、再試行で関数が複数回呼び出されることがあります(今回は試していません)。対策 2 と 3 は、このためのものです。
429 が続く場合
アカウントや関数の同時実行数を確認してください。アカウントの同時実行数のクォータは、aws lambda get-account-settings の出力の AccountLimit.ConcurrentExecutions で確認できます。デフォルトは 1,000 ですが、新しいアカウントでは低く設定されています。
拒否された呼び出しの数は、Lambda のコンソールの「Event Functions」で関数を開き、「モニタリング」タブの「スロットリング」のグラフで確認できます。
確認してみた
同時実行数のクォータが 10 の検証用のアカウント(東京リージョン)で試しました。予約済み同時実行数を設定していない、10 秒待つだけの Lambda 関数(Python 3.13)に、12 個の呼び出しを同時に送りました。
冒頭のエラーと同じ形で確かめるため、AWS CLI の再試行を無効にして(AWS_MAX_ATTEMPTS=1)実行しました。
export AWS_MAX_ATTEMPTS=1
for i in $(seq 1 12); do
( aws lambda invoke --function-name blog-01-fn --cli-binary-format raw-in-base64-out \
--payload "{\"id\":$i}" --cli-read-timeout 60 --debug /dev/null 2>&1 \
| grep -E 'HTTP/1\.1" [0-9]{3}|An error occurred|^Type:|^Reason:|"StatusCode"' > inv-$i.txt ) &
done; wait
12 個のうち 2 個が、「困っていること」と同じ TooManyRequestsException のエラーで拒否されました。--debug の出力では、拒否された 2 個の応答は HTTP 429、ほかの 10 個は 200 でした。次の画像は、inv-9.txt・inv-10.txt・inv-1.txt に見出しを付けて、順に表示したものです。

関数のログに残った呼び出しは 10 個で、拒否された 2 個は実行されていませんでした。予約済み同時実行数は設定しておらず、Reason は ConcurrentInvocationLimitExceeded だったので、アカウントの同時実行数のクォータ(10)に当たったと判断しました。
コンソールの「モニタリング」タブでも、「呼び出し」は 10、「スロットリング」は 2 でした。次の画像は、同じ条件で先に行った検証のグラフで、数は上の検証と同じです。









