Durable Functionsでのバージョン管理の課題と軽減戦略

Durable Functionsでのバージョン管理は、アプリケーションの有効期間中に関数が必然的に追加、削除、および変更されるため、不可欠です。 Durable Functions では、これまで不可能だった方法で関数を連結できます。このチェーンは、バージョン管理の処理方法に影響します。

この記事は次のことに役立ちます。

  • コードの変更が 重大な変更であるかどうかを特定します。
  • 安全にデプロイするための適切な 軽減戦略 を選択します。

クイック戦略の比較

変更が問題を引き起こすことがわかっている場合は、次の表を使用して軽減策を選択します。

戦略 最適な用途 詳細情報
オーケストレーションのバージョン管理 (推奨) 破壊的変更を伴うほとんどのアプリケーション。 組み込みのランタイム機能は、任意のストレージ バックエンドで動作します。 セクションにジャンプ
並列デプロイメント オーケストレーションのバージョン管理を使用できないアプリ、または個別のタスク ハブまたはストレージ アカウントによる完全な分離が必要なアプリ。 セクションにジャンプ
すべてのインフライト インスタンスを停止する インフライト オーケストレーションの損失が許容されるプロトタイプ作成とローカル開発。 セクションにジャンプ

ヒント

ランタイム レベルで自動バージョン分離を提供する組み込みの オーケストレーション バージョン 管理機能をお探しの場合は、「オーケストレーションの バージョン管理」を参照してください。

Important

デプロイする前に、変更が重大な変更であるかどうかを確認します。

  • アクティビティまたはエンティティ関数の 名前、入力の種類、または出力の種類 を変更しましたか?
  • オーケストレーター コードでアクティビティ、サブオーケストレーション、タイマー、または外部イベントの呼び出しを 追加、削除、または並べ替 えしましたか?
  • 実行中のオーケストレーションが引き続き呼び出す可能性がある関数の 名前を変更または削除 しましたか?

" はい " と答えた場合は、オーケストレーションの実行でエラーが発生しないように、以下のいずれかの 軽減策 を使用します。

破壊的変更の種類

破壊的変更の例がいくつかあります。 この記事では、最も一般的な型について説明します。 すべての背後にある主なテーマは、関数コードの変更が新しい関数オーケストレーションと既存の関数オーケストレーションの両方に影響を与えるということです。

アクティビティまたはエンティティ関数のシグネチャの変更

シグネチャの変更とは、関数の名前、入力、または出力の変更を指します。 アクティビティまたはエンティティ関数に対してこの種の変更を行うと、それに依存するすべてのオーケストレーター関数が中断される可能性があります。 この動作は、タイプ セーフな言語の場合に特に当てはまります。 この変更に対応するようにオーケストレーター関数を更新すると、既存のインフライト インスタンスが中断される可能性があります。

たとえば、次のオーケストレーター関数について考えてみましょう。

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

この関数は Foo の結果を受け取り、 Bar に渡します。 より多様な結果値をサポートするには、 Foo の戻り値をブール値から文字列に変更する必要があるとします。 結果は次のようになります。

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string result = await context.CallActivityAsync<string>("Foo");
    await context.CallActivityAsync("Bar", result);
}

この変更は、オーケストレーター関数のすべての新しいインスタンスで正常に動作しますが、すべてのインフライト インスタンスが中断される可能性があります。 たとえば、オーケストレーション インスタンスが Foo という名前の関数を呼び出し、ブール値を取得してからチェックポイントを取得する場合を考えてみましょう。 この時点で署名の変更がデプロイされた場合、チェックポイント処理されたインスタンスは、 Fooの呼び出しを再開して再生するとすぐに失敗します。 このエラーは、履歴テーブルの結果がブール値であるために発生しますが、新しいコードでは文字列値に逆シリアル化しようとすると、予期しない動作が発生したり、型セーフな言語のランタイム例外が発生したりします。

この例は、関数シグネチャの変更が既存のインスタンスを中断するさまざまな方法の 1 つです。 一般に、オーケストレーターが関数の呼び出し方法を変更する必要がある場合、変更は問題になる可能性があります。

オーケストレーター ロジックの変更

バージョン管理の問題のもう 1 つのクラスは、実行中のインスタンスの実行パスを変更する方法でオーケストレーター関数コードを変更することによって発生します。

次のオーケストレーター関数について考えてみましょう。

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

次に、既存の 2 つの関数呼び出しの間に新しい関数呼び出しを追加するとします。

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    if (result)
    {
        await context.CallActivityAsync("SendNotification");
    }

    await context.CallActivityAsync("Bar", result);
}

この変更により、SendNotificationFooの間のBarに新しい関数呼び出しが追加されます。 署名の変更は存在しません。 この問題は、既存のインスタンスが Barの呼び出しから再開したときに発生します。 再生中に、元の呼び出しFootrueを返した場合、オーケストレーターは実行履歴にないSendNotificationへの呼び出しを再生します。 ランタイムは、この不整合を検出し、の呼び出しが予想されたときにSendNotificationの呼び出しが発生したため、Bar エラーを発生させます。 永続的タイマーの作成、外部イベントの待機、サブオーケストレーションの呼び出しなど、他の永続的な操作に API 呼び出しを追加すると、同じ種類の問題が発生する可能性があります。

軽減策

警告

軽減戦略 ("何もしない" アプローチ) なしで破壊的変更をデプロイすると、非 決定的オーケストレーション エラーでオーケストレーションが失敗したり、 Running 状態で無期限に停止したり、パフォーマンスを低下させる低レベルのランタイム エラーがトリガーされたりする可能性があります。 破壊的変更をデプロイするときは、常に次のいずれかの方法を使用してください。

このセクションの他の方法とは異なり、オーケストレーションのバージョン管理は、自動バージョン分離を提供する 組み込みのランタイム機能 です。 個別のデプロイ、タスク ハブ、またはストレージ アカウントを管理する必要はありません。 代わりに、ランタイム自体はバージョン情報を追跡し、オーケストレーション インスタンスが互換性のあるワーカーによって処理されるようにします。

オーケストレーションのバージョニングでは:

  • 各オーケストレーション インスタンスは、作成時に永続的に関連付けられたバージョンを取得します。
  • オーケストレーター関数は、バージョンとブランチの実行を適宜調べ、古いコード パスと新しいコード パスを同じコードベースに保持できます。
  • 新しいオーケストレーター関数バージョンを実行しているワーカーは、古いバージョンによって作成されたオーケストレーション インスタンスの実行を続行できます。
  • ランタイムは、古いオーケストレーター関数バージョンを実行しているワーカーが新しいバージョンのオーケストレーションを実行できないようにします。

この方法では、最小限の構成 (バージョン文字列とオプションの一致戦略) が必要であり、任意の ストレージ プロバイダーと互換性があります。 ダウンタイムなしのデプロイを維持しながら、破壊的変更をサポートする必要があるアプリケーションに推奨される戦略 です

詳細な構成と実装のガイダンスについては、「 オーケストレーションのバージョン管理」を参照してください。

処理中のすべてのインスタンスを停止する

もう 1 つのオプションは、すべてのインフライト インスタンスを停止することです。 Durable Functions に既定の Azure Storage プロバイダーを使用している場合は、内部の control-queue および workitem-queue キューの内容をクリアして、すべてのインスタンスを停止します。 または、関数アプリを停止し、これらのキューを削除して、アプリを再起動します。 キューは、アプリの再起動後に自動的に再作成されます。 以前のオーケストレーション インスタンスは、無期限に "実行中" 状態のままになる可能性がありますが、エラー メッセージでログを乱雑にしたり、アプリに損害を与えたりすることはありません。 このアプローチは、ローカル開発を含む迅速なプロトタイプ開発に最適です。

警告

この方法では、基になるストレージ リソースに直接アクセスする必要があり、Durable Functionsでサポートされているすべてのストレージ プロバイダーに適しているわけではありません。

並列展開

重大な変更が安全にデプロイされるようにするための最も失敗しない方法は、古いバージョンと並行してデプロイすることです。 次のいずれかの手法を使用できます。

  • 異なるストレージ アカウント: すべての更新プログラムを、別のストレージ アカウントを持つ新しい関数アプリとしてデプロイします。 これにより、新しいバージョンの状態が古いバージョンから完全に分離されます。
  • 異なるタスク ハブ: 同じストレージ アカウントを持ち、 タスク ハブ 名が更新された関数アプリの新しいコピーをデプロイします。 この方法では、新しいバージョンの新しいストレージ 成果物が作成されますが、古いバージョンでは既存の成果物が引き続き使用されます。

Azureで並列デプロイを行う場合は、デプロイ スロット を使用して、アクティブな production スロットとして 1 つだけの両方のバージョンを同時に実行できます。 新しいオーケストレーション ロジックを公開する準備ができたら、新しいバージョンを運用スロットにスワップします。

このガイダンスではAzure Storage固有の用語を使用しますが、一般に、サポートされているすべての Durable Functions ストレージ プロバイダーに適用されます。

デプロイ スロットのスワップは、HTTP トリガーと Webhook トリガーで最適に機能します。 キューや Event Hubs などの HTTP 以外のトリガーの場合、トリガー定義は、スワップ操作の一部として更新される アプリ設定から派生 する必要があります。

次のステップ