次の方法で共有


Durable Functions でのオーケストレーションのバージョン管理 (Azure Functions) - パブリック プレビュー

オーケストレーションのバージョン管理は、Durable Functions に必要な決定論的実行モデルを維持しながら、オーケストレーター関数に変更をデプロイするという 主要な課題 に対処します。 この機能がないと、オーケストレーター ロジックまたはアクティビティ関数シグネチャに重大な変更が加えられると、再生中に実行中のオーケストレーション インスタンスが失敗します。これは、オーケストレーションの実行の信頼性を確保する 決定性の要件 を破るためです。 この組み込み機能は、最小限の構成でバージョンの自動分離を提供します。 バックエンドに依存しないため、Durable Task Scheduler を含む Durable Function のストレージ プロバイダーのいずれかを利用するアプリで使用できます。

Note

Durable Task Scheduler ユーザーの場合、Durable Functions ではなく Durable Task SDK を使用している場合は、 Durable Task SDK のバージョン管理に関する記事を参照してください。

Terminology

この記事では、2 つの関連する異なる用語を使用します。

  • オーケストレーター関数 (または単に "オーケストレーター"): ワークフロー ロジック (ワークフローの実行方法のテンプレートまたはブループリント) を定義する関数コードを参照します。
  • オーケストレーション インスタンス (または単に "オーケストレーション"): オーケストレーター関数の特定の実行中の実行を、独自の状態、インスタンス ID、および入力で参照します。 同じオーケストレーター関数から複数のオーケストレーション インスタンスを同時に実行できます。

この区別を理解することは、オーケストレーター関数コードにバージョン対応のロジックが含まれているオーケストレーションのバージョン管理では非常に重要ですが、オーケストレーション インスタンスは作成時に特定のバージョンに永続的に関連付けられます。

動作方法

オーケストレーションのバージョン管理機能は、次の基本原則に基づいて動作します。

  • バージョンの関連付け: オーケストレーション インスタンスが作成されると、そのインスタンスに永続的に関連付けられたバージョンが取得されます。

  • バージョン対応の実行: Orchestrator 関数コードは、現在のオーケストレーション インスタンスとブランチの実行に関連付けられているバージョン値を適宜調べることができます。

  • 下位互換性: 新しいオーケストレーター バージョンを実行しているワーカーは、古いオーケストレーター バージョンによって作成されたオーケストレーション インスタンスの実行を続行できます。

  • 前方保護: ランタイムは、古いオーケストレーター バージョンを実行しているワーカーが、新しいオーケストレーター バージョンによって開始されたオーケストレーションを自動的に実行できないようにします。

Important

オーケストレーションのバージョン管理は現在、.NET 分離モデルで実行されているアプリのパブリック プレビュー段階にあります。 パッケージ バージョン Microsoft.Azure.Functions.Worker.Extensions.DurableTask>=1.5.0 を使用します。

Basic usage

オーケストレーションのバージョン管理の最も一般的なユース ケースは、既存の実行中のオーケストレーション インスタンスを元のバージョンで実行したまま、オーケストレーター ロジックに破壊的変更を加える必要がある場合です。 必要なのは、defaultVersionhost.jsonを更新し、オーケストレーター コードを変更してオーケストレーションのバージョンとブランチの実行を適宜確認するだけです。 必要な手順を見てみましょう。

Note

このセクションで説明する動作は、最も一般的な状況を対象としており、これが既定の構成で提供されます。 ただし、必要に応じて変更できます (詳細については、「 高度な使用方法 」を参照してください)。

手順 1: defaultVersion の構成

オーケストレーションの既定のバージョンを構成するには、Azure Functions プロジェクトのdefaultVersion ファイルにhost.json設定を追加または更新する必要があります。

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>"
    }
  }
}

バージョン文字列は、バージョン管理戦略に適した任意の形式に従うことができます。

  • マルチパート バージョン管理: "1.0.0""2.1.0"
  • シンプルな番号付け: "1""2"
  • 日付ベース: "2025-01-01"
  • カスタム形式: "v1.0-release"

defaultVersionを設定すると、すべての新しいオーケストレーション インスタンスがそのバージョンに永続的に関連付けられます。

バージョン比較規則

StrictまたはCurrentOrOlder戦略が選択されている場合 (バージョンの一致を参照)、ランタイムは、次の規則を使用してオーケストレーション インスタンスのバージョンと worker のdefaultVersion値を比較します。

  • 空または null のバージョンは等しいとして扱われます。
  • 空または null バージョンは、定義されているバージョンよりも古いと見なされます。
  • 両方のバージョンを System.Versionとして解析できる場合は、 CompareTo メソッドが使用されます。
  • それ以外の場合は、大文字と小文字を区別しない文字列比較が実行されます。

手順 2: オーケストレーター関数ロジック

オーケストレーター関数にバージョン対応ロジックを実装するには、オーケストレーターに渡されるコンテキスト パラメーターを使用して、現在のオーケストレーション インスタンスのバージョンにアクセスできます。これにより、バージョンに基づいてオーケストレーター ロジックを分岐できます。

Important

バージョン対応ロジックを実装する場合、以前のバージョンの正確なオーケストレーター ロジックを保持することが 非常に重要 です。 既存のバージョンのアクティビティ呼び出しのシーケンス、順序、または署名に変更を加えると、確定的な再生が中断され、インフライト オーケストレーションが失敗したり、正しくない結果が生成されたりする可能性があります。 以前のバージョンのコード パスは、デプロイ後も変更しない必要があります。

[Function("MyOrchestrator")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    if (context.Version == "1.0")
    {
        // Original logic for version 1.0
        ...
    }
    else if (context.Version == "2.0")
    {
        // New logic for version 2.0
        ...
    }
    ...
}

Note

context.Version プロパティは読み取り専用で、作成時にオーケストレーション インスタンスに永続的に関連付けられたバージョンを反映します。 オーケストレーションの実行中にこの値を変更することはできません。 host.json以外の方法でバージョンを指定する場合は、オーケストレーション クライアント API を使用してオーケストレーション インスタンスを開始するときに行うことができます (「特定のバージョンでの新しいオーケストレーションとサブオーケストレーションの開始」を参照)。

Tip

オーケストレーションのバージョン管理を使用し始めたばかりで、 defaultVersionを指定する前に作成された実行中のオーケストレーションが既にある場合でも、 defaultVersion 設定を今すぐ host.json に追加できます。 以前に作成したすべてのオーケストレーションについて、 context.Versionnull (または同等の言語依存値) を返します。そのため、オーケストレーター ロジックを構造化して、レガシ (null バージョン) と新しいバージョン管理されたオーケストレーションの両方をそれに応じて処理できます。 C# では、レガシ ケースを処理する context.Version == null または context.Version is null を確認できます。 "defaultVersion": nullhost.jsonを指定することは、まったく指定しないことと同じであることにも注意してください。

Tip

状況によっては、異なるレベルでの分岐が好ましい場合があります。 この例のように、この変更が必要な場所でローカル変更を正確に行うことができます。 または、オーケストレーター実装レベル全体であっても、より高いレベルで分岐することもできます。これにより、コードの重複が発生しますが、実行フローが明確に保たれる可能性があります。 シナリオとコーディング スタイルに最も適したアプローチを選択する必要があります。

デプロイ後の動作

新しいバージョン ロジックを使用して更新されたオーケストレーター関数をデプロイしたら、次のことが予想されます。

  • ワーカーの共存: 新しいオーケストレーター関数コードを含むワーカーが起動しますが、古いコードを持つ一部のワーカーはアクティブである可能性があります。

  • 新しいインスタンスのバージョン割り当て: 新しいワーカーによって作成されたすべての新しいオーケストレーションとサブオーケストレーションは、 defaultVersion からバージョンを取得します。

  • 新しいワーカーの互換性: 新しく作成されたオーケストレーションと以前に既存のオーケストレーションの両方を処理できるようになります。これは、前のセクションの手順 2 で行った変更によって、バージョン対応の分岐ロジックによる下位互換性が確保されるためです。

  • 古いワーカーの制限: 古いワーカーは、オーケストレーター コードが新しいバージョンと互換性があるとは想定されていないため、の独自のdefaultVersionで指定されたバージョンhost.jsonのバージョンのオーケストレーションのみを処理できます。 この制限により、実行エラーや予期しない動作が防止されます。

Note

オーケストレーションのバージョン管理は、ワーカーのライフサイクルに影響しません。 Azure Functions プラットフォームは、ホスティング オプションに応じて、通常のルールに基づいて worker のプロビジョニングと使用停止を管理します。

例: シーケンス内のアクティビティを置き換える

この例では、オーケストレーションのバージョン管理を使用して、シーケンスの途中で 1 つのアクティビティを別のアクティビティに置き換える方法を示します。

Version 1.0

host.json configuration:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "1.0"
    }
  }
}

Orchestrator function:

[Function("ProcessOrderOrchestrator")]
public static async Task<string> ProcessOrder(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var orderId = context.GetInput<string>();
    
    await context.CallActivityAsync("ValidateOrder", orderId);
    await context.CallActivityAsync("ProcessPayment", orderId);
    await context.CallActivityAsync("ShipOrder", orderId);
    
    return "Order processed successfully";
}

バージョン 2.0 と割引処理

host.json configuration:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "2.0"
    }
  }
}

Orchestrator function:

using DurableTask.Core.Settings;

[Function("ProcessOrderOrchestrator")]
public static async Task<string> ProcessOrder(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var orderId = context.GetInput<string>();

    await context.CallActivityAsync("ValidateOrder", orderId);

    if (VersioningSettings.CompareVersions(context.Version, "1.0") <= 0)
    {
        // Preserve original logic for existing instances
        await context.CallActivityAsync("ProcessPayment", orderId);
    }
    else // a higher version (including 2.0)
    {
        // New logic with discount processing (replaces payment processing)
        await context.CallActivityAsync("ApplyDiscount", orderId);
        await context.CallActivityAsync("ProcessPaymentWithDiscount", orderId);
    }
    
    await context.CallActivityAsync("ShipOrder", orderId);

    return "Order processed successfully";
}

Advanced usage

より高度なバージョン管理シナリオでは、ランタイムがバージョンの一致と不一致を処理する方法を制御する他の設定を構成できます。

Tip

ほとんどのシナリオで既定の構成 (CurrentOrOlderReject) を使用して、バージョンの移行中にオーケストレーションの状態を維持しながら、安全なローリング 展開を有効にします。 既定の動作では満たされない特定の要件がある場合にのみ、高度な構成を続行することをお勧めします。

Version matching

versionMatchStrategy設定は、オーケストレーター関数を読み込むときにランタイムがオーケストレーション バージョンと一致する方法を決定します。 バージョンの互換性に基づいてワーカーが処理できるオーケストレーション インスタンスを制御します。

Configuration

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionMatchStrategy": "CurrentOrOlder"
    }
  }
}

Available strategies

  • None (推奨されません): オーケストレーションのバージョンを完全に無視します。 受信したすべての作業は、バージョンに関係なく処理されます。 この方法では、バージョン チェックを効果的に無効にし、任意のワーカーがオーケストレーション インスタンスを処理できるようにします。

  • Strict: ワーカーのdefaultVersionhost.jsonで指定されたバージョンとまったく同じバージョンのオーケストレーションからのタスクのみを処理します。 この方法では、最高レベルのバージョン分離が提供されますが、孤立したオーケストレーションを回避するために慎重な展開調整が必要です。 バージョンの不一致の結果については、「 バージョンの不一致処理 」セクションを参照してください。

  • CurrentOrOlder(既定値): ワーカーのdefaultVersionhost.jsonに指定されたバージョン以下のオーケストレーションのタスクを処理します。 この戦略により、下位互換性が有効になり、新しいワーカーは古いオーケストレーター バージョンによって開始されたオーケストレーションを処理でき、古いワーカーが新しいオーケストレーションを処理できなくなります。 バージョンの不一致の結果については、「 バージョンの不一致処理 」セクションを参照してください。

バージョンの不一致の処理

versionFailureStrategy設定は、オーケストレーション インスタンスのバージョンが現在のdefaultVersionと一致しない場合の動作を決定します。

Configuration:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionFailureStrategy": "Reject"
    }
  }
}

Available strategies:

  • Reject (既定値): オーケストレーションを処理しません。 オーケストレーション インスタンスは現在の状態のままであり、互換性のあるワーカーが使用可能になったときに後で再試行できます。 この方法は、オーケストレーションの状態を保持するため、最も安全なオプションです。

  • Fail: オーケストレーションに失敗します。 この方法では、エラー状態でオーケストレーション インスタンスを直ちに終了します。これは、バージョンの不一致が重大な展開の問題を示すシナリオで適切な場合があります。

特定のバージョンを使用して新しいオーケストレーションとサブオーケストレーションを開始する

既定では、すべての新しいオーケストレーション インスタンスは、defaultVersion構成で指定された現在のhost.jsonで作成されます。 ただし、現在の既定値と異なる場合でも、特定のバージョンのオーケストレーションを作成する必要があるシナリオがある場合があります。

特定のバージョンを使用する場合:

  • 段階的な移行: 新しいバージョンをデプロイした後も、古いバージョンのオーケストレーションを作成し続ける必要があります。
  • テスト シナリオ: 運用環境で特定のバージョンの動作をテストする必要があります。
  • ロールバックの状況: 一時的に以前のバージョンでインスタンスを作成する必要があります。
  • バージョン固有のワークフロー: 異なるビジネス プロセスでは、異なるオーケストレーション バージョンが必要です。

オーケストレーション クライアント API を使用して新しいオーケストレーション インスタンスを作成するときに特定のバージョン値を指定することで、既定のバージョンをオーバーライドできます。 これにより、新しいオーケストレーション インスタンスごとに使用されるバージョンをきめ細かく制御できます。

[Function("HttpStart")]
public static async Task<HttpResponseData> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    var options = new StartOrchestrationOptions
    {
        Version = "1.0"
    };
    
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("ProcessOrderOrchestrator", orderId, options);

    // ...
}

オーケストレーター関数内から、特定のバージョンでサブオーケストレーションを開始することもできます。

[Function("MainOrchestrator")]
public static async Task<string> RunMainOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var subOptions = new SubOrchestratorOptions
    {
        Version = "1.0"
    };
    
    var result = await context.CallSubOrchestratorAsync<string>("ProcessPaymentOrchestrator", orderId, subOptions);
    
    // ...
}

従来のコード パスの削除

時間の経過と共に、オーケストレーター関数から従来のコード パスを削除して、メンテナンスを簡素化し、技術的負債を減らすことが必要になる場合があります。 ただし、既存のオーケストレーション インスタンスが壊れないように、コードの削除は慎重に行う必要があります。

レガシ コードを削除しても安全な場合:

  • 古いバージョンを使用しているすべてのオーケストレーション インスタンスが完了 (成功、失敗、または終了)
  • 古いバージョンの新しいオーケストレーション インスタンスは作成されません
  • レガシ バージョンでインスタンスが実行されていないことを監視またはクエリで確認しました
  • 以前のバージョンが最後にデプロイされてから十分な期間が経過しました (ビジネス継続性の要件を考慮)

削除のベスト プラクティス:

  • アクティブに実行されているインスタンスを監視する: Durable Functions 管理 API を使用して、特定のバージョンを使用してインスタンスのクエリを実行します。
  • 保持ポリシーを設定: 各バージョンの下位互換性を維持する期間を定義します。
  • 増分削除: 複数のバージョンを同時に削除するのではなく、一度に 1 つのバージョンを削除することを検討してください。
  • ドキュメントの削除: バージョンが削除された日時とその理由を明確に記録します。

Warning

オーケストレーション インスタンスがこれらのバージョンを実行している間にレガシ コード パスを削除すると、確定的な再生エラーや予期しない動作が発生する可能性があります。 コードを削除する前に、レガシ バージョンを使用しているインスタンスがないことを常に確認してください。

Best practices

Version management

  • マルチパート バージョン管理を使用する: major.minor.patchなどの一貫性のあるバージョン管理スキームを採用します。
  • ドキュメントの破壊的変更: 新しいバージョンが必要な変更を明確に文書化します。
  • バージョンのライフサイクルを計画する: 従来のコード パスを削除するタイミングを定義します。

Code organization

  • 個別のバージョン ロジック: 異なるバージョンに対して、明確な分岐または個別のメソッドを使用します。
  • 確定性を維持する: デプロイ後に既存のバージョン ロジックを変更しないようにします。 変更が絶対に必要な場合 (重大なバグ修正など)、決定論的な動作を維持し、操作のシーケンスを変更しないようにするか、古いオーケストレーションを処理するときに新しいオーケストレーター のバージョンが失敗することを想定してください。
  • 十分にテストする: すべてのバージョン パス (特に移行中) をテストします。

監視と可観測性

  • ログのバージョン情報: デバッグを容易にするために、ログにバージョンを含めます。
  • バージョンの配布を監視する: アクティブに実行されているバージョンを追跡します。
  • アラートを設定する: バージョン関連のエラーを監視します。

Troubleshooting

Common issues

  • 問題: バージョン 1.0 で作成されたオーケストレーション インスタンスが、バージョン 2.0 のデプロイ後に失敗する

    • 解決策: オーケストレーターのバージョン 1.0 コード パスがまったく同じであることを確認します。 実行シーケンスを変更すると、決定論的な再生が中断される可能性があります。
  • 問題: 古いオーケストレーター バージョンを実行しているワーカーが新しいオーケストレーションを実行できない

    • 解決策: これは予期される動作です。 ランタイムは安全性を維持するために、古いワーカーが新しいバージョンのオーケストレーションを実行することを意図的に制限しています。 すべてのワーカーが最新のオーケストレーター バージョンに更新され、それに応じて defaultVersionhost.json設定が更新されていることを確認します。 この動作は、必要に応じて高度な構成オプションを使用して変更できます (詳細については、「 詳細な使用法 」を参照してください)。
  • 問題: オーケストレーターでバージョン情報を使用できない (context.Version設定に関係なく、defaultVersionが null です)

    • 解決策: サポートされている言語と、オーケストレーションのバージョン管理をサポートする Durable Functions 拡張機能のバージョンを使用していることを確認します。
      • .NET Isolated の場合は、バージョン Microsoft.Azure.Functions.Worker.Extensions.DurableTask 以降使用します。
  • 問題: 新しいバージョンのオーケストレーションの進行が非常に遅いか、完全に停止している

    • 解決策: 問題の根本原因が異なる場合があります。
      1. 新しいワーカーが不足している: 新しいオーケストレーションを処理するために、 defaultVersion で同等以上のバージョンを含む十分な数のワーカーがデプロイされ、アクティブになっていることを確認します。
      2. 古いワーカーからのオーケストレーション ルーティングの干渉: 古いワーカーはオーケストレーション ルーティング メカニズムに干渉する可能性があるため、新しいワーカーが処理のためにオーケストレーションを取得するのが困難になります。 これは、特定のストレージ プロバイダー (Azure Storage または MSSQL) を使用する場合に特に顕著になる可能性があります。 通常、Azure Functions プラットフォームでは、デプロイ後すぐに古いワーカーが確実に破棄されるため、通常、遅延は重要ではありません。 ただし、古いワーカーのライフサイクルを制御できる構成を使用している場合は、古いワーカーが最終的にシャットダウンされていることを確認してください。 または、 Durable Task Scheduler を使用することを検討してください。この問題の影響を受けにくいルーティング メカニズムが改善されているためです。

Next steps