エラーの概要

429 Too Many Requests エラーは、Azure APIスロットリング制限に達したことを示す HTTP ステータスコードです。Azure では、各サブスクリプションAPI に対して一定期間内のリクエスト数に上限を設定しており、この制限を超えたときに発生します。特に、自動化スクリプトバッチ処理ループ内から大量のリクエスト送信する場合に頻繁に見られます。

実際のエラーメッセージ例

Azure REST API の直接呼び出しで見られる典型的なレスポンス

{
  "error": {
    "code": "SubscriptionThrottled",
    "message": "The subscription is throttled for the following operation: Microsoft.Compute/virtualMachines/write. Please try after 30 seconds."
  }
}

Azure SDK(Python)で発生した場合のコンソール出力:

azure.core.exceptions.HttpResponseError: (429) Throttling error. Subscription has exceeded throttling limits for operation 'Microsoft.Storage/storageAccounts/write'. Retry after 60 seconds.

よくある原因と解決手順

原因1:リクエストレートが上限を超えている

Azure には、API ごと・操作ごと(例:仮想マシン作成、ストレージ読み書き)に一定秒あたりのリクエスト数制限があります。制限値はサブスクリプション、リージョン、リソースの種類によって異なり、ループ内で連続して API を呼び出すと瞬時に制限に達します。

Before(エラーが起きるコード):

from azure.identity import DefaultAzureCredential
from azure.mgmt.compute import ComputeManagementClient

credential = DefaultAzureCredential()
client = ComputeManagementClient(credential, "<subscription_id>")

# 50 台の VM を一気に作成しようとする
for i in range(50):
    client.virtual_machines.begin_create_or_update(
        "<resource_group>",
        f"vm-{i}",
        vm_config
    )

After(修正後):

import time
from azure.identity import DefaultAzureCredential
from azure.mgmt.compute import ComputeManagementClient

credential = DefaultAzureCredential()
client = ComputeManagementClient(credential, "<subscription_id>")

# リクエスト間に遅延を挿入
for i in range(50):
    try:
        client.virtual_machines.begin_create_or_update(
            "<resource_group>",
            f"vm-{i}",
            vm_config
        )
    except Exception as e:
        if "429" in str(e) or "throttled" in str(e).lower():
            # Retry-After ヘッダーから推奨待機時間を取得
            retry_after = 60
            print(f"スロットリング検出。{retry_after} 秒待機します")
            time.sleep(retry_after)
            # リトライ処理を含める
            client.virtual_machines.begin_create_or_update(
                "<resource_group>",
                f"vm-{i}",
                vm_config
            )
        else:
            raise
    
    # 各リクエスト間に 2 秒の遅延を設定
    time.sleep(2)

原因2:Retry-After ヘッダーを無視している

Azure API が 429 を返す際、必ず Retry-After レスポンスヘッダーに推奨される再試行待機時間を含めます。このヘッダーを無視して即座にリトライすると、さらにスロットリングが重くなります。

Before(エラーが起きるコード):

const { ComputeManagementClient } = require("@azure/arm-compute");
const { DefaultAzureCredential } = require("@azure/identity");

async function createVMs() {
    const credential = new DefaultAzureCredential();
    const client = new ComputeManagementClient(credential, "<subscription_id>");
    
    // Retry-After を無視した単純なリトライ
    let retries = 0;
    while (retries < 5) {
        try {
            await client.virtualMachines.beginCreateOrUpdateAndWait(
                "<resource_group>",
                "vm-1",
                vmConfig
            );
            break;
        } catch (err) {
            retries++;
            // 固定時間でリトライ(推奨時間を無視)
            await new Promise(r => setTimeout(r, 1000));
        }
    }
}

After(修正後):

const { ComputeManagementClient } = require("@azure/arm-compute");
const { DefaultAzureCredential } = require("@azure/identity");

async function createVMs() {
    const credential = new DefaultAzureCredential();
    const client = new ComputeManagementClient(credential, "<subscription_id>");
    
    let retries = 0;
    while (retries < 5) {
        try {
            await client.virtualMachines.beginCreateOrUpdateAndWait(
                "<resource_group>",
                "vm-1",
                vmConfig
            );
            break;
        } catch (err) {
            // Retry-After ヘッダーから待機時間を取得
            let retryAfter = 60;
            if (err.response && err.response.headers) {
                const headerValue = err.response.headers["retry-after"];
                if (headerValue) {
                    retryAfter = parseInt(headerValue, 10);
                }
            }
            
            console.log(`429 エラー。${retryAfter} 秒後に再試行します`);
            await new Promise(r => setTimeout(r, retryAfter * 1000));
            retries++;
        }
    }
}

原因3:複数の操作を同時実行している

Azure 関数、Logic Apps、Data Factory など、複数の処理が並列実行される環境では、複合的なスロットリングが発生しやすくなります。特にマネージドサービスでの自動スケーリング時に、大量のワーカーが同時に同じ API を呼び出すと瞬時に制限に達します。

Before(エラーが起きるコード):

import asyncio
from azure.identity import DefaultAzureCredential
from azure.mgmt.storage import StorageManagementClient

async def delete_storage_accounts():
    credential = DefaultAzureCredential()
    client = StorageManagementClient(credential, "<subscription_id>")
    
    # 20 個のストレージアカウントを同時削除
    tasks = []
    for account_name in storage_accounts:
        task = asyncio.create_task(
            asyncio.to_thread(
                client.storage_accounts.delete,
                "<resource_group>",
                account_name
            )
        )
        tasks.append(task)
    
    # すべてのタスクを同時実行
    await asyncio.gather(*tasks)

After(修正後):

import asyncio
from azure.identity import DefaultAzureCredential
from azure.mgmt.storage import StorageManagementClient

async def delete_storage_accounts():
    credential = DefaultAzureCredential()
    client = StorageManagementClient(credential, "<subscription_id>")
    
    # 同時実行数を制限(並行数 3)
    semaphore = asyncio.Semaphore(3)
    
    async def delete_with_limit(account_name):
        async with semaphore:
            try:
                await asyncio.to_thread(
                    client.storage_accounts.delete,
                    "<resource_group>",
                    account_name
                )
            except Exception as e:
                if "429" in str(e):
                    print(f"スロットリング。30 秒待機してからリトライします")
                    await asyncio.sleep(30)
                    await asyncio.to_thread(
                        client.storage_accounts.delete,
                        "<resource_group>",
                        account_name
                    )
                else:
                    raise
    
    tasks = [delete_with_limit(acc) for acc in storage_accounts]
    await asyncio.gather(*tasks)

ツール固有の注意点

Azure ストレージアカウントのスロットリング制限

ストレージアカウントには、BLOB、Table、Queue などのサービスごとに独立した制限があります。標準アカウントのスケーラビリティ目標は、単一アカウントあたり秒間 20,000 リクエスト程度ですが、特定の操作(例:PutBlock)はさらに低い制限を持ちます。大規模なアップロードダウンロード時は、複数ストレージアカウントに分散させるか、Azure Data Lake Storage Gen2 への移行を検討してください。

Azure App Service・Function App での 429

Azure Function App でバージョン 4 ランタイムを使用している場合、デフォルトの HTTP 接続数制限(http.connectionLimit)により、外部 API へのアウトバウンド呼び出しがスロットルされることがあります。このとき、Azure の REST API ではなく、呼び出し先の外部サービスの 429 が返される可能性も高いため、エラーメッセージmicrosoft.com を含むか確認し、実際にどのサービスが制限を返しているかを特定してください。

Azure DevOps の API レート制限

Azure DevOps(旧 VSTS)で Pipelines、Work Items、Repos API を大量に呼び出す場合、認証方式によって制限が異なります。PAT(Personal Access Token)では秒間 200 リクエストアプリケーション認証では秒間 6,000 リクエストが目安です。CI/CD で多数のジョブを並列実行する際は、リトライロジックと指数バックオフの実装が必須です。

Azure Resource Graph のクエリ制限

Resource Graph では、複雑な KQL クエリや大規模なサブスクリプション横断検索の際に 429 が発生しやすくなります。クエリの複雑性スコアを $skip トークンで段階的に削減し、バッチサイズを最大 1,000 件に制限してください。

それでも解決しない場合

ログとデバッグ情報の確認

Azure CLI で診断ログを有効化:

az monitor diagnostic-settings create \
  --name <setting_name> \
  --resource <resource_id> \
  --logs '[{"category":"ServiceFabricSystemEventTable","enabled":true}]' \
  --workspace <workspace_id>

Python SDK でデバッグレベルのログを有効化し、リクエスト・レスポンスヘッダーを確認:

import logging
logging.basicConfig(level=logging.DEBUG)

# Azure SDK ログを有効化
azure_logger = logging.getLogger("azure")
azure_logger.setLevel(logging.DEBUG)

公式リファレンスの確認

以下のドキュメントで、各 Azure サービスの具体的なスロットリング制限値を確認してください:

コミュニティサポート

Azure SDKリトライ実装に関する既知の問題や解決方法は、公式 GitHub リポジトリで確認できます:

問題が特定の SDK バージョンやサービスに限定される場合は、リージョンの状態ページ(https://status.azure.com)も確認し、Azure 側のインシデント有無を確認してください。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。