Corda 5の高可用性(HA)が何を意味するのか/Corda 4からどのように大きく進歩したのか
はじめに
Corda 5の主要な機能の1つは高可用性(HA)です。高可用性システムとは、運用中に発生する可能性のある障害に対応し、継続稼働を保証するものです。これを実現するためには、システムに冗長性を持たせる必要があります。この記事では、Corda 5がこれまでのCordaのリリースと比較してどのような高可用性を提供し、それがFlowの実行のコンテキストでどのように実装されているかを説明します。
さて、高可用性を考えるために、まずCorda 5のアーキテクチャについて説明しましょう。
ワーカーアーキテクチャ
Corda 5は以前のバージョンのCordaとは根本的に異なるアーキテクチャを使用しています。下図はCorda 5のクラスタがどのように構成されているかを示しています。Corda 5はCordaの実行の異なる側面を担当するいくつかのワーカープロセスで構成されています。
RPCWorker: 特定の仮想ノードに代わってシステムを利用するリクエストを処理します。
FlowWorker: ビジネスワークフローを実行します。
DBWorker: クラスタデータベースとのやりとりを処理します。
CryptoWorker: 署名などの機密性の高い暗号化操作を処理します。
また、クラスタ横断通信やネットワークメンバーシップを処理するための追加のWorkerも利用可能です。これらのWorkerはKafkaを介して通信し、プロセス間の中央メッセージバスとして機能します。Kafkaは異なるプロセス間でメッセージをストリーミングするための業界標準のソリューションであることから、Corda 5のWorker通信の仕組みとして採用されました。
また、Kafkaはデータのロードバランスも実現しています。ということで、次にロードバランシングについて見ていきましょう。
ロードバランシング
Corda 5はどのような可用性を提供するのでしょうか?
HAを提供するために、Cordaは各コンポーネントごとに冗長性を持たせなければなりません。完全なクラスタを提供するのであれば、より多くのインフラが必要となり、当然コストがかかります。これは避けることができません。もっとも、(ワーカーアーキテクチャを採用しているため、)ただシステムを動作させるだけであっても、インフラ側でそれ相応の準備が必要です。
可用性を実現するために、CordaはKafkaが提供するロードバランス機能を利用します。Workerがイベントストリームを購読(Subscribe)すると、Kafkaブローカーはストリームの一部をそのWorkerに割り当てます。そして、各ワーカーのインスタンスがメッセージの処理を担当するようになります。(この設定は、ホット/ホット、アクティブ/アクティブと呼ばれる冗長性の実現です。)
この方法の利点は、ワーカーインスタンスに障害が発生したときに明らかになります。障害発生時、すでに別のワーカーインスタンスが稼働していれば、スラック(障害に見舞われたワーカーが処理すべきストリーム)をピックアップすることができます。Kafkaはこの場合、ストリームの分配をリバランスし、障害が発生したインスタンスが以前ピックアップしていたストリームのパーティションを、まだ稼働しているインスタンスに割り当てます。これにより、システムはフェイルオーバーすることができます。
(訳注:用語が少し専門的です。例えばこちらなどを参考にしてください)
Corda 4との対比
Corda 4をご存知の方は、なぜこのスキームがCorda4ではできないのか不思議に思われるかもしれません。
Corda 4のノードは、複数のワーカープロセスに分かれているのではなく、1つのモノリシックなノードになっています。なぜ単純に複数のインスタンスを実行できないのでしょうか?問題は、ロードバランシングとフェイルオーバーです。2つ目のCorda 4インスタンスを用意することは可能ですが、負荷分散に関する仕組みがないため、システムパフォーマンスの面で何のメリットもありません。単に、ノードの運用コストを単純に増加させることになります。Corda 4はアクティブ/スタンバイのセットアップを実現することができるだけで、2番目のインスタンスは準備ができているが何も処理をしていない状態になります。実際、Corda 4は技術的な理由でホット/ホットで動作させることができないため、可能な限り最高の可用性シナリオはアクティブ/スタンバイです。
しかし、この仕組みは、フェイルオーバーに要する時間がかなり長くなり、フェイルオーバー中はシステムが利用できなくなることを意味します。
Corda 5 はここからかなりのステップアップとなります。
Flow
ここで焦点を変えて、CordaのFlowのことを考えます。特に高可用性のシステムの上でどのように適切に実行できるかを見ていきましょう。
FlowはあらゆるCorDapp (Cordaを使った分散アプリケーション)の基礎となるものです。Flowはビジネスロジックの一部を表します。Flowは、取引先間の通信の課題を抽象化し、ビジネスロジックを単純で連続したコードのように書くことができるAPIを提供しています。もちろん、その裏側で、Flowを弾力的に実行できるように多くの作業を行っています。
Cordaはこの弾力性を確保するためにCheckPointという解決策を採用しています。Flowがコード内の適切なポイントに到達すると、プラットフォームはFlowの実行を一時停止して現在の状態を記録し、CheckPointオブジェクトを生成します。このオブジェクトは、取引先からのメッセージの到着やデータベースへのリクエストの完了など、Flowを再開できる状態になったときに再び使用することができます。また、Flowの実行の途中でプラットフォームに何らかの障害が発生した場合、以前のチェックポイントからFlowを復元することができるようになっています。
FlowはCorda 4でも同じように存在していました。しかし、Corda 5ではFlowを実現するにあたって新しい課題が発生しています。
1つのノードが一度にリクエストを処理するのではなく、複数FlowWorkerが潜在的に存在します。Workerの1つが故障した場合、Flowは別のものに拾われ、継続されることが望まれます。Corda 5はどのようにこの機能を実現したのでしょうか?解決策を理解するためには、Corda 5がどのようにKafkaを使用しているかを深く掘り下げる必要があります。
メッセージパターン
Corda 5はメッセージパターン・ライブラリと呼ばれるKafka上の抽象化レイヤーを内部で使用しています。これはCorda 5がWorker間の通信に使用する共通のメッセージパターンをカプセル化したものです。ほとんどのパターンでは、Kafkaの設定をそのまま適用することができます。しかし、Flowはいくつかの特別な作業を必要とするユニークな課題を提示します。
(※訳注:こちらのセクションの基礎知識として、Flowの状態遷移に関する記事もございます。)
Flowは、CheckPointと、そのCheckPointを変化させる一連のイベントから構成されます。FlowWorkerはイベントをInputとして受け取ると、そのInputイベントに対応するCheckpointを読み込んで、(※訳注:そのCheckpointに対応した状態にある)Flowを再構成しなければなりません。
そこから、新しいCheckPointを生成するのに適したポイントに到達するまで、Flowのコードを実行します。
このとき、次に到達する新しいチェックポイントはチェックポイントに到達した際出力される全てのOutputイベントと一緒に生成される必要があり、これらは、入力が消費されたことを示すとともに、Kafkaにアトミックに書き戻されなければなりません。2種類のイベント(Input イベントとCheckpoint)が互いに同期している必要があります。Kafkaのメッセージパターン・ライブラリは、この種のユースケースを処理するための専用パターンが用意されています。これをstate and eventパターンと呼びます。このパターンにより、workerが処理しようとする入力イベントに対して、正確にCheckpoint(=state, 状態)を割り当てることができるようになります。
Kafkaのstate and eventパターンについては、今後のブログ記事で詳しく説明する予定です。
フェイルオーバー時のFlowの実行
Checkpointとイベントのパターンについて学んだので、ようやく、Flowのフェイルオーバーがどのように機能するかを見ることができます。Flowを処理するFlowWorkerに障害が発生すると、そのワーカーに割り当てられていたイベントパーティション(※訳注:Kafka用語です)が、Kafkaによって生き残ったフローワーカーに再分配されます。この新しいワーカーは、トピックに対応するCheckpointを自分自身に割り当てます。これで、FlowWorkerは、障害が発生したFlowWorkerから当該作業を肩代わりできるようになりました。
上図の左側に、障害発生前のFlowWorkerが表示されています。各ワーカーは3つの仮想ノード、Alice、Bob、Charlieのイベントを処理しています。(この場合のAliceのように、同じ仮想ノードに対するすべてのFlowが同じWorkerによって処理されるとは限らないことに注意してください)。
各Flowは、前の図と同様に、仮想ノードと識別子(この場合は番号)で識別される。1つのWorkerに障害が発生すると、ステートとイベントパーティションは同期して別のWorkerに割り当てられます。この状況を表しているのが右図のシナリオです。重要な点は、Flowの完全な状態は、Worker自体ではなく、FlowCheckpointに含まれていることです。その結果、新しく割り当てられたWorkerは、前のWorkerが去ったところから正確に引き継ぐことができます。
Kafkaを注意深く設定することで、イベントを消費したとマークすることと、そのイベントを処理した出力をKafkaに書き戻すことがアトミックになります。誤って入力イベントを失ったり、古いFlowの状態を誤って上書きしたりすることがないようにすることができます。
まとめ
この記事ではCorda 5の高可用性(HA)が何を意味するのか、そしてそれがCorda 4からどのように大きく進歩したのかを説明しました。また、CordaのFlowについて少し掘り下げ、新しいアーキテクチャがどのように弾力性のあるワークフローをサポートし続け、さらにそれらを実行するプロセスのフェールオーバーを許容するのかを説明しました。
<ご質問・ご要望の例>
- Corda Portalの記事について質問したい
- ブロックチェーンを活用した新規事業を相談したい
- 企業でのブロックチェーン活用方法を教えて欲しい 等々
SBI R3 Japan エンジニアリング部長
書籍出してます:https://amzn.asia/d/c0V31Vd
趣味:サッカー、ガンプラ、ドライブ、キャンプ