Azure FunctionsでfunctionTimeoutを延長しても約230秒でHTTP応答が切れる原因と対処

host.jsonfunctionTimeoutを30分や-1に変更しても、HTTPトリガーの応答が約230秒で切れることがあります。原因は、関数の実行時間上限と、HTTP接続が応答を待てる時間が別の制限だからです。

functionTimeoutで延長できるのは関数の実行時間です。一方、HTTPトリガーにはAzure Load Balancerに由来する約230秒の応答制限があります。そのため、230秒を超える可能性がある処理は、1回のHTTP接続を待たせ続けるのではなく、Durable Functionsなどを使って「処理開始」と「状態確認」を分離するのが基本です。([Microsoft Learn][1])

目次

Azure Functionsでタイムアウトを延ばしてもHTTP応答が切れる理由

Azure Functionsのタイムアウトを調査するときは、少なくとも次の3層を分けて考える必要があります。

制限の層制御しているもの主な症状基本的な対処
functionTimeout関数コードの実行時間実行がタイムアウトし、ワーカープロセスが再起動するプランとhost.jsonを確認する
Azure側のHTTP応答制限HTTPトリガーがクライアントへ応答するまでの時間約230秒前後で接続が切れる202応答を返す非同期方式に変更する
クライアントや中継サービス呼び出し元が応答を待つ時間230秒より短い時間でタイムアウトするSDK、プロキシ、ゲートウェイの設定を確認する

functionTimeoutは関数の実行時間を制御する

functionTimeoutは、トリガーによって開始された関数実行が完了するまでの上限です。設定時間を超えるとタイムアウトエラーになり、通常は言語ワーカープロセスが再起動されます。

たとえば次の設定は、関数の実行時間を10分に設定します。

{
  "version": "2.0",
  "functionTimeout": "00:10:00"
}

ただし、実際に指定できる最大時間はホスティングプランによって異なります。また、この値を延ばしてもHTTP接続の約230秒制限は解除されません。([Microsoft Learn][2])

HTTPトリガーには約230秒の応答制限がある

Microsoft Learnでは、functionTimeoutの設定にかかわらず、HTTPトリガーがリクエストへ応答できる最大時間は230秒とされています。これはAzure Load Balancerの既定のアイドルタイムアウトに由来する制限です。([Microsoft Learn][1])

つまり、次のような状態が起こり得ます。

  1. クライアントがHTTPリクエストを送信する
  2. Azure Functionsが長時間処理を開始する
  3. 約230秒経過してもHTTP応答が返らない
  4. クライアント側の接続が切れる
  5. 関数実行自体は、プランや実行状態によってはその後も続いている

HTTP接続が切れた事実だけでは、関数実行も同時に停止したとは判断できません。クライアント側のエラー時刻と、関数側の開始・終了ログを分けて確認する必要があります。

ホスティングプランごとのfunctionTimeout上限

現在のAzure Functionsでは、主なプランの既定値と最大値は次のように整理できます。

ホスティングプラン既定値最大値主な注意点
Consumption5分10分10分を超える実行には利用できない
Flex Consumption30分上限なしスケールイン時などは猶予時間の影響を受ける
Premium30分上限なし長時間実行は可能だがHTTP応答は約230秒まで
Dedicated30分上限なし上限なしで使うにはAlways Onが必要

ここでいう「上限なし」は、Azure Functionsが通常の実行時間上限を強制しないという意味です。処理が必ず中断されないという保証ではありません。

Flex ConsumptionとPremiumでは、スケールイン時の実行猶予は60分、プラットフォーム更新時の猶予は10分です。Dedicatedでもプラットフォーム更新時には10分の猶予があります。長時間処理では、再実行や途中再開を前提にした設計が必要です。([Microsoft Learn][1])

functionTimeoutを-1にしてもHTTP制限は消えない

対応プランでは、次のように-1を指定して実行時間を無制限にできます。

{
  "version": "2.0",
  "functionTimeout": "-1"
}

ただし、Microsoft Learnでは、無制限よりも固定の上限を設定することが推奨されています。また、-1にしても約230秒のHTTP応答制限は変わりません。([Microsoft Learn][2])

Consumptionプランで-1を指定しても、プラン自体の最大実行時間である10分を超えられるわけではありません。設定ファイルの値よりも、ホスティングプランの上限が優先されます。

約230秒で切れる原因を切り分ける手順

クライアントと関数の時刻を別々に記録する

まず、次の4時刻を記録します。

  • HTTPリクエストの送信時刻
  • クライアントでエラーになった時刻
  • 関数の処理開始時刻
  • 関数の処理終了時刻

関数には、リクエスト単位の識別子も記録しておきます。

logger.LogInformation(
    "Long-running job started. RequestId={RequestId}, StartedAt={StartedAt}",
    requestId,
    DateTimeOffset.UtcNow);

終了時にも同じRequestIdを出力すれば、クライアントの接続が切れた後に処理が継続したかを確認できます。

利用中のホスティングプランを確認する

Function Appの概要やプロパティから、少なくとも次の情報を確認します。

  • Consumption、Flex Consumption、Premium、Dedicatedのどれか
  • WindowsかLinuxか
  • Functionsランタイムの世代
  • Dedicatedの場合はAlways Onの状態
  • 実際にデプロイされているhost.json

ローカルのhost.jsonだけを変更し、変更後のファイルがAzureへデプロイされていないケースにも注意が必要です。

発生時刻から原因を判断する

観測結果疑うべき原因
クライアントだけが約230秒で切れ、関数はその後完了しているAzure側のHTTP応答制限
Consumptionで約5分または設定した時間に関数実行も終了するfunctionTimeout
Consumptionで10分を超える設定が有効にならないプランの最大実行時間
30秒、60秒、100秒など、230秒より短く切れるクライアント、プロキシ、APIゲートウェイなど
時間が一定せず、外部API呼び出しの直後に失敗する外部依存先、通信エラー、アプリ側例外
ログにタイムアウトがなく、処理途中で終了する再起動、スケール、メモリ不足、アプリ障害など

ブラウザ、HTTPクライアント、API Management、リバースプロキシなどを経由している場合は、それぞれに独自のタイムアウトが設定されている可能性があります。調査時は、呼び出し経路を一つずつ減らし、Function Appへ直接送った場合と比較します。

230秒を超える処理は非同期APIに変更する

長時間処理では、次のAPI構成が適しています。

  1. クライアントが処理開始APIを呼ぶ
  2. APIは処理を登録する
  3. APIはすぐに202 Acceptedを返す
  4. 応答にはジョブIDと状態確認先を含める
  5. クライアントは状態確認先を定期的に呼ぶ
  6. 完了後に結果を取得する

この方式であれば、処理全体が10分や1時間かかっても、最初のHTTP接続を維持し続ける必要がありません。

Durable Functionsの非同期HTTPパターン

Durable Functionsには、長時間処理を開始し、その状態確認先を返す仕組みが用意されています。

処理開始時の応答は、おおむね次の形になります。

HTTP/1.1 202 Accepted
Location: https://example/.../instances/{instanceId}?code=<redacted>
Retry-After: 10

クライアントはLocationに指定されたURLをポーリングします。処理中はHTTP 202、オーケストレーションが完了または失敗するとHTTP 200が返るという流れです。([Microsoft Learn][3])

CreateCheckStatusResponseを使うと、状態確認、終了、外部イベント送信などに利用する管理URLを含む応答を生成できます。

.NET isolated workerで開始要求を返すコード例

次は、.NET isolated workerモデルに統一した最小構成例です。

[Function(nameof(StartLongRunningJob))]
public static async Task<HttpResponseData> StartLongRunningJob(
    [HttpTrigger(AuthorizationLevel.Function, "post")]
    HttpRequestData request,
    [DurableClient]
    DurableTaskClient client)
{
    const string orchestratorName = "LongRunningOrchestration";

    string instanceId =
        await client.ScheduleNewOrchestrationInstanceAsync(
            orchestratorName);

    return client.CreateCheckStatusResponse(
        request,
        instanceId);
}

このHTTPトリガーが行うのは、オーケストレーションの登録と状態確認先の返却だけです。実際の長時間処理は、別途作成したLongRunningOrchestrationとActivity Functionで実行します。

この例では、次のAPIを同じ実行モデルでそろえています。

  • [Function]
  • HttpRequestData
  • HttpResponseData
  • DurableTaskClient
  • ScheduleNewOrchestrationInstanceAsync
  • CreateCheckStatusResponse

CreateCheckStatusResponse関連のAPIは、.NET isolated workerのHTTPトリガーではHttpRequestDataHttpResponseDataを対象としており、ASP.NET Coreで定義されたHTTP型には対応していません。IActionResultHttpRequestを混在させないようにします。([Microsoft Learn][3])

コードは利用中のFunctionsランタイム、Durable Functions拡張機能、NuGetパッケージのバージョンに合わせてビルドと動作確認が必要です。

状態確認URLは秘密情報として扱う

Durable Functionsが返す管理URLには、環境によってcodeなどの認証情報がクエリ文字列として含まれます。

次の場所へ無加工で出力しないようにします。

  • 公開ログ
  • エラーメッセージ
  • 問い合わせ画面
  • スクリーンショット
  • チャットやメール
  • アクセス解析ツール

ログに残す場合は、インスタンスIDだけを記録し、URLのクエリ文字列は除去します。

Durable Functionsを使わない場合はキューへ処理を渡す

処理が単純で、複雑な分岐や状態管理が不要なら、Azure Storage QueueやService Busを使う方法もあります。

構成例は次のとおりです。

  1. HTTPトリガーが入力内容を検証する
  2. ジョブIDを発行する
  3. メッセージをキューへ登録する
  4. HTTP 202とジョブIDを返す
  5. Queueトリガー関数が実処理を行う
  6. 結果をTable Storage、Cosmos DB、SQL Databaseなどへ保存する
  7. 状態確認APIが処理状況と結果を返す

Microsoft Learnでも、HTTPトリガーからキューへ処理を渡し、すぐに応答する方法が長時間処理の対策として案内されています。([Microsoft Learn][4])

Task.Runでバックグラウンド処理を始める方法は避ける

次のような実装は、安全な非同期処理にはなりません。

_ = Task.Run(() => ExecuteLongRunningJob());
return response;

関数がHTTP応答を返すと、Azure Functionsランタイムはその関数実行を完了したものとして扱います。ランタイムは返却後のバックグラウンドスレッドを追跡しないため、サイトの停止や再起動によって処理が途中で失われる可能性があります。([Microsoft Learn][4])

Durable Functions、Queueトリガー、Service Busなど、処理状態を外部に保存できる仕組みへ渡してください。

よくある誤解と正しい対処

誤解実際の動作正しい対処
functionTimeoutを30分にすればHTTPも30分待てるHTTP応答は約230秒で切れる202と状態確認URLを返す
Premiumへ変更すればHTTP制限もなくなる実行時間上限は緩和されるがHTTP制限は残る非同期APIへ変更する
functionTimeout: -1なら絶対に中断されないスケールや更新による中断可能性は残る再実行可能な設計にする
HTTP応答後にTask.Runで処理すればよいバックグラウンド処理は保証されないDurable Functionsかキューを使う
タイムアウト時に同じ要求を再送すればよい二重登録や二重更新が起こり得るジョブIDや冪等キーを使う
230秒以内なら常に同期方式でよい処理時間のばらつきで上限を超えることがある上限に近い処理は非同期化する

特に、平均処理時間だけで判断しないことが重要です。通常は60秒でも、データ量や外部APIの遅延によって一部の要求だけ230秒を超えるなら、同期APIのままでは断続的な障害になります。

同期処理と非同期処理の判断基準

処理の特徴推奨方式
数秒から数十秒で安定して終了する通常のHTTPトリガー
230秒へ近づく可能性がある非同期API
外部APIを複数回呼び出すDurable Functions
承認待ちや一定時間の待機があるDurable Functions
単純なバッチ処理を後から実行するQueueトリガー
途中経過、再試行、キャンセルが必要Durable Functions
大量ファイル変換や帳票生成Durable Functionsまたはキュー
クライアントが結果を即時に必要とする処理分割、事前計算、キャッシュを検討

同期処理を続ける場合でも、設計上限を229秒に置くのは危険です。コールドスタート、外部サービスの遅延、データ量の増加を考慮し、十分な余裕を確保します。

約230秒で切れるときに実施すべきこと

functionTimeoutを延ばしても約230秒でHTTP応答が切れる場合は、設定ミスではなく、HTTPトリガーの応答制限に達している可能性があります。

まず、クライアントの切断時刻と関数の終了時刻を比較し、関数実行とHTTP接続のどちらが終了したのかを確認します。次に、利用中のホスティングプランとhost.jsonの設定を照合します。

処理が230秒を超える可能性があるなら、functionTimeoutの延長だけで解決しようとせず、Durable FunctionsのCreateCheckStatusResponseやキューを使って、開始要求にHTTP 202と状態確認先を返す構成へ変更してください。
[1]: https://learn.microsoft.com/en-us/azure/azure-functions/functions-scale “Azure Functions Scale and Hosting | Microsoft Learn”
[2]: https://learn.microsoft.com/en-us/azure/azure-functions/functions-host-json “host.json reference for Azure Functions 2.x | Microsoft Learn”
[3]: https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-http-features “HTTP Features in Durable Functions – Azure Functions | Microsoft Learn”
[4]: https://learn.microsoft.com/en-us/azure/azure-functions/performance-reliability “Improve Azure Functions performance and reliability | Microsoft Learn”

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次