持続的オーケストレーション

永続的オーケストレーションでは、 オーケストレーター関数 を使用して、完全にコードで定義された信頼性の高い実行時間の長いワークフロー内の他の関数の実行を調整します。 オーケストレーター関数には、次の特性があります。

  • 手続き型コードを使用してワークフローを定義します。 宣言型スキーマやデザイナーは必要ありません。
  • 他の関数を同期的および非同期的に呼び出します。 呼び出し関数の出力をローカル変数に保存できます。
  • 関数が await や yield オペレーターを呼び出すと自動的に実行進行状況をチェックし、サイクルやVMの再起動時にローカル状態を失わないようにします。
  • 実行時間の長いプロセスをサポートします。 オーケストレーション インスタンスの合計有効期間は、秒、日、または月にすることができます。また、インスタンスを決して終了するように構成することもできます。

この記事では、オーケストレーション ID、イベント ソーシング、実行履歴、サブオーケストレーション、持続的タイマー、エラー処理などの一般的なワークフロー パターンなど、永続的なオーケストレーションの概要について説明します。

Durable Functions アプリで使用できる関数の種類については、「Durable Task プログラミング モデルを参照してください。

ヒント

C#を.NET孤立ワーカーモデルで使う場合、関数ベースのアプローチ([Function]属性を持つ静的メソッド)またはクラスベースのアプローチ(TaskOrchestrator<TInput, TOutput>から継承するクラス)のいずれかでオーケストレーションを作成できます。 クラス ベースのアプローチでは、 Microsoft.DurableTask.Generators ソース ジェネレーター パッケージが必要であり、厳密に型指定された呼び出しが提供されます。 詳細については、 ソース ジェネレーターとクラス ベースの構文に関するページを参照してください。 この記事の C# コード例では、両方の方法を示します。

Durable Task SDK は、Durable Functions と同じオーケストレーター機能を提供し、並列処理とイベントドリブン調整を使用して、信頼性の高い実行時間の長いワークフローを構築します。 Durable Functions とは異なり、Durable Task SDK オーケストレーションは 、Durable Task Scheduler によってサポートされるスタンドアロン アプリケーションとして実行されます。

オーケストレーション アイデンティティ

オーケストレーションの各 インスタンス には、インスタンス ID とも呼ばれる インスタンス識別子があります。 既定では、各インスタンス ID は自動生成されたグローバル一意識別子 (GUID) です。 ただし、ユーザー生成の文字列値はインスタンスIDとして使用できます。 各オーケストレーション インスタンス ID は 、タスク ハブ内で一意である必要があります。

インスタンス ID には、次の規則が適用されます。

  • 1 ~ 100 文字にする必要があります。
  • @で始めてはなりません。
  • /、\、#、または?文字を含めてはなりません。
  • コントロール文字を含めてはなりません。

注

可能な限り、自動生成されたインスタンス ID を使用します。 オーケストレーションインスタンスと外部のアプリケーション固有のエンティティ(例えば購入注文書やドキュメント)と1対1のマッピングがある状況には、ユーザー生成インスタンスIDを使いましょう。

注

文字制限ルールの実際の適用は、アプリが使用する ストレージ プロバイダー によって異なる場合があります。 正しい動作と互換性を確保するには、上記のインスタンス ID 規則に従います。

オーケストレーションのインスタンス ID は、ほとんどの インスタンス管理操作に必要なパラメーターです。 インスタンス ID は、診断にも重要です。 たとえば、トラブルシューティングや分析のために Application Insights で オーケストレーション追跡データを検索 するときに使用します。 このため、生成されたインスタンス ID を外部の場所に保存して、データベースやアプリケーション ログなど、後で簡単に参照できるようにします。

オーケストレーションのインスタンス ID は、ほとんどの インスタンス管理操作に必要なパラメーターです。 インスタンス ID は診断にも重要であるため、生成されたインスタンス ID を外部の場所に保存して、データベースやアプリケーション ログなど、後で簡単に参照できるようにします。

Reliability

オーケストレーター関数は 、イベント ソーシング 設計パターンを使用して、実行状態を確実に維持します。 Durable Task Framework では、オーケストレーションの現在の状態を直接格納する代わりに、追加専用ストアを使用して、関数オーケストレーションが実行する一連のアクションをすべて記録します。 追加専用ストアには、完全なランタイム状態 のダンプ に比べて多くの利点があります。 パフォーマンス、スケーラビリティ、応答性の向上などの利点があります。 また、トランザクション データ、完全な監査証跡、履歴の最終的な整合性も得られます。 監査証跡は、信頼性の高い補正アクションをサポートします。

Durable Task Framework では、イベント ソーシングが透過的に使用されます。 オーケストレーター関数は、バックグラウンドで C# の await 演算子と JavaScript と Python の yield 演算子を使用します。 これらの演算子は、オーケストレーター スレッドの制御を Durable Task Framework ディスパッチャーに戻します。 Javaでは、タスクで .await() を呼び出すと、Throwable のカスタム インスタンスを介してディスパッチャーに制御が戻されます。 その後、ディスパッチャーは、オーケストレーター関数がストレージにスケジュールする新しいアクションをコミットします。 アクションの例としては、1 つ以上の子関数を呼び出したり、永続的なタイマーをスケジュールしたりすることが含まれます。 透過的コミット アクションは、追加専用ログと同様に、すべての新しいイベントをストレージに追加することで、オーケストレーション インスタンスの実行履歴を更新します。 同様に、コミット アクションは、実際の作業をスケジュールするメッセージをストレージに作成します。 この時点で、オーケストレーター関数をメモリからアンロードできます。

利用可能な ストレージプロバイダー のどれでもランタイムステートストアとして利用できます。

オーケストレーション関数がより多くの処理を行う場合 (応答メッセージを受信した場合や永続的なタイマーが期限切れになった場合など)、オーケストレーターは起動し、関数全体を最初から再実行してローカル状態を再構築します。 再生中に、コードが関数を呼び出そうとする (またはその他の非同期作業を行う) 場合、Durable Task Framework は現在のオーケストレーションの実行履歴を参照します。 アクティビティが既に実行され、結果が生成されたと検出された場合は、その関数の結果が再生され、オーケストレーター コードは引き続き実行されます。 再生は、関数コードが完了するか、新しい非同期作業をスケジュールするまで続行されます。

注

再生パターンが正しく確実に機能するためには、オーケストレーター関数コードが 決定論的である必要があります。 非決定的オーケストレーター コードでは、ランタイム エラーまたはその他の予期しない動作が発生する可能性があります。 オーケストレーター関数のコード制限の詳細については、「 オーケストレーター関数のコード制約」を参照してください。

注

オーケストレーター関数がログ メッセージを出力すると、再生動作によって重複するログ メッセージが出力される可能性があります。 この動作が発生する理由とその回避方法については、 リプレイ セーフなログ記録に関するページを参照してください。

オーケストレーションの履歴

Durable Task Framework のイベント ソーシング動作は、記述するオーケストレーター関数コードと密接に結び付けられます。 次に示す例のように、アクティビティ チェーン オーケストレーター関数を持っているとします。

分離ワーカー モデル
[Function("HelloCities")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

    // Return ["Hello Tokyo!", "Hello Seattle!", "Hello London!"].
    return outputs;
}

クラス ベースのモデル (分離ワーカー)

クラス ベースのアプローチではソース ジェネレーターが使用され、 Microsoft.DurableTask.Generators NuGet パッケージが必要です。

using Microsoft.DurableTask;

[DurableTask]
public class HelloCities : TaskOrchestrator<object?, List<string>>
{
    public override async Task<List<string>> RunAsync(
        TaskOrchestrationContext context, object? input)
    {
        var outputs = new List<string>();

        outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
        outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
        outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

        // Return ["Hello Tokyo!", "Hello Seattle!", "Hello London!"].
        return outputs;
    }
}

プロセス内モデル
[FunctionName("HelloCities")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

    // Return ["Hello Tokyo!", "Hello Seattle!", "Hello London!"].
    return outputs;
}
using Microsoft.DurableTask;

[DurableTask]
public class HelloCities : TaskOrchestrator<object?, List<string>>
{
    public override async Task<List<string>> RunAsync(TaskOrchestrationContext context, object? input)
    {
        var outputs = new List<string>();

        outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
        outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
        outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

        return outputs;
    }
}

アクティビティ関数をスケジュールするたびに、Durable Task Frameworkは各チェックポイントでその関数の実行状態を保存します。 各チェックポイントで、フレームワークは状態を永続的なストレージ バックエンドに保存します。 この状態は オーケストレーションの歴史です。

履歴テーブル

各チェックポイントで、持続的タスクフレームワークは以下の動作を実行します。

  • 実行履歴を耐久性のある保管に保存します。
  • オーケストレーターが呼び出す関数のメッセージをエンキューします。
  • オーケストレーター自身のメッセージ、例えば永続的なタイマーメッセージなどをエンキューします。

チェックポイントが完了すると、フレームワークは、オーケストレーター関数にさらに実行すべき作業が発生するまで、その関数をメモリから取り除きます。

注

Azure Storageは、データを保存する際にテーブルストレージとキュー間のデータの一貫性についてトランザクション保証を提供していません。 失敗を処理するために、Durable Functions Azure Storageプロバイダーはコマンドおよびクエリ責任分離(CQRS)などの最終的整合性パターンを使用します。 代替ストレージプロバイダーは、より強力な一貫性保証を提供します:

前に示した関数が完了すると、その履歴は Table Storage の次の表のデータのようになります。 エントリは例示のために省略されています。

PartitionKey(インスタンスID) イベントの種類 タイムスタンプ (日時記録) 入力 名前 結果 地位
eaee885b 実行開始 2021-05-05T18:45:28.852Z null 値 HelloCities
eaee885b オーケストレーター開始 (OrchestratorStarted) 2021-05-05T18:45:32.362Z
eaee885b タスクスケジュール済み 2021-05-05T18:45:32.670Z セイハロー
eaee885b OrchestratorCompleted 2021-05-05T18:45:32.670Z
eaee885b タスク完了 2021-05-05T18:45:34.201Z """Hello Tokyo!""
eaee885b オーケストレーター開始 (OrchestratorStarted) 2021-05-05T18:45:34.232Z
eaee885b タスクスケジュール済み 2021-05-05T18:45:34.435Z セイハロー
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.435Z
eaee885b タスク完了 2021-05-05T18:45:34.763Z 「「こんにちはシアトル!」」
eaee885b オーケストレーター開始 (OrchestratorStarted) 2021-05-05T18:45:34.857Z
eaee885b タスクスケジュール済み 2021-05-05T18:45:34.857Z セイハロー
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.857Z
eaee885b タスク完了 2021-05-05T18:45:34.919Z 「「「こんにちはロンドン!」」
eaee885b オーケストレーター開始 (OrchestratorStarted) 2021-05-05T18:45:35.032Z
eaee885b OrchestratorCompleted 2021-05-05T18:45:35.044Z
eaee885b 実行完了 2021-05-05T18:45:35.044Z "["Hello Tokyo!"","Hello Seattle!"","Hello London!""" 完了

テーブル列には、次の値が含まれています。

  • PartitionKey: オーケストレーションのインスタンス ID。
  • EventType: イベントの型。 すべての履歴イベントの種類の詳細については、「Durable Task Framework History Events」を参照してください。
  • タイムスタンプ: 履歴イベントの協定世界時タイムスタンプ。
  • 入力: 関数の JSON 形式の入力。
  • 名前: 呼び出された関数の名前。
  • 結果: 関数の出力 (具体的には、その戻り値)。

Warnung

この表はデバッグ ツールとして役立ちますが、Durable Functions拡張機能の進化に伴って、その形式と内容が変更される可能性があります。

タスクの完了を待機した後に関数が再開されるたびに、Durable Task Framework によってオーケストレーター関数が最初から再実行されます。 再実行のたびに、実行履歴を調べて、現在の非同期タスクが完了しているかどうかを判断します。 実行履歴にタスクが既に完了していることを示す場合、フレームワークはそのタスクの出力を再生し、次のタスクに進みます。 このプロセスは、実行履歴全体が再生されるまで続行されます。 現在の実行履歴が再生されると、ローカル変数は以前の値に復元されます。

特徴とパターン

次のセクションでは、オーケストレーター関数の機能とパターンについて説明します。

オーケストレーター関数のサブオーケストレーション

オーケストレーター関数はアクティビティ関数を呼び出すこともできますが、他のオーケストレーター関数も呼び出すことができます。 たとえば、オーケストレーター関数のライブラリから大規模なオーケストレーションを構築できます。 または、オーケストレーター関数の複数のインスタンスを並列で実行できます。

詳細と例については、「 Durable Functions (Azure Functions) のサブオーケストレーション」を参照してください。

持続的タイマー

オーケストレーションでは、遅延を実装したり、非同期アクションのタイムアウト処理を設定したりするために 、永続的タイマー をスケジュールできます。 言語ネイティブ sleep API ではなく、オーケストレーター関数で永続的タイマーを使用します。

詳細と例については、Durable Functions (Azure Functions) の Timers を参照してください。

外部イベント

オーケストレーター関数は、外部イベントがオーケストレーション インスタンスを更新するのを待機できます。 このDurable Functions機能は、多くの場合、人間の対話やその他の外部コールバックを処理するのに役立ちます。

詳細と例については、「 Durable Functions (Azure Functions) での外部イベントの処理」を参照してください。

エラー処理

オーケストレーター関数では、プログラミング言語のエラー処理機能を使用できます。 オーケストレーションコードは、try/catch のような既存のパターンをサポートします。

オーケストレーター関数は、呼び出すアクティビティまたはサブオーケストレーター関数に再試行ポリシーを追加することもできます。 アクティビティまたはサブオーケストレーター関数が例外で失敗した場合、指定された再試行ポリシーは自動的に遅延し、指定された回数まで実行を再試行できます。

注

オーケストレーター関数にハンドルされない例外がある場合、オーケストレーション インスタンスは Failed 状態で終了します。 オーケストレーションインスタンスが失敗した後は再試行できません。

詳細と例については、「 Durable Functions (Azure Functions) でのエラーの処理」を参照してください。

重要なセクション (Durable Functions 2.x)

オーケストレーション インスタンスはシングル スレッドであるため、オーケストレーション 内 で競合状態は問題になりません。 ただし、オーケストレーションが外部システムとやり取りする場合、競合状態が発生する可能性があります。 外部システムとのやり取り時の競合条件を緩和するために、オーケストレーター関数はロックを使って クリティカルセクション を定義できます。 .NETとJavaScriptはクリティカルセクションをサポートしています。

次のサンプル コードは、クリティカル セクションを定義するオーケストレーター関数を示しています。 クリティカルセクションに入るには、ロック状態を耐久的に管理する 耐久的エンティティへの参照を1つ以上渡す必要があります。 このオーケストレーションの 1 つのインスタンスのみが、クリティカル セクションのコードを一度に実行できます。

LockAsyncメソッドを使って重要なセクションに入力してください。

[FunctionName("Synchronize")]
public static async Task Synchronize(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var lockId = new EntityId("LockEntity", "MyLockIdentifier");
    using (await context.LockAsync(lockId))
    {
        // Critical section. Only one orchestration can enter at a time.
    }
}

.NET孤立したワーカーオーケストレーションでは、TaskOrchestrationContext.Entities.LockEntitiesAsync(.NET孤立APIマッピングを参照)を用います。

LockAsync メソッドは永続的なロックを取得し、破棄されたときにクリティカル セクションを終了するIDisposableを返します。 この IDisposable 結果を using ブロックと共に使用して、クリティカル セクションの構文表現を取得できます。 オーケストレーター関数がクリティカル セクションに入ると、そのコード ブロックを実行できるインスタンスは 1 つだけです。 クリティカル セクションに入ろうとするその他のインスタンスは、前のインスタンスがクリティカル セクションを終了するまでブロックされます。

重要なセクション機能は、永続エンティティへの変更を調整する場合にも役立ちます。 重要なセクションの詳細については、「エンティティの 調整」を参照してください。

注

クリティカルセクションは.NETおよびJavaScriptオーケストレーション用にDurable Functions 2.xで利用可能です。 .NETの場合、APIはモデルによって異なり、プロセス中はIDurableOrchestrationContext.LockAsyncを使い、孤立はTaskOrchestrationContext.Entities.LockEntitiesAsyncを使用します。

HTTP エンドポイントの呼び出し (Durable Functions 2.x)

オーケストレーター関数は、 オーケストレーター関数コードの制約で説明されているように、I/O操作を実行することはできません。 この制限を回避するために、I/O操作を行うコードはアクティビティ関数でラップします。 外部システムとやり取りするオーケストレーションでは、アクティビティ関数を頻繁に使用して HTTP 呼び出しを行い、結果をオーケストレーションに返します。

この一般的なパターンを合理化するために、オーケストレーター関数では、 CallHttpAsync メソッドを使用して HTTP API を直接呼び出すことができます。

分離ワーカー モデル
[Function("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    Uri url = context.GetInput<Uri>();

    // Make an HTTP GET request to the specified endpoint.
    DurableHttpResponse response = await context.CallHttpAsync(
        method: HttpMethod.Get,
        uri: url,
        content: null,
        retryOptions: null);

    if ((int)response.StatusCode == 400)
    {
        // Handle error codes.
    }
}

プロセス内モデル
[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Uri url = context.GetInput<Uri>();

    // Make an HTTP GET request to the specified endpoint.
    DurableHttpResponse response = 
        await context.CallHttpAsync(HttpMethod.Get, url);

    if ((int)response.StatusCode == 400)
    {
        // Handle error codes.
    }
}

基本的な要求および応答パターンをサポートするだけでなく、一般的な非同期HTTP 202ポーリングパターンの自動処理もサポートしています。 また、 マネージド ID を使用した外部サービスでの認証もサポートしています。

詳細および詳細な例については、 HTTP 機能を参照してください。

注

オーケストレーター関数から HTTP エンドポイントを直接呼び出すことは、Durable Functions 2.0 以降で使用できます。

アクティビティ関数に複数のパラメーターを渡す

複数のパラメータを直接アクティビティ関数に渡すことはできません。 代わりにオブジェクトの配列や複合オブジェクトを渡してください。

分離ワーカー モデル

.NET では、レコードなどのシリアル化可能な複合型を使用して、複数のパラメーターを渡します。

public record CourseInfo(string Major, int UniversityYear);

[Function("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    int universityYear = context.GetInput<int>();
    CourseInfo courseInfo = new("ComputerScience", universityYear);
    object courseRecommendations = await context.CallActivityAsync<object>(
        "CourseRecommendations", courseInfo);
    return courseRecommendations;
}

プロセス内モデル

.NET では、シリアル化可能な複合型を使用して複数のパラメーターを渡します。 次の例では、単純なクラスを使用します。

public class CourseInfo
{
    public string Major { get; set; }
    public int UniversityYear { get; set; }
}

[FunctionName("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var input = new CourseInfo
    {
        Major = "ComputerScience",
        UniversityYear = context.GetInput<int>()
    };

    object courseRecommendations = await context.CallActivityAsync<object>(
        "CourseRecommendations",
        input);
    return courseRecommendations;
}

.NETでは、レコード型やタプルを使って複数のパラメータを1つの複合オブジェクトとして渡します。

using Microsoft.DurableTask;

public record LocationInfo(string City, string State);

[DurableTask]
public class GetWeatherOrchestration : TaskOrchestrator<object?, string>
{
    public override async Task<string> RunAsync(TaskOrchestrationContext context, object? input)
    {
        var location = new LocationInfo("Seattle", "WA");
        string weather = await context.CallActivityAsync<string>("GetWeather", location);
        return weather;
    }
}

次のステップ