アプリケーションの有効期間中に関数が追加、削除、および変更されることは避けられません。 Durable Functions を使用すると、これまで不可能だった方法で関数を連結できます。このチェーンは、バージョン管理の処理方法に影響します。
破壊的変更の種類
注意すべき破壊的変更の例がいくつかあります。 この記事では、最も一般的なものについて説明します。 すべての背後にある主なテーマは、新しい関数オーケストレーションと既存の関数オーケストレーションの両方が、関数コードの変更の影響を受けるということです。
アクティビティまたはエンティティ関数のシグネチャの変更
シグネチャの変更とは、関数の名前、入力、または出力の変更を指します。 この種の変更がアクティビティまたはエンティティ関数に対して行われた場合、それに依存するすべてのオーケストレーター関数が中断される可能性があります。 これは、タイプ セーフな言語の場合に特に当てはまります。 この変更に対応するようにオーケストレーター関数を更新すると、既存のインフライト インスタンスが中断される可能性があります。
たとえば、次のオーケストレーター関数があるとします。
[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);
}
この変更により、Foo と Bar の間に SendNotification に新しい関数呼び出しが追加されます。 署名の変更はありません。 この問題は、既存のインスタンスが Bar への呼び出しから再開したときに発生します。 再生中に 、Foo への元の呼び出しが true
返された場合、オーケストレーターの再生は SendNotification を呼び出しますが、これは実行履歴には含まれません。 ランタイムは、この不整合を検出し、Bar への呼び出しが予想されたときに SendNotification の呼び出しが発生したため、非決定論的オーケストレーション エラーを発生させます。 永続的タイマーの作成、外部イベントの待機、サブオーケストレーションの呼び出しなど、他の永続的な操作に API 呼び出しを追加すると、同じ種類の問題が発生する可能性があります。
対応方法
バージョン管理の課題に対処するための戦略の一部を次に示します。
- 何もしない (推奨されません)
- オーケストレーションのバージョン管理 (ほとんどの場合推奨)
- 処理中のすべてのインスタンスを停止する
- 並列展開
何もしない
バージョン管理に対する単純なアプローチは、何も実行せず、インフライト オーケストレーション インスタンスが失敗するようにすることです。 変更の種類によっては、次の種類のエラーが発生する可能性があります。
- オーケストレーションは、非決定的オーケストレーション エラーによって失敗することがあります。
- オーケストレーションが無期限に停止し、
Running
状態が報告されることがあります。 - 関数が削除されると、その関数を呼び出そうとするすべての関数がエラーで失敗する可能性があります。
- 実行がスケジュールされた後に関数が削除されると、Durable Task Framework エンジンで低レベルのランタイム エラーが発生し、パフォーマンスが著しく低下する可能性があります。
このような障害の可能性があるため、"何もしない" 戦略はお勧めしません。
オーケストレーションのバージョン管理
注
オーケストレーションのバージョン管理は現在、パブリック プレビュー段階です。
オーケストレーションのバージョン管理機能を使用すると、異なるバージョンのオーケストレーションを共存させ、競合や非決定性の問題なしに同時に実行できるため、実行中のオーケストレーション インスタンスを手動で介入せずに完了できるようにしながら、更新プログラムを展開できます。
オーケストレーションのバージョニングでは:
- 各オーケストレーション インスタンスは、作成時に永続的に関連付けられたバージョンを取得します
- オーケストレーター関数は、それに応じてバージョンとブランチの実行を調べることができます
- 新しいオーケストレーター関数バージョンを実行しているワーカーは、古いバージョンによって作成されたオーケストレーション インスタンスの実行を続行できます
- ランタイムは、古いオーケストレーター関数バージョンを実行しているワーカーが新しいバージョンのオーケストレーションを実行できないようにします
この戦略は、 ダウンタイムなしのデプロイを維持しながら破壊的変更をサポートする必要があるアプリケーションに推奨されます。 この機能は現在、.NET 分離アプリで使用できます。
詳細な構成と実装のガイダンスについては、「 Durable Functions でのオーケストレーションのバージョン管理」を参照してください。
稼働中のすべてのインスタンスを停止する
もう 1 つのオプションは、すべてのインフライト インスタンスを停止することです。 Durable Functions 用の既定の Azure Storage プロバイダーを使用している場合は、すべてのインスタンスを停止するには、内部コントロール キューと workitem-queueキューの内容をクリアします。 または、関数アプリを停止し、これらのキューを削除して、アプリをもう一度再起動することもできます。 アプリが再起動すると、キューが自動的に再作成されます。 以前のオーケストレーション インスタンスは、無期限に "実行中" 状態のままになる可能性がありますが、エラー メッセージでログを乱雑にしたり、アプリに損害を与えたりすることはありません。 このアプローチは、ローカル開発を含む迅速なプロトタイプ開発に最適です。
注
この方法では、基になるストレージ リソースへの直接アクセスが必要であり、Durable Functions でサポートされているすべてのストレージ プロバイダーに適していない場合があります。
並行してデプロイする
重大な変更が安全にデプロイされるようにするための最も失敗しない方法は、古いバージョンとサイド バイ サイドでデプロイすることです。 これは、次のいずれかの手法を使用して行うことができます。
- 既存の関数を as-isしたまま、すべての更新プログラムをまったく新しい関数としてデプロイします。 これは通常、新しい関数バージョンの呼び出し元の再帰的な更新に伴う複雑さのため、推奨されません。
- すべての更新プログラムを、別のストレージ アカウントを使用して新しい関数アプリとしてデプロイします。
- 同じストレージ アカウントと更新された タスク ハブ 名を使用して、関数アプリの新しいコピーをデプロイします。 これにより、新しいバージョンのアプリで使用できる新しいストレージ 成果物が作成されます。 以前のバージョンのアプリは、以前のストレージ 成果物のセットを使用して引き続き実行されます。
サイド バイ サイドデプロイは、関数アプリの新しいバージョンをデプロイするための推奨される手法です。
注
サイド バイ サイド デプロイ戦略のこのガイダンスでは、Azure Storage 固有の用語を使用しますが、一般に、サポートされているすべての Durable Functions ストレージ プロバイダーに適用されます。
デプロイ スロット
Azure Functions または Azure App Service で並列デプロイを行う場合は、新しいバージョンの関数アプリを新しい デプロイ スロットにデプロイすることをお勧めします。 デプロイ スロットを使用すると、関数アプリの複数のコピーを、そのうちの 1 つだけをアクティブな 運用 スロットとして並べて実行できます。 新しいオーケストレーション ロジックを既存のインフラストラクチャに公開する準備ができたら、新しいバージョンを運用スロットにスワップするのと同じくらい簡単です。
注
この戦略は、オーケストレーター関数に HTTP トリガーと Webhook トリガーを使用する場合に最適です。 キューや Event Hubs などの HTTP 以外のトリガーの場合、トリガー定義は、スワップ操作の一部として更新される アプリ設定から派生 する必要があります。