リアクティブ宣言

用語集

非同期

オックスフォード英語辞典は、非同期 (asynchronous) を「同時に存在したり起きたりしないこと」と定義している。リアクティブ宣言の文脈では、「クライアントからサービスへ送信されたリクエストが、送信後の任意の時点で処理されること」を意味する。送信先のサービス内でのリクエスト処理の実行を直接クライアントが観測したり、それに対して同期を取ることはできない。非同期の対義語である同期処理では、サービスがリクエストを処理するまでクライアントは自身の実行を再開しない。

バック・プレッシャー

あるコンポーネントが全体に追いつけなくなった場合、システム全体として何らかの対処をする必要がある。過負荷状態のコンポーネントが壊滅的にクラッシュしたり、制御無くメッセージを損失することは許されない。処理が追いつかなくなっていて、かつクラッシュすることも許されないならば、コンポーネントは上流のコンポーネント群に自身が過負荷状態であることを伝えて負荷を減らしてもらうべきだ。このバック・プレッシャー (back-pressure) と呼ばれる仕組みは、過負荷の下でシステムを崩壊させず緩やかに応答を続ける重要なフィードバック機構だ。バック・プレッシャーはユーザまで転送してもよく、その場合、即応性 (resilient) は低下するが負荷の下でのシステムの耐障害性が保証される。また、システムがその情報を使って自身に他のリソースを振り向け、負荷分散を促すこともできる。弾力性を参照。

バッチ処理

現在のコンピュータは同じタスクを繰り返し実行することに最適化されている。命令キャッシュや分岐予測によって、クロック周波数を一定に保ったまま一秒間に処理できる命令数を増加することができるためだ。そのため、同じ CPU コアへ異なるタスクを立て続けに与えてしまうと、この最適化によって得られる性能をフルに引き出すことができない。もし可能ならば、異なるタスクを交互に実行する頻度を少なくするようにプログラムを構成するべきだ。つまり、データ要素を一つの集合にしてバッチ (batching) で処理したり、異なる処理ステップを専用のハードウェアスレッドで実行したりすればよい。

同期や協調を必要とする外部リソースを使用する際も、同様の論理が当てはまる。永続的ストレージ機器が提供する I/O 帯域幅は、単一のスレッド(と CPU コア)がコマンドを発行することにより、全てのコアに帯域幅を争わせる場合よりも劇的に改善する。この方法のさらなる利点は、機器へのエントリポイントが単一なので、機器にとって最適なアクセスパターンへより適合するように操作の順序を入れ替えられることだ。

さらに、バッチ処理により I/O のような高価な操作や高価な計算のコストを分配できることがある。例えば、複数のデータ要素を同じネットワークパケットやディスクブロックにまとめることで、効率の向上と利用の削減ができる

委譲

タスクを非同期に他のコンポーネント委譲 (delegation) すると、そのタスクの実行は委譲先のコンポーネントのコンテキストで行われる。つまり、委譲されたタスクの実行は異なるエラー処理コンテキスト上や、異なるスレッド内、異なるプロセス内、異なるネットワークノード上で行われることがある。コンポーネントは、タスクを処理する責任をその他のコンポーネントへ委譲して他の処理を実行できるし、あるいは障害処理や進捗報告といった動作が必要な場合に、委譲したタスクの進捗を観察することもできる。

コンポーネント

ここで説明するモジュラー・ソフトウェア・アーキテクチャ (modular software architecture) は、とても古くからある概念だ。例えば Parnas (1972) [ACM] を見よ。ここでは、モジュールの代わりに「コンポーネント」 (component) という用語を採用する。これは、コンポーネントの方が意味的に「仕切られた区画」に近いからだ。それぞれのコンポーネントは自立していて、カプセル化されており、他のコンポーネントから隔離 されている。この概念が最も当てはまるのはシステムの実行時特性に対してだが、一般的に同様にソースコードのモジュール構造にも反映される。異なるコンポーネントは、同じソフトウェアモジュールを用いて共通のタスクを実行するかもしれないが、そのとき、それぞれコンポーネントの最上位の動作を定義するプログラムコードはコンポーネント自身のモジュールだ。コンポーネント境界は、問題領域のコンテキスト境界 (bounded context) と密接なつながりがある。つまり、隔離が保たれていると、システム設計は問題領域を反映しやすいので容易に進化できる。メッセージのプロトコルは、コンテキスト(コンポーネント)境界の間の自然なマッピングと通信レイヤを提供する。

弾力性(スケーラビリティと対比して)

弾力性 (elasticity) とは、変化する要求を満たすために、リソースを比例的に追加または除去して、システムのスループットを自動的にスケールアップまたはスケールダウンすることだ。実行時におけるリソースの動的な追加や除去の恩恵を受けるには、システムはスケーラブル(スケーラビリティを参照)である必要がある。弾力性は、つまり、スケーラビリティに対して自動的なリソース管理の概念を加えて拡張したものだ。

障害(エラーと対比して)

障害 (failure) はサービス内での予期しないイベントであり、サービスが正常に動作を続けられなくなる。障害が起きるとたいてい、現在の(場合によっては後に続く全ての)クライアントリクエストに応答できなくなる。これは、予期され状況に合わせてコード化されるエラーとは対照的だ。例えば、入力の検証中にエラーが見つかれば、それはメッセージの通常の処理の一部としてクライアントへ伝えられるだろう。障害は予期できないので、システムが同等の動作レベルへ復旧しようとする前に介入する必要がある。これは、障害が致命的ではない場合でも、障害の後ではシステムのいくつかの能力が減少しているからだ。エラーは正常な動作の一部として予期されており、即座に対処されるので、システムはエラーの後でも同等の能力で動作を維持できる

障害の例として、ハードウェアの誤動作や、リソースの枯渇によるプロセスの終了、内部状態を破損させるようなプログラムの欠陥が挙げられる。

隔離(と封じ込め)

隔離 (isolation) は、時間または空間における分離 (decoupling) によって定義できる。時間的分離とは、送信者と受信者がそれぞれ独立したライフサイクルを持てるという意味で、通信を可能とするために同時に存在している必要がない。これは、コンポーネント間に非同期境界を追加してメッセージパッシング (message-passing) で通信することで可能になる。(位置透過性として定義される)空間的分離とは、送信者と受信者を同じプロセス内で実行する必要がないという意味だ。しかし、運用部門やランタイム自身が決めた場所が最も効率的だったとしても、それはアプリケーションの生存期間中に変わりうる。

真の隔離は、オブジェクト指向言語において最も見られるカプセル化の概念を超えたところにあり、これらのものを区切って封じ込めることができる:

  • 状態と動作: シェアード・ナッシングな設計が可能になると共に、(普遍的スケーラビリティの法則 (Universal Scalability Law) が定義するような)競合および一貫性のコストを最小化する。
  • 故障: エラーを他のコンポーネントへ転送することなく、細粒度のレベルで捕捉し、シグナルし、管理できる。

明確に定義されたプロトコルを介した通信はコンポーネント間の強い隔離を実現し、それにより疎結合になるので、システムの理解、拡張、テスト、進化がより容易になる。

位置透過性

弾力性のあるシステムとは適応性のあるシステムだ。それは要求の変化に対して継続的に対応し、緩やかかつ効率的にスケールを増減できる必要がある。一つの鍵となる洞察は、ここでは全てが分散コンピューティングであるということだ。そのことに気付くと、この問題は非常に簡潔になる。このことは、システムが実行されているのがシングルノード(QPI リンクを介して通信する複数の独立した CPU を備える)上であろうと、複数ノードのクラスタ(ネットワークを介して通信する独立したマシンを備える)上であろうと、同様に当てはまる。この事実から言えるのは、マルチコア上での垂直方向のスケーリングと、クラスタ上での水平方向のスケーリングとの間にコンセプトの違いはないということだ。

もし全てのコンポーネントに機動性 (mobility) があり、任意の場所に配備が可能ならば、ローカル通信は単なる最適化であり、あらかじめシステムの静的トポロジや配備モデルを定義しておく必要がなくなる。こうした決定を運用の人員やランタイムに任せることで、システムの適応化や最適化をそれがどのように使われるか次第で行うことができる。

この非同期性メッセージパッシングにより可能になる空間的分離(隔離の項での定義を見よ)と、ランタイムインスタンスとその参照の分離は、我々が位置透過性 (location transparency) と呼んでいるものだ。位置透過性は”透過的な分散コンピューティング”としばしば誤解されるが、実際にはその反対だ。我々は、プロセス内のメソッド呼び出しを(RPC や XA のように)ネットワーク上でエミュレートするのではなく、ネットワークとその制約の全て(部分障害、ネットワーク分断、メッセージの喪失、そして非同期かつメッセージに基づくことによる性質)を受け入れて、プログラミングモデルの第一級市民として扱う。我々の位置透過性に対する見方は、Waldo らによる A Note On Distributed Computing と完全に一致する。

メッセージ駆動(イベント駆動と対比して)

メッセージは特定の宛先へと送られるデータ項目である。イベントは既定の状態に到達したコンポーネントから発生するシグナルである。メッセージ駆動 (message-driven) のシステムでは、アドレス可能な受信者はメッセージの到着を待ってそれに反応するか、さもなくば休止状態になる。イベント駆動のシステムでは、通知のリスナーはイベントの発生源に取り付けられ、イベントが発生した時に呼び出される。つまり、イベント駆動システムがアドレス可能なイベントの発生源に焦点を当てるのに対して、メッセージ駆動システムはアドレス可能な受信者に専心している。メッセージは、自身のペイロードに符号化されたイベントを持つことができる。

イベント駆動システムではイベント消費チェインの短命な性質故に、耐障害性の達成はより難しい。処理が開始されると共に取り付けられたリスナーはイベントに反応して結果へと変換する。このとき、リスナーは一般的に成功や障害を直接に処理して元のクライアントへ報告を返す。一方で、コンポーネントの障害に反応してその機能を正常に回復させるには、これらの障害が短命なクライアントリクエストと結びつかないようにし、しかしあらゆるコンポーネントの健康状態に反応するように扱う必要がある。

ノンブロッキング

並行プログラミングにおいて、一つのリソースを巡って競争する複数のスレッドが、それらのリソースを相互に排他的に保護することで無期限に実行が延期されるとき、あるアルゴリズムはノンブロッキング (non-blocking) とみなされる。このことは、実際には API として明示される。API は、リソースが利用可能ならアクセスさせ、そうでなければ直ちに返って、リソースが現時点では利用できなかったり、操作が開始されて未だ完了していないことを呼び出し元へ伝える。リソースに対するノンブロッキング API では、呼び出し元は、リソースが利用可能になるまでブロックして待つ代わりに他の仕事をすることができる。加えて、リソースのクライアントはリソースが利用可能になるか操作が完了したときに自身へ通知するよう登録することができる。

プロトコル

プロトコル (protocol) は、コンポーネント間でのメッセージの交換や転送に対する取り扱いと作法を定義する。プロトコルの策定は、メッセージ交換に参加する者同士の関係、プロトコルの累積的な状態、そして送信可能なメッセージの集合によってなされる。つまりプロトコルは、参加者が任意の時点で他の参加者へ送信できるメッセージを定義する。プロトコルは、交換におけるいくつかの共通の形体(要求-応答型、(HTTP におけるような)繰り返される要求-応答型、出版-購読型、プッシュ型やプル型のストリーム)に分類できる。

ローカルなプログラミングインタフェースと比べて、プロトコルはより一般的だ。なぜなら、インタフェースは呼び出し元と受信者の間の相互作用を一つずつしか指定できないのに対して、プロトコルは二人以上の参加者を含むことができ、またメッセージ交換の状態の進行を予見するからだ。

注目すべきは、ここで定義したプロトコルはどんなメッセージを送ることができるかのみを指定しており、どうやって送るかは指定していないということだ。コンポーネントがプロトコルを使用する際に、符号化や復号を行うコーデックや転送メカニズムといった実装の詳細は透過的だ。

レプリケーション

コンポーネントを異なる場所で同時に実行することをレプリケーション (replication) と呼ぶ。つまり、コンポーネントは異なるスレッドやスレッドプール、プロセス、ネットワークノード、コンピューティングセンターで実行される。レプリケーションはスケーラビリティ(受信したワークロードをコンポーネントの複数のインスタンスに分散する)と耐障害性(受信したワークロードを複数のインスタンスに複製して、同じリクエストを並列に処理する)を提供する。これらのやり方は組み合わせることができ、例えば、受信した負荷によってインスタンスの総数が変化しても、コンポーネントのあるユーザに関する全てのトランザクションが二つのインスタンスで実行されることを保証する、といったことができる(弾力性を見よ)。

リソース

コンポーネントが自身の機能を実行するのに依存するあらゆるものがリソース (resource) であり、コンポーネントが必要とするのに従って供給されなければならない。これらのリソースには CPU 割り当て、メインメモリ、永続化ストレージなどがあり、同様にネットワーク帯域、メインメモリ帯域、CPU キャッシュ、ソケット間 CPU リンク、高信頼なタイマーとタスクスケジューリングサービス、その他の入出力デバイス、データベースやネットワークファイルシステムのような外部サービス、などが含まれる。必要なリソースが不足していると必要なときにコンポーネントが機能しないので、これらの全てのリソースについて弾力性と耐障害性を考慮する必要がある。

スケーラビリティ

システムが自身の性能を向上させるためにさらなる計算リソースを利用する能力は、スループットの向上のリソースの増加に対する比率によって測定される。完全なスケーラビリティ (scalability) があるシステムでは両者の数値は比例するので、リソースの割り当てが二倍になればスループットも二倍になる。ボトルネックやシステム内の同期点は、一般にスケーラビリティが制約される原因となる。アムダールの法則とグンサーの普遍的スケーラビリティモデル を見よ。

システム

システム (system) はユーザやクライアントにサービスを提供する。システムはその大小に関わらず、多数またはごく少数のコンポーネントから成る。システムの全てのコンポーネントは協調してサービスを提供する。多くの場合、コンポーネントは同じシステム内でクライアント−サーバ関係にある(フロントエンドコンポーネントがバックエンドコンポーネントに依存している場合など)。システムは共通の耐障害モデルを共有する。つまり、あるコンポーネントの障害は他のコンポーネントへ委譲してシステム内で対処する。これは、システム内のコンポーネントグループをサブシステムとみなして、その機能やリソース、障害モデルをシステムの他の部分から隔離するのに有用だ。

ユーザ

サービスの何らかの消費者を指して使う「ユーザ」 (user) という用語は、ここでは、人間やその他のサービスのことである。

↑ ページ先頭に戻る