こんにちは、サイオステクノロジー技術部 武井です。シリーズでお送りしております「多分わかりやすいDurable Functions」ですが、いよいよ3回目、折り返し地点です。今回は、Durable Functionsの内部の動きについて説明します。
※全シリーズの記事一覧はこちら
- 多分わかりやすいDurable Functions 〜サーバーレスは次のステージへ〜【入門編】
- 多分わかりやすいDurable Functions 〜サーバーレスは次のステージへ〜【実践編】
- 今回はこちら → 多分わかりやすいDurable Functions 〜サーバーレスは次のステージへ〜【理論編】
- 多分わかりやすいDurable Functions 〜サーバーレスは次のステージへ〜【番外編】
Durable Functionsの大きな特徴の1つとして「Reliable Execution」というのがあります。日本語に訳すと「信頼性の高い実行」です。
Durable Functionsは、もちろんAzureのデータセンターで動いているのですが、そこにあるサーバーやネットワークは、常に安定して動くとは限りません。サーバーがいつ故障しても大丈夫なように、Durable Functionsは、どこまで実行したのか、それが成功しているのか失敗しているのかを、特定のタイミングで外部のストレージ(Azure Table Storage)に保存をしております。
サーバーが壊れた場合は、別のサーバーがAzure Table Storageから、実行状態を持ってきて、再度関数を実行します。これがDurable FunctionsがDurableと呼ばれる理由なのかな勝手に妄想しております。こちらも言葉だけだとわかりにくいかと思いますので、これより図を交えてご説明いたします。
Durable Functionsの動き
本章では、Durable Functionsの内部的な動きについて、ご説明します。今回の例では、入門編でご紹介した以下の処理を実現するDurable Functionsを動かした場合の動きを追ってみます。
URLのクエリパラメーターnumに指定された数字に2を足し、さらにその数字に2をかける処理を実行する。
■ その1:クライアント関数のControl Queueへの登録
まず、Durable Functionsが動くためには、「Control Queue」「Work Item Queue」「DurableFunctionsHubHistory」の3つが必要であり、その詳細は以下のとおりです。(その他にも必要なものがありますが、説明をわかりやすくするために、ちょっと簡略化してます)
Control Queue | オーケストレーター関数は、キュートリガーで動作するようになっており、Control Queueはオーケストレーター関数が起動するためのキューになります。 |
Work Item Queue |
アクティビティ関数は、キュートリガーで動作するようになっており、Work Item Queueはアクティビティ関数が起動するためのキューになります。 |
DurableFunctionsHubHistory |
アクティビティ関数の実行状態を記録するAzure Table Storageです。 |
まず、クライアント関数は、オーケストレーター関数を起動するために、Control Queueにメッセージを投げます。
■ その2:オーケストレーター関数の起動
Control Queueに投げられたメッセージをオーケストレーター関数が受け取り、オーケストレーター関数が起動します。
■ その3:オーケストレーター関数の状態の登録
オーケストレーター関数はDurableFunctionsHubHistoryテーブルに、自身の実行状態を登録します。EventTypeには、オーケストレーター関数全体が起動したことを表すExectionStarted、Nameにはオーケストレーターの関数名であるOrchestrator、Inputにはオーケストレーター関数の引数である2を登録します。
さらにこの後このオーケストレーター関数は何回か繰り返し実行(リプレイ)します。このリプレイの仕組は後述しますが、一回目のリプレイであることを表す状態を、EventTypeにOrchestratorStartedとして登録します。
■ その4:オーケストレーター関数のWork Item Queueへの登録
オーケストレーター関数は、アクティビティ関数Plus2を起動するために、Work Item Queueにメッセージを登録します。
■ その5:アクティビティ関数Plus2の実行開始要求の登録
アクティビティ関数Plus2の実行を開始を要求したことをDurableFunctionsHubHistoryテーブルに登録します。EventTypeにTaskScheduled、Nameに関数名であるPlus2を登録します。ここで一旦オーケストレーター関数は寝てしまいます。ここは非常に重要なポイントです。
■ その6:アクティビティ関数Plus2の起動
Work Item Queueへのメッセージの登録を受けて、アクティビティ関数Plus2が起動します。
■ その7:アクティビティ関数Plus2の実行終了状態の登録
アクティビティ関数Plus2の実行が終了しますと、DurableFunctionsHubHistoryテーブルにその状態を登録します。EventTypeにTaskCompleted、Resultに計算結果である4を登録します。
■ その8:アクティビティ関数のControl Queueへの登録
アクティビティ関数は、今、寝ているオーケストレーター関数を起こすために、Control Queueにメッセージを登録します。
■ その9:オーケストレーター関数の起動
Control Queueへのメッセージの登録を受けて、オーケストレーター関数が再び起き上がります。そして、またコードを最初から実行します。これをリプレイと呼びます。つまり、オーケストレーター関数はアクティビティ関数が実行される度に、一旦寝て、皿に起き上がり、また最初からコードを実行するのです。
■ その10:アクティビティ関数Plus2の実行状態チェック
そして今、再びアクティビティ関数Plus2を実行しようとしています。でも、これ、既に実行済みですよね・・・。なので、オーケストレーター関数は、DurableFunctionsHubHistoryテーブルを見て、アクティビティ関数Plus2が実行済みかどうかをチェックします。もちろん、実行済みですのでアクティビティ関数Plus2の実行をスキップします。
■ その11:オーケストレーター関数のWork Item Queueへの登録
アクティビティ関数MultiplyBy2を起動するために、オーケストレーター関数はWork Item Queueにメッセージを登録します。
■ その12:アクティビティ関数MultiplyBy2の実行開始要求の登録
アクティビティ関数MultiplyBy2の実行を開始を要求したことをDurableFunctionsHubHistoryテーブルに登録します。EventTypeにTaskScheduled、Nameに関数名であるMultiplyBy2を登録します。ここで、またまた一旦オーケストレーター関数は寝てしまいます。ここは非常に重要なポイントです。
■ その13:アクティビティ関数MultiplyBy2の起動
Work Item Queueへのメッセージの登録を受けて、アクティビティ関数MultiplyBy2が起動します。
■ その14:アクティビティ関数MultiplyBy2の実行終了状態の登録
アクティビティ関数Multiply2の実行が終了しますと、DurableFunctionsHubHistoryテーブルにその状態を登録します。EventTypeにTaskCompleted、Resultに計算結果である8を登録します。
■ その15:アクティビティ関数のControl Queueへの登録
アクティビティ関数は、今、寝ているオーケストレーター関数を起こすために、Control Queueにメッセージを登録します。
■ その16:オーケストレーター関数の起動
Control Queueへのメッセージの登録を受けて、オーケストレーター関数が再び起き上がります。そして、またコードを最初から実行します。つまり先ほどと同様、またリプレイされます。つまり、オーケストレーター関数はアクティビティ関数が実行される度に、一旦寝て、皿に起き上がり、また最初からコードを実行するのです。
■ その17:アクティビティ関数Plus2の実行状態チェック
そして今、再びアクティビティ関数Plus2を実行しようとしています。でも、これ、既に実行済みですよね・・・。なので、オーケストレーター関数は、DurableFunctionsHubHistoryテーブルを見て、アクティビティ関数Plus2が実行済みかどうかをチェックします。もちろん、実行済みですのでアクティビティ関数Plus2の実行をスキップします。
■ その18:アクティビティ関数MultiplyBy2の実行状態チェック
そして同様に、再びアクティビティ関数MultiplyBy2を実行しようとしています。でも、これ、既に実行済みですよね・・・。なので、オーケストレーター関数は、DurableFunctionsHubHistoryテーブルを見て、アクティビティ関数MultiplyBy2が実行済みかどうかをチェックします。もちろん、実行済みですのでアクティビティ関数MultiplyBy2の実行をスキップします。
■ その19:オーケストレーター関数終了
全てのアクティビティ関数の実行が終わったので、DurableFunctionsHubHistoryテーブルのEventTypeにExecutionCompletedを登録して、終了になります。
長くはなりましたが、以上がDurable Functionsの内部的な動きになります。
VMが壊れちゃったとき
先程の章で、Durable Functionsの動きはご理解頂けかと思いますが、なんでこんな面倒くさいことをしているのでしょうか?いちいちオーケストレーター関数やアクティビティ関数の起動のためにAzure Queue Storageを使ったり、実行状態を保存するのにAzure Table Storage使わないで、全部一台のVMでオンメモリで行ってしまえばシンプルかもしれません。でも、それだとVMが壊れちゃったときときに、復旧できなくなってしまいます。
ここで、先程の例を挙げまして、アクティビティ関数Plus2を起動しようとしたら、実はそのアクティビティ関数をホストしているVMが壊れていたことを想定します。
■ その1:VMの故障
アクティビティ関数Plus2を起動しようとしたら、実はそのVMが壊れてしまいました。
■ その2:新しいVMでの実行
Azureの素晴らしい仕組みによって、新しいVMが用意されました(詳しい動きはわかりませんが概ねこういうことだろうなと思っております)。そして、Work Item Queue内のメッセージを取得し、アクティビティ関数Plus2を起動します。
実はそのVMが壊れてしまいました。
■ その3:アクティビティ関数Plus2の実行状態の取得
アクティビティ関数Plus2の実行状態を取得するため、DurableFunctionsHubHistoryにアクセスします。これはまだ未実行(TaskScheduledのみでTaskCompletedがない)なので、実行する必要がアリそうです。
もし、これがオンメモリで全て行われていたなら、VMが壊れた場合に、その状態を復旧することができません。Durable Functionsは、アクティビティ関数が起動するごとにその状態をAzure Table Storageにチェックアウトしてリプライしているからこそ、信頼性の硬い実行が実現できます。これがDurable Functionsが「Durable」と言われる理由の1つです。
ちなみに
今までご説明した動きなのですが、Visual Studioでアクティビティ関数のところにブレークポイントを打ってデバッグ実行しながら、Azure Storage Exploreでその状態を見ることで、把握ができます。
まとめ
今回は、理論編と題しまして、Durable Functionsの内部的な動きをご説明しました。自分で、いろいろ動かしてみたり、MSのドキュメントを見たりして、概要は把握したつもりですが、もし、間違い等ありましたら、ご指摘頂けますと幸いですm(_ _)m
次回は、番外編と題しまして、Durable Functionsを使って、ちょっと面白いことをしようかなと思っております。