ちょっと話題の記事

Amazon Simple Workflow Service (SWF) 入門

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

よく訓練されたアップル信者、都元です。今回は、Amazon Simple Workflow Serviceについてご紹介します。これが一体どんなサービスなのか、本エントリを読めば、きっとあなたの道具箱にも新しいコンポーネントが増えることと思います。

ワークフロー

humanworkflow

「ワークフロー」が何なのかが分からなければ、SWFを理解するのは難しいので、まずここから。

一般に、ワークフローシステムと言うと、会社の管理業務における各種申請稟議の決裁に関する仕組みを思い浮かべます。申請の種類によって、上長や関連部門長の判断を仰ぎ、その結果、申請が通ったり却下されたりします。この申請はいつ誰の権限で承認されたのか、この申請は誰によってなぜ却下されたのか、そういった証跡を軸に内部統制を強化したり、業務の効率化を図るためのシステムです。各段階において、人と人とのフローを制御するため、これをヒューマンワークフローと呼んだりもします。

これをもう少し一般化すると、次々に流れてくる仕事(ワークアイテム)を複数のサブシステム(worker)が連携しながら処理していくシステムになります。注文を請けたら、確認メールを送って、在庫を確保して、カード決済をして、伝票を印刷して…。その最も単純な形は、一直線のフローを各workerが逐次処理する「流れ作業」です。しかし現実には一直線の単純なフローというのはあまりなく、workerによる処理の失敗やタイムアウトが発生したり、失敗時はリトライしたり別ルートによるリカバリーのフローに切り変わったり。キャンセルがあったり変更があったり。優先があったり保留があったり。このような仕組みの上で、各々のサブシステムによる処理はいつ、どんな風に実行されたのか、その履歴を追跡出来る…。このように、システム間のフロー制御は、ヒューマンワークフローに対して、システムワークフローと呼んだりするようです。

さて、このようなワークフローシステムを丸腰で構築しようとしても、各ワークアイテムの状態や履歴の管理(今までどんな履歴を辿って来て、今どのような状態にあるのか)や、フロー制御(次にどのステップに進むべきなのか)の記述はかなり複雑なものになります。さらに、細かな例外処理等を組み込んだり、キャンセルのフローを追加するなど、フロー定義自体が複雑になると、下手な設計では簡単に破綻してしまうことでしょう。そこで、このような処理様式を抽象化し、ワークアイテムの状態や履歴を管理する部分を共通化したり、フロー定義の記述を標準化したりする動きが出てきました。その結果生まれたのがビジネスプロセス管理(BPM)と呼ばれるシステムです。BPMのうち、定義済みのフローに従って、実際にワークアイテムの処理を行う実行基盤をワークフローエンジンと呼びます。

スケーラブルなワークフロー実行基盤

では、このシステムにおいて大量のワークアイテムが発生する状況を考えてみましょう。さらに、各workerの計算量も要求に応じて増大していくことを考えてみましょう。そうなると欲しくなるのがスケーラビリティですね。workerを複数のノードに分散し、並列処理を可能にしたい。さらに、状況に応じてworkerのノード数を増減させたい。

しかし、ノード間の通信に失敗したらどうする? workerから応答が無くなったら? タイムアウトの処理は? …これらも丸腰で実装しなければならないとしたら、冷や汗モノです。しかし、こういうのはAWSの十八番な訳です。こういったワークフローシステムを、スケーラブルに実装するための基盤、これがAmazon SWFです。

SWFの概念

simpleworkflow

SWFにおける登場人物

ここで、SWFの簡単な構成図を示します。登場人物は、SWF以外では starter, decider, worker の三者です。この三者をまとめてactorと呼びます。これらactorの間で、ワークアイテムがやり取りされます。

starterはワークアイテムを作成して、ワークフローシステムに発行する役割を担います。また、複数のstarterから並列してワークアイテムを発行しても構いません。

ワークアイテムにはstarterからの「入力データ」の他に「イベント履歴」と呼ばれる情報を持っています。スタート直後はほぼ空っぽですが、フローが進むにつれて、どんな処理を経て来たのか、そしてその結果はどうだったのか、といった履歴が追記されていきます。

ちなみに、ワークアイテムのことをSWF用語的にはWorkflowExecutionと呼びます。しかし、一般的な話から始めてしまった都合上、本エントリーではひとまずワークアイテムで通そうと思います。

workerは、フローの各段階における実際の処理を行う役割を担います。また、workerが実行する処理のことをactivityと呼びます。担当するactivityによって、複数種類のworkerがいても構いません。例えば activity-A→B→C という流れ作業の場合、activity-Aを担当するworker、activity-Bを担当するworker…のような体制です。さらに、同種のworkerが複数居ても構いません。

deciderは、それぞれのワークアイテムの状態(イベント履歴等)に従って「次はどのactivityを実行すべきか」を決定する役割を持ちます。SWFは、ことあるごとに「次どうしたらいい?」とdeciderに問い合わせ、その結果に従ってワークアイテムの次の処理をスケジューリングします。deciderもworkerと同様、複数ノードによる並列処理が可能です。

actorは、ステートレスに実装する必要があります。つまり、各actorがお互いの事を意識しないまま、仕事を完遂できるような実装です。つまり、ワークアイテムのイベント履歴情報を元に処理内容を決定することになります。ステートレス性により、各actorは並列化ができますので、スケーラビリティを確保できます。

SWFの挙動解説

以上を踏まえ、SWFの実際の動きを見ていきます。

まず、(1) deciderとworkerはロングポーリングでSWF APIと繋がります。つまり、SWFにHTTPリクエストを投げますが、APIはなかなかレスポンスを返しません。処理が必要なタイミングになって初めてレスポンスが返される仕組みです。ただし、60秒間何もなければ一旦コネクションが閉じられます。deciderやworkerは、APIにロングポーリングの接続を張り続けるループを実装する必要があります。

deciderとworkerの体制が整った状態で、starterは必要に応じて (2) ワークアイテムを発行します。すると、SWFは (3) deciderに対してロングポーリングに対するレスポンスとしてワークアイテムを返し、次にすべき事を問い合わせます。この問い合わせを「DecisionTask」と呼びます。deciderは、ワークアイテムの「入力データ」や「イベント履歴」に従って、次に実行すべきactivityを決定します。例えば「次はactivity-Aを実行する」という決定です。決定の際、場合によってはDB等も参照しても良いかもしれません。最後に、(4) この決定をSWFのAPIに通知します。これがdeciderの仕事の1単位です。仕事を終えたdeciderは再びAPIに対してロングポーリングを張り、次のDecisionTaskに備えます。

(4)のレスポンスを受けたSWFは、その決定を「イベント履歴」に追記します。またこの決定に従い、(5) activity-Aを実行できるworkerに対してロングポーリングのレスポンスとしてワークアイテムを返します。これを「ActivityTask」と呼びます。workerは、ワークアイテムの「入力データ」や「イベント履歴」に従って、activity-Aを実行します。activity実行の際、場合によってはDB等を参照/記録しても良いかもしれません。activityの (6) 実行結果をSWFのAPIに通知します。これがworkerの仕事の1単位です。仕事を終えたworkerは再びAPIに対してロングポーリングを張り、次のActivityTaskに備えます。

(6)のレスポンスを受けたSWFは、その結果を「イベント履歴」に追記します。その上でdeciderに次の処理決定を委ねます。これを必要なだけ繰り返し、最終的にdeciderが「次のactivityはナシ、これで完了です」という決定をすることにより、ワークフローが完了します。

以上、言葉で説明しようとするとかなり複雑ですが、概念としては非常にシンプルにまとまっていると思います。

上記の要素の他に、activityの種類によって「タイムアウト」を定義できます。例えば、workerのActivityTaskを振ったのだけど返事が来ないといった場合、指定した時間が経過すると、SWFは「workerが返事を寄越さないんだけど、どーする?」といったDecisionTaskを投げます。
さらに複雑なケースだと、長い時間が掛かるactivityがあったとします。その場合、activityを一生懸命実行中なのか、フリーズしているのかが非常に不安になりますね。実行中なら構いませんが、万一の場合は失敗時のフローにいち早く動かなければなりません。そういった場合は、workerがSWFに対して定期的にHeartbeatを送り「ちゃんと生きてるよ、まだ仕事終わらないけど」というアピールをします。SWF側は、Heartbeatが一定時間以上途切れたら、何か異常事態が起こっていることを認識できる仕組みです。

その他SWFの重要概念

以上の通り、SWFにおいて、activityはワークフローを構成する部品になります。この部品がdeciderによって組織化されたものがワークフローです。場合によっては、複数のワークフローが同じactivityを共有するかもしれません。そういった、関連する要素(activity + workflow)を束ねる役割を果たす概念を、SWFにおいてはDomainと呼びます。

また、DecisionTaskやActivityTaskのスケジューリングは、TaskListと呼ばれるキューのような場所で行われます。それぞれのタスクはTaskListにキューイングされ、各decider及びworkerに振り分けを行います。応用的な使い方ですが、複数のTaskListを用意することによって、ワークアイテムのルーティング制御(例えば優先度等を制御したり)も可能です。

まとめ

おつかれさまでした。以上が、SWFの基礎です。次回は、この知識を踏まえて、実際にSWFでワークアイテムの処理をしてみようと思います。お楽しみに。