host.jsonのfunctionTimeoutを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])
つまり、次のような状態が起こり得ます。
- クライアントがHTTPリクエストを送信する
- Azure Functionsが長時間処理を開始する
- 約230秒経過してもHTTP応答が返らない
- クライアント側の接続が切れる
- 関数実行自体は、プランや実行状態によってはその後も続いている
HTTP接続が切れた事実だけでは、関数実行も同時に停止したとは判断できません。クライアント側のエラー時刻と、関数側の開始・終了ログを分けて確認する必要があります。
ホスティングプランごとのfunctionTimeout上限
現在のAzure Functionsでは、主なプランの既定値と最大値は次のように整理できます。
| ホスティングプラン | 既定値 | 最大値 | 主な注意点 |
|---|---|---|---|
| Consumption | 5分 | 10分 | 10分を超える実行には利用できない |
| Flex Consumption | 30分 | 上限なし | スケールイン時などは猶予時間の影響を受ける |
| Premium | 30分 | 上限なし | 長時間実行は可能だがHTTP応答は約230秒まで |
| Dedicated | 30分 | 上限なし | 上限なしで使うには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構成が適しています。
- クライアントが処理開始APIを呼ぶ
- APIは処理を登録する
- APIはすぐに
202 Acceptedを返す - 応答にはジョブIDと状態確認先を含める
- クライアントは状態確認先を定期的に呼ぶ
- 完了後に結果を取得する
この方式であれば、処理全体が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]HttpRequestDataHttpResponseDataDurableTaskClientScheduleNewOrchestrationInstanceAsyncCreateCheckStatusResponse
CreateCheckStatusResponse関連のAPIは、.NET isolated workerのHTTPトリガーではHttpRequestDataとHttpResponseDataを対象としており、ASP.NET Coreで定義されたHTTP型には対応していません。IActionResultやHttpRequestを混在させないようにします。([Microsoft Learn][3])
コードは利用中のFunctionsランタイム、Durable Functions拡張機能、NuGetパッケージのバージョンに合わせてビルドと動作確認が必要です。
状態確認URLは秘密情報として扱う
Durable Functionsが返す管理URLには、環境によってcodeなどの認証情報がクエリ文字列として含まれます。
次の場所へ無加工で出力しないようにします。
- 公開ログ
- エラーメッセージ
- 問い合わせ画面
- スクリーンショット
- チャットやメール
- アクセス解析ツール
ログに残す場合は、インスタンスIDだけを記録し、URLのクエリ文字列は除去します。
Durable Functionsを使わない場合はキューへ処理を渡す
処理が単純で、複雑な分岐や状態管理が不要なら、Azure Storage QueueやService Busを使う方法もあります。
構成例は次のとおりです。
- HTTPトリガーが入力内容を検証する
- ジョブIDを発行する
- メッセージをキューへ登録する
- HTTP 202とジョブIDを返す
- Queueトリガー関数が実処理を行う
- 結果をTable Storage、Cosmos DB、SQL Databaseなどへ保存する
- 状態確認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”

コメント