次の方法で共有


オーケストレーションのバージョン管理

オーケストレーター ロジックへの変更のデプロイは、永続的オーケストレーション システムを使用する際の重要な考慮事項です。 オーケストレーションが中断され、後で再開された場合 (ホストの更新中など)、ランタイムはオーケストレーションのイベントを再生し、次の手順を実行する前に前のすべての手順が正常に実行されるようにします。 展開間でオーケストレーション コードが変更された場合、実行される手順が同じでなくなる可能性があります。 その場合、システムはオーケストレーションを続行させるのではなく、非決定性エラーをスローします。

オーケストレーションのバージョン管理 により、非決定性に関連する問題が回避され、永続的オーケストレーションに必要な決定論的実行モデルを維持しながら、新しい (または古い) オーケストレーションをシームレスに操作できます。

この組み込み機能は、最小限の構成でバージョンの自動分離を提供します。 バックエンドに依存しないため、Durable Functions storage プロバイダー (Durable Task Scheduler を含む) のいずれかを使用するすべてのアプリで使用できます。

Durable Task SDK では、次の 2 つのスタイルのバージョン管理がサポートされています。このスタイルは、個別に使用することも、一緒に使用することもできます。

用語

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

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

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

どのように機能するのか

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

  • バージョンの関連付け: オーケストレーション インスタンスが作成されると、永続的に関連付けられたバージョンが取得されます。
  • バージョン対応の実行: Orchestrator コードは、現在のオーケストレーション インスタンスに関連付けられているバージョン値を調べ、それに応じて実行を分岐します。
  • 下位互換性: 新しいオーケストレーター バージョンを実行しているワーカーは、古いバージョンによって作成されたオーケストレーション インスタンスを引き続き実行します。
  • 前方保護: ランタイムは、古いオーケストレーター バージョンを実行しているワーカーが、新しいバージョンで開始されたオーケストレーションを実行できないようにします。

[前提条件]

オーケストレーションのバージョン管理を使用する前に、プログラミング言語に必要なパッケージ バージョンがあることを確認してください。

.NET以外の言語 (JavaScript、Python、PowerShell、または Java) を extension バンドル と共に使用している場合、関数アプリは Extension Bundle バージョン 4.30.0 以降を参照する必要があります。 最小バージョンが少なくともextensionBundleになるように、host.json4.30.0範囲を構成します。 例えば次が挙げられます。

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[4.30.0, 5.0.0)"
    }
}

バンドル バージョンの選択と更新の詳細については、 拡張機能バンドル構成のドキュメントを参照してください

.NET以外の言語の拡張機能バンドル要件に加えて、以下に示す言語固有の SDK パッケージの最小バージョンも使用する必要があります。 オーケストレーションのバージョン管理が正しく機能するためには、拡張機能バンドルと SDK パッケージの両方が必要です。

Microsoft.Azure.Functions.Worker.Extensions.DurableTask バージョン 1.14.0 以降を使用。

既定のバージョンの設定

オーケストレーションのバージョン管理を使用するには、まず、新しいオーケストレーション インスタンスの既定のバージョンを構成します。

Azure Functions プロジェクトの defaultVersion ファイルに host.json 設定を追加または更新します。

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

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

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

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

アプリケーションを構成するときに、クライアント ビルダーで既定のバージョンを設定します。

v1.9.0 以降、.NET SDK (Microsoft.DurableTask.Client.AzureManaged) で使用できます。

builder.Services.AddDurableTaskClient(builder =>
{
    builder.UseDurableTaskScheduler(connectionString);
    builder.UseDefaultVersion("1.0.0");
});

バージョンは単純な文字列であり、任意の値を受け入れます。 SDK は、.NETの System.Version に変換しようとします。 成功した場合、そのライブラリは比較に使用されます。 それ以外の場合は、単純な文字列比較が使用されます。

クライアントで既定のバージョンを設定すると、このクライアントによって開始されたすべてのオーケストレーションは、そのバージョンに永続的に関連付けられます。 バージョンはオーケストレーション コンテキストでも使用でき、条件付きステートメントで使用できます。

バージョン比較規則

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

  • 空または null のバージョンは等しいとして扱われます。
  • 空または null バージョンは、定義されているバージョンよりも古いと見なされます。
  • 両方のバージョンが数値の場合 (たとえば、 "1.0""2.0")、バージョン番号として比較されるため、 "2.0""1.0"よりも新しくなります。
  • それ以外の場合は、大文字と小文字を区別しない文字列比較が実行されます。

次の例は、バージョン比較のしくみを示しています。

バージョン A バージョン B 結果
"1.0" "2.0" A の方が古い
null "1.0" A の方が古い
null null 等しい
"v1-release" "v2-release" A が古い (アルファベット順)

StrictまたはCurrentOrOlder一致戦略が選択されている場合 (バージョンの照合を参照)、バージョンの比較は言語によって異なります。

  • .NET: SDK はバージョンを System.Version として解析しようとします。 両方が正常に解析された場合、比較では CompareToが使用されます。 それ以外の場合、SDK は文字列比較を使用します。
  • Python: SDK では、セマンティック バージョン管理の比較に packaging.version を使用します。
  • Java: SDK は、バージョンを単純な文字列として比較します。

バージョン対応オーケストレーター ロジック

バージョン対応のロジックを実装するには、コンテキスト パラメーターを使用して、現在のオーケストレーション インスタンスのバージョンとブランチの実行にアクセスします。

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
        ...
    }
    ...
}
[DurableTask]
class HelloCities : TaskOrchestrator<string, List<string>>
{
    private readonly string[] Cities = ["Seattle", "Amsterdam", "Hyderabad"];

    public override async Task<List<string>> RunAsync(
        TaskOrchestrationContext context, string input)
    {
        List<string> results = [];
        foreach (var city in Cities)
        {
            results.Add(await context.CallSayHelloAsync($"{city} v{context.Version}"));
            if (context.CompareVersionTo("2.0.0") >= 0)
            {
                results.Add(await context.CallSayGoodbyeAsync($"{city} v{context.Version}"));
            }
        }
        return results;
    }
}

context.Version プロパティは読み取り専用であり、作成時にオーケストレーション インスタンスに永続的に関連付けられているバージョンを反映します。 オーケストレーションの実行中にこの値を変更することはできません。

ヒント

既定のバージョンを指定する前に、既にインフライト オーケストレーションが作成されている場合、 context.Version はそれらのインスタンスに対して null (または言語に依存する同等の) を返します。 レガシ (null バージョン) と新しいバージョン管理されたオーケストレーションの両方を処理するようにオーケストレーター ロジックを構成します。

デプロイ動作

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

  • ワーカーの共存: 新しいオーケストレーター関数コードを含むワーカーは開始しますが、古いコードを持つ一部のワーカーはアクティブなままである可能性があります。
  • 新しいインスタンスのバージョン割り当て:新しいワーカーによって作成されたすべての新しいオーケストレーションとサブオーケストレーションには、defaultVersion からバージョンが割り当てられます。
  • 新しいワーカーの互換性: 新しく作成されたオーケストレーションと以前に既存のオーケストレーションの両方を処理できるのは、バージョン対応の分岐ロジックによって下位互換性が確保されるためです。
  • 古いワーカーの制限: 古いワーカーは、オーケストレーター コードが新しいバージョンと互換性があるとは想定されていないため、defaultVersionで独自のhost.jsonで指定されたバージョン以下のバージョンのオーケストレーションのみを処理できます。

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

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

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

バージョン 1.0

host.json 構成:

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

オーケストレーター関数:

[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 構成:

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

オーケストレーター関数:

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

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

    if (TaskOrchestrationVersioningUtils.CompareVersions(context.Version, "1.0") <= 0)
    {
        // Preserve original logic for existing instances
        await context.CallActivityAsync("ProcessPayment", orderId);
    }
    else
    {
        // New logic with discount processing
        await context.CallActivityAsync("ApplyDiscount", orderId);
        await context.CallActivityAsync("ProcessPaymentWithDiscount", orderId);
    }

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

    return "Order processed successfully";
}

バージョンの一致

バージョン照合戦略は、バージョンの互換性に基づいてワーカーが処理するオーケストレーション インスタンスを決定します。

次の表では、使用可能な戦略について説明します。

戦略 説明
なし 処理時にバージョンは考慮されません。 すべての作業は、バージョンに関係なく処理されます。
Strict オーケストレーションのバージョンと worker のバージョンが正確に一致している必要があります。
最新または古いバージョン オーケストレーションのバージョンは、ワーカー バージョン以下である必要があります。 これが既定の戦略です。

コンフィギュレーション

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionMatchStrategy": "CurrentOrOlder"
    }
  }
}
  • None (推奨されません): バージョン チェックを無効にします。 すべてのワーカーがオーケストレーション インスタンスを処理します。
  • Strict: defaultVersionとまったく同じバージョンのオーケストレーションからのみタスクを処理します。 孤立したオーケストレーションを回避するには、慎重な展開調整が必要です。
  • CurrentOrOlder (既定値): defaultVersion以下のバージョンのオーケストレーションからのタスクを処理します。 古いワーカーが新しいオーケストレーションを処理できないようにしながら、下位互換性を有効にします。

ワーカービルダーを使ってマッチ戦略を設定します。

v1.9.0 以降、.NET SDK (Microsoft.DurableTask.Worker.AzureManaged) で使用できます。

builder.Services.AddDurableTaskWorker(builder =>
{
    builder.AddTasks(r => r.AddAllGeneratedTasks());
    builder.UseDurableTaskScheduler(connectionString);
    builder.UseVersioning(new DurableTaskWorkerOptions.VersioningOptions
    {
        Version = "1.0.0",
        DefaultVersion = "1.0.0",
        MatchStrategy = DurableTaskWorkerOptions.VersionMatchStrategy.Strict,
        FailureStrategy = DurableTaskWorkerOptions.VersionFailureStrategy.Reject,
    });
});

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

バージョンの不一致処理戦略は、オーケストレーション インスタンスのバージョンが worker バージョンと一致しない場合の動作を決定します。

次の表では、使用可能な戦略について説明します。

戦略 説明
拒否する オーケストレーションが拒否され、作業キューに戻されます。 別のワーカーが後で試すことができます。 この戦略が既定です。
失敗 オーケストレーションが失敗し、作業キューから削除されます。

コンフィギュレーション

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionFailureStrategy": "Reject"
    }
  }
}
  • Reject (既定値): オーケストレーション インスタンスは現在の状態のままであり、互換性のあるワーカーが使用可能になったときに後で再試行できます。 この方法はオーケストレーションの状態を保持するため、最も安全なオプションです。
  • Fail: エラー状態でオーケストレーション インスタンスを直ちに終了します。 このオプションは、バージョンの不一致が重大なデプロイの問題を示している場合に適している場合があります。

各戦略を使用するタイミング

拒否: 後で、または別のワーカーでオーケストレーションを再試行する場合は、この戦略を使用します。 Rejectエラー中:

  1. オーケストレーションが拒否され、作業キューに戻されます。
  2. 別のワーカーがオーケストレーションをデキューします。
  3. キューから削除されたオーケストレーションは、別のワーカーまたは同じワーカーに再び割り当てられる可能性があります。

このプロセスは、オーケストレーションを処理できるワーカーが使用可能になるまで繰り返されます。 この戦略は、ワーカーが段階的に更新されるローリング デプロイをシームレスに処理します。

失敗: 他のバージョンのワーカーがオーケストレーションを処理することが想定されていない場合は、この戦略を使用します。 オーケストレーションが失敗し、終了状態になります。

特定のバージョンを指定してオーケストレーションを開始する

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

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

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

従来のコード パスを削除する

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

レガシ コードを安全に削除できる場合

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

Warnung

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

ベスト プラクティス

バージョン管理

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

コード編成

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

監視と可観測性

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

Troubleshooting

一般的な問題

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

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

    • 解決策: この動作は想定されています。 ランタイムは、古いワーカーが新しいバージョンのオーケストレーションを実行できないようにします。 すべての worker が最新バージョンに更新され、それに応じて defaultVersionhost.json設定が更新されていることを確認します。
  • 問題: オーケストレーターでバージョン情報を使用できない (context.Version設定に関係なく、context.getVersion()またはdefaultVersionが null です)

    • 解決策: 「前提条件」 セクションを調べて、環境がオーケストレーションのバージョン管理のすべての要件を満たしていることを確認します。
  • 問題: 新しいバージョンのオーケストレーションは進行が非常に遅いか、停止している

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

次のステップ