眠いしお腹すいたし(´・ω・`)

C#関連を主に書きます。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Durable Functionsで学ぶクラウドデザインパターン -Scheduler Agent Supervisor Pattern-

長らくお待たせ致しました?

Durable Functionsで学ぶクラウドデザインパターンシリーズ

前回の記事を書いてから、Durable FunctionsはGAされて(2018.06.03現在、V1のみ V2はPreview)現在のバーションは1.4.1。

その間にDurable Functionsの生みの親、Chris Gillum (@cgillum) | Twitterさんがde:codeで日本語で登壇したりしてます。

もう使うしかない機運漂うDurable Functions、ガンガン触っていきましょう。

そして要望が出たり問題を見つけたらイシューからのプルリクだo(・`д・´。)

さてさて今回は第3弾として「Scheduler Agent Supervisor パターン」を学んでみましょう。

Scheduler Agent Supervisor

docs.microsoft.com

さて、このデザインパターン、これまで紹介したCompensating Transaction PatternPipes and Filters Patternに比べると、解決したい問題などが回復性に寄っていて複雑です。

そうです、今流行りのワード?「回復性」です。

システムの回復性に関しては

回復性に優れた Azure 用アプリケーションの設計 | Microsoft Docs

NoOpsで高可用性・ハイスケールシステムを自律運用させよう! 実現に必要な3つのポイント【デブサミ2018】 (1/2):CodeZine(コードジン)

システム運用は自動化が常識に、NoOpsまでの歴史 | 日経 xTECH(クロステック)

色々な記事が見つかるかと思います。

個人的にシステムは作成する上で非常に重要な概念であり、これはオンプレミスだろうとクラウドだろうとサーバレスであろうと障害との付き合いは普遍的であり、その中でもっとも目指すべき方向の一つであると考えています。

もちろん「運用は人の手を使ってやれば良いのだ。そんなアーキテクチャを考えて実装するより下請けを安く買い叩けば良いのだ」なんていう考えであれば話は別ですが、現実の中で人の手をできる限り少なく運用できる事はエンジニアのQOLにも直結する大切な問題であると思うのです。

少し脱線しましたが、具体的にScheduler Agent Supervisor Patternを見てみましょう。

分散された一連のアクションを 1 つの操作として調整します。 いずれかのアクションが失敗した場合は、全体の操作が全体として成功または失敗するように、その失敗を透過的に処理しようとするか、または実行された作業を元に戻します。 これにより、一時的な例外、長期間続く障害、プロセスのエラーなどのために失敗したアクションを復旧して再試行することが可能になるため、分散システムに回復性が追加される場合があります。

ワケ⊂(´-` ) ワカ( ´-`)つ ラン♪⊂(´ヘ`)つ

もっと私みたいな日本語が不自由な人でもわかる日本語で書いてもらいたいですね( -`ω-)

仕方がないので詳細な説明の方を見て見ましょう。

アプリケーションは、その一部でリモート サービスが呼び出されたり、リモート リソースにアクセスしたりする可能性のあるいくつかの手順を含むタスクを実行します。 それぞれのステップは互いに独立しているかもしれませんが、それらを指揮するのは、タスクを実装するアプリケーションのロジックです。

要するに、アプリケーション(サービス)は処理の中でサービスのリポジトリにだけアクセスするのではなくて外部のAPIを呼び出したりする事もありますよね? それらをコントロールするのはアプリケーションのロジックになりますよね?

っていう事でしょう。そりゃそうでしょう。

可能な場合は常に、アプリケーションはタスクが完了まで実行されるようにし、リモート サービスまたはリソースへのアクセス時に発生する可能性のあるすべての障害を解決する必要があります。 障害は、さまざまな原因で発生することがあります。 たとえば、ネットワークが停止したり、通信が中断されたり、リモート サービスが無応答または不安定な状態になったり、おそらくリソースの制約のためにリモート リソースが一時的にアクセスできなくなったりすることがあります。 多くの場合、障害は一時的であり、再試行パターンを使用して処理できます。

アプリケーションは外部APIへのアクセスやリポジトリへのアクセスで発生する可能性がある障害を、なんとかして処理が完了するように実装する必要がありますよね?

これらの原因には通信が中断されたり外部APIが不安定であったりスロットリングに引っかかったりすることがあるよね?

多くの場合は、こういう障害は一時的に発生するものだから再試行パターンで処理可能だよね?

アプリケーションは、容易に復旧できないより永続的な障害を検出した場合、システムを整合性のある状態に復元し、操作全体の整合性を保証できる必要があります。

でも、障害が長引くような感じならシステムを問題ない状態までロールバックしたり一時停止したりしてロジック全体で辻褄が合うように戻してあげる必要があるよね?

みたいな感じでしょうか。

まぁ要約するとロジックのなかで処理毎にリトライ処理をするのは当然として、短期間リトライでなんとかならない場合には、長時間待機し障害普及ごにリトライしたり補償トランザクションで整合性を保つようにロールバックするなどの工夫ができるとCOOL!!って感じでしょうか。

これに対する解決策として

https://docs.microsoft.com/ja-jp/azure/architecture/patterns/scheduler-agent-supervisor#solution

を書いています。

はい、ここでこの文を読んで「あれ??」って思った方、それです m9⊃゜∀゜)!!

ここで言っているSchedulerとAgentをOrchestratorとActivityに置き換えて見ましょう。

あら不思議!! Durable Functionsを使うだけでSchedulerとAgentになってしますのです。

あぁ Durable Functions まじCOOL(●´∀`●)

残るはSuperVisorを用意するだけで良いのです。

サンプル

github.com

今回のサンプルはドキュメント内で例としてあげられてる

https://docs.microsoft.com/ja-jp/azure/architecture/patterns/scheduler-agent-supervisor#example

物をDurable Functionsで作成して見ました。

ECで注文を受けた場合データベースに受注情報を書き込みリモートサービスをコールする。

リモートサービスをコールする箇所でのエラーが発生した場合にリトライ処理を行う。

しかしリトライが閾値を超えた場合には一時停止しアラートを上げる。

ユーザは問題の解決後にリトライを行うことができる。

みたいな感じです。

今回は複雑なので少し図を描いてみました。

まずは正常時の処理ですが非常に単純です。

f:id:tamafuyou:20180603205720p:plain

OrderRequest関数でリクエストを受け付けて(ECウェブアプリケーションからのコールを想定)Orchestratorを起動しStorageTableに受注情報を保存し外部APIをコールして終了します。

次に外部APIをコール時にAPIをコールしリトライしても解決しなかった場合のアーキテクチャの図です。

f:id:tamafuyou:20180603210123p:plain

CallRemoteServiceが正常に終了しなかった場合、失敗レポートとしてFailReportテーブルにInstanceIdなどの情報を記録します。

その後OrchestrotorはWaitForExternalEventをして外部からRiseEventされるまで待機状態となります。

FailReportWatcher関数はTimerTriggerで動いていて定周期でFailReportテーブルを監視しています。

FailReportテーブルにデータを見つけるとSlackにアラートの通知を行います。

その際にRetry用のリンクも一緒にメッセージとして送信します。

ユーザは外部APIの障害が解決後にそのリンクをクリックします。

リンクはFailReportRetry関数のURLであり、関数内でFialReportに記述されているInstanceIdに対してRiseEventを行います。

RiseEventが行われるとOrchestratorが再び動き出しCallRemoveServiceから再開されます。

コード解説

ではOrchestratorを見てみましょう。

gist.github.com

上記のアーキテクチャを読むとなんとなくOrchestraorもわかると思います。

ポイント1

CallRemoteService Activityの呼び出し際にCallActivityWithRetryを使用しています。 これを使用する事でExponential backoffなトライ処理を行う事ができます。

ポイント2

CallRemoteServiceの呼び出しでリトライをしたけれど成功しなかった場合にWaitForExternalEventで待機状態に移るのですが、その際にタイムアウトを5日として待機しタイムアウトした場合には補償トランザクション処理を行っています。

Activityに関してなのですが各々はとてもTableにデータを書き込んだりAPIにアクセスしたりしているだけで単純ですので解説は省きます。

終わりに

どうでしょう?SchedulerAgentSupervisorパターンの理解は進みましたでしょうか?

ここまで読まれて頂けたのであれば一見するとかなり複雑そうなアーキテクチャもDurable Functionsを使用するとかなり簡潔に実装する事ができる事、Durable Functionsのパワーを実感して頂けたのではと思っております。

今回のサンプルは実戦ではもっと考慮するべき事があるのですが省いています。

実戦で使用する時にはどこまで考慮するべきか・・・検討してみるのも1つの勉強になるのではないかと思います。

いやぁ、Durable Functionsって本当にいいものですねぇ

それではまた次回をお楽しみに、サヨナラ、サヨナラ、サヨナラ!

おまけ

Durable Functionsの公式Wikiに記事のリンクを記載いただけました。

Home · Azure/azure-functions-durable-extension Wiki · GitHub

v(。・・。)イエィッ♪