🗒️

Azure FunctionsとAzure Durable Functionsの違い

2025/01/29に公開

掲題について、GPT-o1さんに質問した回答です。


Azure FunctionsとAzure Durable Functionsの違い

Azure Functions はイベント駆動型のサーバーレスプラットフォームで、HTTP や Queue、Timer、Event Grid など様々なトリガーに応じてコードを実行するための仕組みです。一方で Azure Durable Functions は Azure Functions 上で複数の関数のオーケストレーションステートフルなワークフローを構築するための拡張機能です。両者の主な違いは以下の通りです。

1. ステートフルなオーケストレーションの有無

  • 普通の Azure Functions:

    • 基本的にはステートレスで、関数の実行が終了すると状態は失われます。
    • ワークフロー構築をするなら、外部のリソース(Storage, DB, もしくは Logic Apps など)を使って、関数間の連携を独自に管理する必要があります。
  • Durable Functions:

    • 関数の実行状態や中間結果を自動的に管理しながら、一連のワークフローとして複数の Functions をオーケストレーションできる。
    • Orchestrator 関数 がワークフローの実行順序や条件分岐を記述し、Activity 関数 が実際の処理ロジックを担当。
    • 状態管理は内部で Azure Storage を用いて自動化されるため、開発者が明示的に永続化や再開ロジックを記述しなくても「再試行」「中断と再開」が可能になる。

2. 関数間の呼び出しパターン

  • 普通の Functions:

    • 関数を連携させる場合、HTTPリクエストキューイベントなど外部メッセージを介して呼び出す必要がある。
    • チェーン状に順番に呼び出すには、次の関数を呼ぶロジックを自前で書き、状態管理をする必要がある。
  • Durable Functions:

    • Orchestrator 関数await context.CallActivityAsync(...) のようにシンプルなコードで同期的な書き方を行いつつ、実際にはノンブロッキングでActivity関数を実行・再開する。
    • これにより「関数A → 関数B → 関数C」といったチェーンや、Parallel Execution、Fan-out/Fan-inなどを手軽に実装できる。

3. 長時間の実行と自動再試行

  • 普通の Functions:

    • タイムアウト設定があり、長時間実行(数十分〜数時間以上)は現実的でない。
    • リトライを実装するにはFunction Retries設定か、外部にロジックを組んで管理する必要がある。
  • Durable Functions:

    • ワークフロー自体は短いタスク(=Activity 関数)に分割し、オーケストレーター関数が状態を保持しながら必要なときに再起動する仕組みがある。
    • 設定により自動リトライも簡単に実装でき、長時間のワークフロー(例えば24時間以上の待機)にも対応可能。

4. 実行パターンの違い

  • 普通の Functions でのパターン例:

    • イベントドリブン(HTTP / Timer / Queue / Eventなど)で単発実行。
    • 状態管理をしないシンプルなバッチ処理やWeb APIに向いている。
  • Durable Functions でのパターン例:

    • Function Chaining: Step1 → Step2 → Step3 … と順番に実行。
    • Fan-out/Fan-in: 大量のタスクを並列実行し、その完了を待って次の処理へ。
    • Async HTTP APIs: リクエスト受け取り → 長時間処理 → 後で結果を返す。
    • Aggregator (Monitor): 特定のイベントを監視し、必要に応じてリトライ・再開する。

5. 注意点

  • Durable Functions のOrchestrator 関数内では、書き方に制約がある(I/Oや乱数を直接書くと問題が起きるなど)。
  • 普通の Functions と比べ、Durable Functions は実装が複雑になりがちだが、その分高度なワークフローや状態管理が可能になる。

まとめ

  • Azure Functions … シンプルな、ステートレスのイベント駆動実行。軽量なマイクロサービスやバックエンド処理に最適。
  • Azure Durable Functions … Azure Functions 上でワークフローのオーケストレーションステートフルなプロセスを扱いたいときに使う。複数のFunctionsを連携し、複雑なシーケンスや並列処理・リトライ・長時間タスクを簡易的に記述できる利点がある。

これが両者の主要な違いとなります。


さらに、チェーンとファンアウト/ファンインのパターンが多いかなと思ったので、実装の違いを聞いてみました。


チェーンとファンアウト/ファンインの実装上の違い

Durable Functions で「Function Chaining」と「Fan-out/Fan-in」を実装する場合、オーケストレーター関数(Orchestrator Function)内での関数呼び出しの方法に違いがあります。
以下、Python版Durable Functionsの例を中心に、両者の実装パターンを比較します。

1. Chaining (チェーン)

1-1. 特徴

  • 順番に直列実行するワークフロー。
    • 例: Step A → Step B → Step C と順番に呼び出し、BはA完了後、CはB完了後に実行される。
  • 1つの結果が次のステップの入力になる構造をシンプルに記述可能。

1-2. 実装例 (Python)

# Sample Orchestrator (Chaining)

import azure.functions as func
import azure.durable_functions as df

def orchestrator_function(context: df.DurableOrchestrationContext):
    # 1) Call Activity A
    resultA = yield context.call_activity("ActivityA", "input A")

    # 2) Call Activity B, using A's result
    resultB = yield context.call_activity("ActivityB", resultA)

    # 3) Call Activity C, using B's result
    resultC = yield context.call_activity("ActivityC", resultB)

    return resultC

main = df.Orchestrator.create(orchestrator_function)

ポイント

  1. Stepごとに順番にyield context.call_activity(...) しており、直列実行が行われる。
  2. 次のステップは前のステップの完了を待って実行されるので、「A→B→C」が明確に保証される。

2. Fan-out/Fan-in

2-1. 特徴

  • 同時並列に複数のActivityを呼び出して実行し、全タスクが完了したら結果を集約(Fan-in)する。
  • 大量の並列処理や分割して並列化したいユースケースで使われる(例: バックアップ、画像変換、データ処理など)。

2-2. 実装例 (Python)

# Sample Orchestrator (Fan-out/Fan-in)

import azure.functions as func
import azure.durable_functions as df

def orchestrator_function(context: df.DurableOrchestrationContext):
    # 1) Fan-out: start multiple tasks in parallel
    parallel_tasks = []
    for i in range(10):
        parallel_tasks.append(context.call_activity("ActivityX", f"input {i}"))

    # 2) Wait for all tasks to finish
    results = yield context.task_all(parallel_tasks)

    # 3) Do some aggregation if needed (Fan-in)
    #    For example, combine results or sum them, etc.
    aggregated_result = sum(results)  # as an example

    return aggregated_result

main = df.Orchestrator.create(orchestrator_function)

ポイント

  1. 複数の context.call_activity(...) をリストに追加parallel_tasks
  2. yield context.task_all(...)全タスクの完了を待機し、すべて揃ったら結果を受け取りFan-inの処理をする。
  3. 各タスクは同時並列で実行されるため、時間が大きく短縮できる可能性がある。

3. 実装上の主な違い

  1. 関数呼び出し順序

    • Chaining: A → B → C という直線的なフロー。
    • Fan-out/Fan-in: 同時並列で複数の関数を呼び出してから、一括して結果を取得するフロー。
  2. コードの書き方

    • Chaining: yield context.call_activity(...) をステップごとに記述する。順番通りに並べる。
    • Fan-out/Fan-in: parallel_tasks = [ ... ] をリストで作成 → results = yield context.task_all(parallel_tasks) で一度に待機・回収。
  3. 戻り値の扱い

    • Chaining: 前ステップの戻り値を次ステップに引き継げる。
    • Fan-out: 全タスクの結果をリストや辞書などでまとめて受け取る。必要があれば集計・マージする。
  4. 同期/非同期

    • Chaining: 1ステップ完了までは次に進まない(同期的に並べる)
    • Fan-out: Activity 関数を並列に起動し、それらが完了するまで非同期に待ち、最後にまとめる。

4. Azure Docs リンクでの例

4-1. Chaining の例

4-2. Fan-out/Fan-in の例

まとめ

  • チェーン(chaining): 直列ステップを順番通り呼び出す。1つのActivity完了→次のActivity開始 というシンプルなフロー。
  • ファンアウト/ファンイン(fan-out/fan-in): 複数のActivityを並列実行し、すべて完了したら結果を一括集約する。
  • 実装の違いは、オーケストレーター関数内で**call_activityを直列で呼ぶか、リストに格納して task_all で待機するか**という点に集約されます。

いずれもDurable Functions のオーケストレーションパターンであり、状況に合わせて使い分けるのがポイントです。


再試行処理も違うとのことだったので、何が違うのか聞きました。


通常のFunctionsとDurable Functionsのエラーハンドリングの違い

Durable Functions における「再試行 (リトライ)」と、通常の Azure Functions が持つ「再試行」とでは、仕組みや設定可能な範囲が異なります。
以下に、両者の違い・Durable Functions 特有の再試行設定についてまとめます。

1. 通常の Functions の再試行

  • トリガー種別による制限
    • Queue Trigger の場合、メッセージを削除しないと再度取得されるため、結果的に再試行が起きる。
    • Timer Trigger の場合、指定時刻に繰り返し実行されるが、単発処理で例外が起きても自動再試行は無い。
    • Event Grid / Service Bus Queue なども固有の再試行仕組みがある。
  • host.json による設定
    • Functions v2 以降、host.json で「maxDequeueCount」(Queue) や「maxDeliveryCount」(Service Bus) を指定して一定回数失敗したらポイズンキューへ送るなどの動きが可能。
  • 全般
    • ステートレスのため、呼び出しのたびにイベント(トリガー)を受け、失敗すればトリガー側のリトライで再試行される仕組みが多い。
    • コードから細かく「リトライ間隔を制御」する場合は、外部リソース(Queue, Database)などを使った状態管理を自前で実装する必要がある。

2. Durable Functions の再試行

Durable Functions では、オーケストレーションの仕組みを通じてActivity 関数の自動再試行リカバリ処理を簡単に組み込むことができます。

2-1. Activity 関数の自動再試行

例: Python の場合、call_activity_with_retry メソッドを使います。

import azure.durable_functions as df
import datetime

def orchestrator_function(context: df.DurableOrchestrationContext):
    retry_options = df.RetryOptions(
        first_retry_interval_in_milliseconds=5000,
        max_number_of_attempts=3
    )
    retry_options.backoff_coefficient = 1.5

    # Activity 関数を再試行設定付きで呼び出し
    result = yield context.call_activity_with_retry(
        "MyActivity",
        retry_options,
        input_data
    )
    return result

ポイント

  1. RetryOptions で最大試行回数や初回リトライ間隔、バックオフ係数などを細かく指定可能。
  2. Activity 関数が失敗 (例外スロー) すると、オーケストレーターが自動的に状態管理し、設定に従って再度呼び出す。
  3. これにより**ビジネスロジック上の「指定回数失敗でエラー」や「指数バックオフ」**を柔軟に実装できる。

2-2. Orchestrator 関数のステートフル動作

  • 失敗後の状態保存
    • Activity が失敗しても Orchestrator の状態はストレージに保存され、再実行時に失敗の直前からやり直せる。
  • 他のパターンのエラー処理
    • チェインの途中で失敗した場合、どこから再開するかなどをオーケストレーター関数のロジックで制御可能。
    • call_sub_orchestrator_with_retry などを使ってサブオーケストレーションも再試行できる。

3. 違いのまとめ

特徴 通常の Azure Functions Durable Functions
状態管理 ステートレス(コード終了後に状態は消える) Orchestratorがステートフルに管理し、中断/再開/再試行を自動サポート
リトライ設定 host.jsonやトリガー依存 (Queue, Service Bus, etc.)、外部リソース頼み call_activity_with_retry などを利用し、コードで細かく設定できる
リトライ対象 基本的にトリガー単位 Activity 関数ごとに個別設定可
バックオフ係数/間隔制御 トリガー側に簡易設定はあるが制限が多い RetryOptions初回待機時間・最大回数・バックオフ係数など柔軟に設定
長時間の処理 不向き(Functionタイムアウト等) 長時間ワークフローでも中断再開でき、リトライも含め柔軟な制御可能

Durable FunctionsActivity 関数単位の細かい再試行制御やワークフローのステートフル復元ができるため、高度な再試行ロジックを簡単に実装できる点で普通の Functions と異なります。

4. 参考リンク

以上が、Durable Functions における再試行と通常のAzure Functionsでの再試行の違いです。


最後に、コストの差があるか聞いてみました。


通常のFunctionsとDurable Functionsのコスト差

Azure FunctionsDurable Functions はいずれも同じ「Azure Functions プラットフォーム」の上で動くため、従量課金モデルプラン(Consumption/Premium/Dedicatedなど)自体は共通です。ただし、Durable Functions特有のオーケストレーション処理により、以下の点でコストに差が生じる可能性があります。


1. 基本的な課金モデルは同じ

1-1. 消費プラン (Consumption Plan)

  • 実行時間 + 実行回数(リクエスト数) + Azure Storage トランザクションなどを含む形で従量課金。
  • メモリ消費実行時間 (GB・秒) での計算や、無料枠がある点などは通常のAzure Functionsと変わらない。

1-2. プレミアムプラン / 専用プラン

  • 常時稼働するコンテナリソースを確保するため月額の固定費(割り当てvCPU数 + メモリ)、および実行回数などが加わる。
  • こちらもDurable/通常Functionsの区別なく同一のプラン体系。

2. Durable Functions で追加されるコスト要素

  1. オーケストレーションの管理 (Storage / Queue)

    • Durable Functionsは内部でAzure Storageを使い、「オーケストレーター関数の状態」「Activity関数の待ち行列」「ヒストリーイベント」などを保存します。
    • このため、ストレージトランザクション(読み書き回数)やストレージ容量が、通常のFunctionsより増加する可能性があります。
    • 特に長時間稼働するワークフローやステップ数が多いオーケストレーションほど、ストレージの操作回数が増えコストに影響する。
  2. オーケストレーター関数の起動回数

    • Orchestrator 関数自体も再開や中断のたびに“実行”としてカウントされる。
    • 例: Fan-out/Fan-in で大量の Activity 呼び出しを行うと、それに応じてオーケストレーションが複数回動き、実行リクエスト数が増える。
  3. Activity 関数の件数

    • 通常のFunctionsではイベントが来るごとに1回の関数実行だが、Durable Functionsでは1つのジョブ(オーケストレーション) の中でも多くのActivity関数を呼び出す場合がある。
    • その分だけFunction実行回数が増え、微増のコストとなる可能性がある。

3. コストへの影響のまとめ

  1. 従来の Functions:

    • 主に “関数が呼び出された回数 × 実行時間 (× メモリ消費量)” での課金。
    • ストレージはトリガーに依存(Queue/EventGridなど)する場合には多少の追加コストあり。
  2. Durable Functions:

    • オーケストレータ関数Activity関数の両方で実行時間・実行回数が加算される。
    • さらにオーケストレーションのヒストリ管理やステータス更新で Azure Storage のトランザクションが発生。
    • 長時間のワークフローや大量ステップの並列実行がある場合、通常のFunctionsよりもトランザクションが増えるため、総コストがやや高くなる可能性

4. 実際の運用での考え方

  • シンプルなワークフローや短時間処理なら、通常のFunctionsと大きく変わらないか、僅差程度。
  • ステップ数が多い(大量Fan-out) or 長期実行で、ヒストリー記録や再開回数が増えるとストレージ操作が多くなり、コスト差が顕在化する。
  • ただし、Durable Functionsを使わずに「自前でワークフロー管理」しても、やはりQueueやDB、あるいは別のツールで同様のトランザクションが発生するため、Durable Functionsのオーケストレーションコストが“追加”というより “隠しコストを表面化”しているとも言える。

5. 参考ドキュメント


まとめ

  • 課金プラン自体は同じ (Consumption/Premiumなど) で、Durable Functions専用の追加月額費は無い。
  • ただし、オーケストレーション管理のためにAzure Storageトランザクションや実行回数が増えるので、普通のFunctionsに比べ若干コストが上乗せされることがある。
  • 特に長時間・多ステップ・大量並列処理のシナリオだと、Durable Functionsはストレージ書き込み + Activity呼び出しが多くなりやすく、結果としてコスト増に繋がる点に留意が必要です。

以上、Durable Functionsについてのメモでした。

ヘッドウォータース

Discussion