エラーメッセージAWS APIに対して大量のリクエストを実行していると、突然スクリプトが停止することがあります。画面に赤いテキストが並び、最後には次のようなメッセージが表示されます。
botocore.exceptions.ClientError: An error occurred (ThrottlingException) when calling the DescribeInstances operation: Rate exceeded
このエラーにはさまざまな種類があります。DynamoDBでは ProvisionedThroughputExceededException、EC2では多くの場合 RequestLimitExceeded と表示されます。名称は異なりますが、根本的な原因はシンプルです。アカウントやリージョンに対してAWSが許可している制限速度を超えてリクエストを送信していることです。
発生原因AWSはインフラを保護するために「トークンバケット」アルゴリズムを使用しています。これは、1分間に5人の顧客に対応できるコーヒーショップのようなものだと考えてください。一度に20人が店に入ってきた場合、最初の5人はすぐにコーヒーを受け取れますが、残りの人はバリスタの作業が追いつくまで待たなければなりません。
以下のような場合に、これらの制限に達する可能性が高くなります:
- 高速なループ処理: 1回も
time.sleep()を挟まずに、スクリプトで500個のS3オブジェクトやSSMパラメータをループ処理している。- Lambdaのスパイク: 100個のLambda関数が同時に起動し、すべてがSecrets Managerから同じシークレットを一斉に取得しようとしている。- 大規模なCI/CDパイプライン: 大規模なデプロイ中に、TerraformやPulumiなどのツールが1秒間に数百回のBoto3コールを行っている。## 解決策(ステップバイステップ)### 1. Boto3の適応型リトライ戦略を使用するBoto3のデフォルトのリトライポリシーは、大量のリクエストを処理するスクリプトには不十分なことがよくあります。最も簡単な解決策は、デフォルト設定をadaptiveモードを使用した堅牢なConfigオブジェクトに置き換えることです。
import boto3
from botocore.config import Config
# よりスマートなクライアント側レート制限のために 'adaptive' モードを有効にする
my_config = Config(
retries = {
'max_attempts': 10,
'mode': 'adaptive'
}
)
# クライアントに設定を適用する
ec2 = boto3.client('ec2', config=my_config)
response = ec2.describe_instances()
なぜ Adaptive モードを使うのか?
- Standard(標準): 基本的な指数バックオフを使用して、一時的なエラーに対して最大3回リトライします。- Adaptive(適応型): バルク操作における決定版です。スロットリングレスポンスを監視し、サービスのキャパシティに合わせて送信リクエストの速度を実際に低下させます。これにより、最初からバケット制限に達するのを防ぎます。### 2. カスタム指数バックオフを実装する特定の高リスクな関数に対して、より精密な制御が必要な場合があります。Pythonの呼び出しをリトライロジックでラップする場合、
tenacityライブラリが業界標準として使われています。
from tenacity import retry, wait_exponential, stop_after_attempt
import boto3
s3 = boto3.client('s3')
@website/content/errors/en/nodejs/fix-fatal-error-callandretrylast-allocation-failed-javascript-heap-out-of-memory.md(wait=wait_exponential(multiplier=1, min=2, max=10), stop=stop_after_attempt(5))
def get_s3_object_with_retry(bucket, key):
return s3.get_object(Bucket=bucket, Key=key)
# スロットリングが発生した場合、この呼び出しは2秒、4秒、8秒と待機します
result = get_s3_object_with_retry("my-bucket", "large-data.json")
3. APIの利用パターンを最適化するリトライを追加する前に、リクエスト回数を減らせないか検討してください。最適化は常に、エラーハンドリングよりもクリーンな解決策です。
- バッチ処理: 複数形(plural)のAPIを使用します。ループ内で
ssm.get_parameterを呼び出す代わりに、ssm.get_parametersを使用して一度に10個のパラメータを取得します。- サーバー側フィルタリング: 「実行中」の2つのインスタンスを探すためだけに、1,000個のEC2インスタンスをリストアップしないでください。APIコールのFiltersパラメータを使用して、AWS側に負荷の高い処理を任せましょう。- キャッシュ: シークレットや設定値を取得する場合は、60秒間メモリに保存するようにします。同じシークレットを1分間に1,000回取得すれば、確実にスロットリングが発生します。### 4. サービスのクォータ引き上げをリクエストするコードが効率的であるにもかかわらず制限に達する場合は、デフォルトの制限を超えたスケールが必要な可能性があります。例えば、SSMパラメータストアのデフォルトは毎秒40トランザクション(TPS)です。100 TPSが必要な場合は、申請する必要があります。 - AWSマネジメントコンソールで Service Quotas コンソールを開きます。- サービス(例:「EC2」)と特定のクォータ(例:「DescribeInstances rate」)を検索します。- クォータを選択し、「クォータの引き上げをリクエスト」をクリックします。## 確認方法修正が機能しているかどうかを推測で判断しないでください。すべてのBoto3レスポンスには、バックグラウンドで何が起こったかを正確に示す
ResponseMetadataが含まれています。
import boto3
from botocore.config import Config
config = Config(retries={'max_attempts': 5})
ssm = boto3.client('ssm', config=config)
response = ssm.get_parameter(Name="MyConfig")
# リトライ履歴を確認する
retries = response['ResponseMetadata'].get('RetryAttempts', 0)
print(f"成功しました!リトライ回数は {retries} 回でした。")
もし RetryAttempts が常に高い値(例:5回中4回など)を示す場合、現在のバックオフ戦略に対してスループットが高すぎます。

