しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Durable Functions v1.6 の Rewind API を使うと失敗した関数から再実行が可能に

Azure Functions v2 の Breaking changes に合わせて Durable Functions v1.6 がリリースされました。

今回の Breaking changes は Run From Packages で配布している場合に地味に大変でしたが、その話はまた今後どこかで書こうかと思いますが需要なさそう。

オーケストレーションのパフォーマンスが改善してたりするみたいなので、とりあえずアップデートはしておいた方が良いでしょう。Azure Functions v2 を使ってる場合は必須ですよ。

さて、メインは新しい Functions Runtime への対応っぽいですが、リリースノートには非常に興味深い機能が追加されていると書かれていました。失敗したオーケストレーションをやり直す機能です。

Rewind failed orchestrations (preview, #301): A new API has been added which allows you to "rewind" failed orchestration instances back to their previously good state. This can be a life-saver when you have long-running orchestrations that fail in unexpected ways.

Release Durable Functions v1.6.0 Release · Azure/azure-functions-durable-extension · GitHub

まだプレビューなので不具合があるかも知れないですが、これは待望の機能です。

これまで Durable Functions は Event Sourcing で実行の履歴が全て管理されているのに、失敗した場合には新しく最初からやり直すしか方法が無い点に不満を持っていました。

Added rewind endpoint to HTTP client response links by cmkerner20 · Pull Request #341 · Azure/azure-functions-durable-extension · GitHub

ちなみにこの Rewind 機能を実装したのはインターンの方らしいです。素晴らしいコントリビューションとして言いようがないですね。

使い方はまだドキュメントが公開されてないですが、実装を見ればすぐにわかりました。普通にオーケストレーションを開始すると、レスポンスに rewindPostUri が追加されています。

f:id:shiba-yan:20180831184209p:plain

オーケストレーションが失敗した場合、このエンドポイントを POST で叩くと再実行されます。

とりあえず実際に動作を試して見ました。サンプルなので適当なオーケストレーターとアクティビティを用意して、2 つ目のアクティビティはわざと例外を投げて落ちるようにしています。

[FunctionName("Function1")]
public static async Task RunOrchestrator([OrchestrationTrigger] DurableOrchestrationContext context)
{
    var value = context.CurrentUtcDateTime.Ticks.ToString();

    await context.CallActivityAsync("Function1_Task1", value);
    await context.CallActivityAsync("Function1_Task2", value);
    await context.CallActivityAsync("Function1_Task3", value);
}

[FunctionName("Function1_Task1")]
public static void Task1([ActivityTrigger] string value, ILogger log)
{
    log.LogInformation($"Task1 running: Input {value}.");
}

[FunctionName("Function1_Task2")]
public static void Task2([ActivityTrigger] string value, ILogger log)
{
    throw new Exception();
    //log.LogInformation($"Task2 running: Input {value}.");
}

[FunctionName("Function1_Task3")]
public static void Task3([ActivityTrigger] string value, ILogger log)
{
    log.LogInformation($"Task3 running: Input {value}.");
}

アクティビティが失敗して、オーケストレーションが Failed になったのを確認後、Rewind API を叩いて再実行をリクエストします。reason には適当に理由を入れておけば、ログに出力されるので分かりやすいです。

実際にどのように実行されているかはログを見ないと理解しにくかったので、Application Insights でクエリを書いて分かりやすく並べました。まずは初回の失敗したログです。

f:id:shiba-yan:20180831183645p:plain

Function1_Task2 が例外を投げたので、オーケストレーターも Failed していることが確認できますね。

次に Rewind API の実行後のログです。Instance Id は Rewind 後も変化しないので追跡が可能です。

f:id:shiba-yan:20180831183652p:plain

オーケストレーターのステータスが Rewound に変化して、再実行が行われています。

前回の実行で成功した Function1_Task1 は再実行されずに Function1_Task2 から実行されていることが確認できます。もちろん状態も前回のままなのでオーケストレーターの作法に従って CurrentUtcDateTime などを使っていれば、安全に再実行が行えます。

アクティビティのコードに不具合があった場合には、修正をデプロイ後 Rewind API を使えば失敗した処理から続きを実行出来るので、リカバリの選択肢が広がりそうです。もちろん外部の API を叩いている場合も同様に使えるでしょう。

個人的にはこれで Durable Functions は完成形になったのではないかと思っています。使わないと損します。