Chatwork Creator's Note

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

読者になる

リアクティブは難しいが役に立つ

お久しぶりです、かとじゅん(@j5ik2o)です。テックブログを書くのは何年ぶりか…。

サービスが停止したり応答性が低下すると、お叱りや逆に励ましをいただきますが、エンジニアとして設計レベルからそういった問題に対処するにはどうするか、日々精進しているところですmm。この記事はそういう論点で注目されている「リアクティブ原則」についてまとめてみたいと思います。

それなりのボリュームになってしまったので、時間があるときに読んでいただければと思います。

さて、Linux Foundation内の新たなトップレベルプロジェクトであるReactive Foundationが主催する、Reactive Summit 2020が11月10日にオンラインで開催されたので参加しました。

www.reactivesummit.org

参加されていたスピーカーはLightbendをはじめ、Netflix, Facebook, Red Hat, Pivotal, IBM, Tesla などなど、錚々たる企業のエンジニアが登壇されていました。

www.reactivesummit.org

Lightbend CTO Jonas Bonér氏による Keynote

KeynoteはLightbend CTOのJonas Bonérさん1The Reactive Principles: Design Principles For Cloud Native Applicationsでした。スライド資料と動画はまだ公開されていないようです。

リアクティブといえば、2014年に公開されたThe Reactive Manifesto(リアクティブ宣言)を思い出します2。以下の4つの概念で構成されています。

  • 即応性(Responsive)
    • システムは可能な限りすみやかに応答します。 同時に複数ユーザーから重なり合うリアルタイムなリクエストがあっても、障害に直⾯しても、応答性は維持されます。最終的にユーザーに提供する価値
  • 耐障害性(Resilient)
    • システムは障害に直⾯しても即応性を保ち続けます。 障害はそれぞれのコンポーネントに封じ込められ、コンポーネントは互いに隔離されるので、システムが部分的に故障してもシステム全体を危険にさらすことなしに障害から回復することが保証されます。
  • 弾力性(Elastic)
    • システムはワークロードが変動しても即応性を保ち続けます。 ワークロードに連動してリソースを増減させ(スケールアップ/ダウンとスケールアウト/イン)、シャーディングやレプリケーションされたコンポーネント間でワークロードを分散させます。
  • メッセージ駆動(Message-Driven)
    • リアクティブシステムは⾮同期・ノンブロッキングなメッセージ・パッシングによってコンポーネント間の境界を確⽴します。 メッセージ駆動は上記を実現するために利用される手段。

もちろん、要件に対してこれらの特徴をすべて満たす必要があるのか都度考える必要はありますが、定義としては「リアクティブシステムである」というときはこれらを満たす必要があります。そして、なぜ即応性を維持するのか、それは「いつでも使えるシステムを実現するため」です。簡単なことのように聞こえるけど、リアクティブは現実問題として本当に難しい…。

この宣言から4年経った、Reactive Summit 2020のキーノートではThe Reactive Principles(2020-11-06版)が公表されました。 日本語ではリアクティブ原則 or リアクティブ原理?この記事ではリアクティブ原則に統一します。

リアクティブ原則(The Reactive Principles)

詳しくはこちら参照。

principles.reactive.foundation

ちなみに、First AuthorはJonas Bonérさんのようですが、他の代表的な協力者として以下の方が挙げられています。いわゆるガチ勢な方々。

リアクティブシステム ≠ リアクティブプログラミング

ところで、リアクティブについて調べているときに、リアクティブシステム、リアクティブ・アーキテクチャ、リアクティブプログラミングという用語に頻繁に遭遇します。よく誤解されていますが、これらは等価ではありません。3

リアクティブシステムは、アーキテクチャレベルでリアクティブ原則を適用しています。リアクティブシステムを実現する手段としてFuture/Promise, Reactive Streams, アクターモデルなどのリアクティブプログラミングが利用されます。だからといって、自動的にリアクティブシステムになりません。

例えば、アプリケーションを1ノードだけにデプロイした場合、そのノードが故障したら全システムを失います。これではリアクティブ宣言の耐障害性(回復力)がないのでリアクティブシステムではありません。たとえ、このシステムがリアクティブプログラミングを使っていても、リアクティブシステムではありません。

この違いを理解することが重要です。

リアクティブ原則は分散システムの実用的な考え方をまとめたもの

話を本題に戻します。

その「リアクティブ原則」は、リアクティブシステムとリアクティブプログラミングの両方のアイデア、テクニック、パターンを実用的な基本原則としてまとめたものです。概念としては以下です。

  • 応答性を維持する/Stay Responsive
  • 不確実性を受入れる/Accept Uncertainty
  • 失敗を受け入れる/Embrace Failure
  • 自律性を表明する/Assert Autonomy
  • 一貫性を調整する/Tailor Consistency
  • 時間を分離する/Decouple Time
  • 空間を分離する/Decouple Space
  • ダイナミクスを処理する/Handle Dynamics

実は、リアクティブ原則 自体ははじめて見たのですが、このドキュメントで述べられている概念自体はすでにLightbend Academyで学んでリアクティブシステムに関する認証をいくつか取得していたので、発表内容に新しさは感じませんでした。だよねぇという感じ…。まぁ仕方ないですね…。

note.com

詳しくは本家の定義を見ていただくとして、ここでは僕の感想というより、リアクティブ原則に対する見解や考え方を簡単にまとめたいと思います(すべての項目に対して書くとボリュームがすごいことになる(…というかすでにすごいボリュームになっている!)ので、後半部分は薄めにコメントするので、気になる人は本家の定義を参照してみてください)。

応答性を維持する/Stay Responsive

always respond in a timely manner

常にタイムリーに対応すること

応答性を維持することは、ビジネス的な観点で重要です。業務で利用するシステムやサービスが数時間停止したら、自分の業務が遂行できないだけでなく、顧客や取引先にも迷惑を掛ける可能性があります。たとえ、システムが稼働していても、応答が著しく遅くなることも利用者の不満に繋がります。インターネットやスマートフォンが普及した現代では、代替のシステムやサービスは無数にあるため、ユーザーは見切りをつけてほかの選択肢をすぐに見つけてしまうでしょう。つまり、応答性ないソフトウェアには、代替サービスなどへの乗り換えリスクがあるということです。

つまり、リアクティブにすることは技術的な理由ではなく、応答性のあるユーザー体験を提供することを目的としています。誤解されている方が多いと思いますが、リアクティブであること=速さを求めることではありません。応答性は応答時間が早いことだけではなく、ワークロードが変動しても障害に直面してもユーザーのために応答性を一定に維持することが重要です。

不確実性を受入れる/Accept Uncertainty

build reliability despite unreliable foundations

信頼性のない基礎にもかかわらず、信頼性を構築する

分散システムでは、ハードウェアの故障、信頼性の乏しいネットワーク通信、複数ノードでの分散合意などの非決定性の問題があります。

複数ノードでの分散合意について簡単に解説します。以下のようにメンバーが多くなればなるほど合意するまでの時間がかかります(コヒーレンシ遅延)。ITシステムでなくても、人間系でも起こる問題です。

例えば、飲み会の予定を調整する場合、参加者数が2人は通信チャネル1個ですが、3人は3個、4人は6個です。通信チャネルの数はノード数に比例ではなく、指数関数的に増えます。飲み会を開催するには、全メンバーが予定を確認して同期しなければなりません。カレンダーツールを使っていて開催日が更新された場合でも、メンバーはすでに古くなった予定を見ているかもしれません。最新に同期するには新しい予定がこれらの通信チャネルを通る必要があるため、合意に時間が掛かるようになってしまいます。また、チームは分散システムの1つであり、2ピザのチームや人月の神話の背景には、コヒーレンシ遅延があります。

図 コヒーレンシ遅延

f:id:j5ik2o:20201117003531p:plain

分散システムでシステムが増えると、システム内の状態はそれを扱う頃にはもう古くなっている可能性があります。そのギャップを埋める合意の調整コストは貴重な計算資源を消費します。タダではありません。以下のようにノードを増やしても調整コストが増大し、スケールしなくなっていくと言われています。これが ガンザーの法則(Gunther's Universal Scalability Law) という文脈です。

図 ガンザーの法則(Gunther's Universal Scalability Law)

f:id:j5ik2o:20201117005610p:plain:w400

リアクティブシステムでは、こういった調整コストを減らし、アプリケーションアーキテクチャがこれらの不確実性を直接管理できる方法を提供します。実装レベルでいうと、シャーディングやCRDTsなどです。

失敗を受け入れる/Embrace Failure

expect things to go wrong and build for resilience

物事がうまくいかないことを期待し、回復力のために構築する

つまり耐障害性(障害からの回復力)をどう作り込むかという話ですが、この手の話をするときReactive Design Patternsに登場する Bulkheading4 という概念をよく話します。

Bulkheading

Bulkheadingという概念は船舶由来です。大型貨物船の船倉は隔壁によって多くの区画に分割されています。船底が何らかの原因で破損した場合でも、影響を受けた区画だけが浸水し、他の区画は適切に密閉された状態を維持できるため浮力を維持できます。以下の図はReactive Design Patternsで紹介されています。

図 Bulkheading(出典 Reactive Design Patterns)

f:id:j5ik2o:20201117014713j:plain

障害に直面してもシステムが全機能を喪失しないための、Bulkheadingをソフトウェアでどのように実現するか。Error Kernel patternとlet-it-crash patternを使えば可能です。これらはReactive Design Patternsで紹介されているパターンです。

Error Kernel pattern

Reactive Design Patternsでは以下のように説明されています。

In a supervision hierarchy, keep important application state or functionality near the root while delegating risky operations towards the leaves.

スーパービジョン・ヒエラルキーでは、重要なアプリケーションの状態や機能はルートの近くに置いておき、危険な操作はヒエラルキーの末端に委譲する

スーパービジョンヒエラルキーとは監督の階層のことです。図 スーパービジョン・ヒエラルキーのようなイメージです。これはAkkaでのアクター構造の一例を表したものです。個々の丸はコンポーネントであるアクターを示してます。

図 スーパービジョン・ヒエラルキー

f:id:j5ik2o:20201117091434p:plain

コンポーネントに親子関係があり、親は子のスーパーバイザ=監督者です。スーパーバイザであるコンポーネントは簡単に故障するような仕事はせずに、失敗しやすい仕事はヒエラルキー下層の専門のコンポーネントに任せるという考え方です。 上位のコンポーネントは下位のコンポーネントを監督したりタスクを指示し、下位はタスクを実行できます。

1つ1つの丸はコンポーネント(実際はアクター)であり前述した区画のような機能を持ちます。このような構造を採用することで障害が発生しても、全体に障害が波及することを抑制します。

このパターンはErlangで数十年にわたって確立されていて、Jonas Bonér氏がAkkaを実装するためのインスピレーションの1つになっています。

let-it-crash pattern

Reactive Design Patternsから引用。

Prefer a full component restart to internal failure handling.

内部的な故障処理よりも、コンポーネントの完全な再起動を推奨する

例外などの内部で生じたエラーから回復する機構は、故障した部分を十分に隔離できません。モジュール内部のすべてが障害の影響を受けやすいのが現実です。

let-it-crashでは、スーパーバイザに障害対応の判断を委任し、その指示に従ってコンポーネントを再起動して復旧します。 これはプロセスの再起動ではなく、システムの一部を再起動することを意味します。もちろん、通常のオブジェクトグラフでこのようなことを実現するのは難しいので、アクターモデルなどの仕組みを利用する必要があります。そして、このような階層的な再起動を用いる障害処理によって、障害モデルを大幅に簡素化でき、予期しない障害に直面しても生き残る可能性が高まります。

ルート(/)の再起動はプロセスの再起動を意味します。OOMなどではさすがにプロセス再起動が必要なりますが、日常的に起こる障害についてはほとんどはこの仕組みでカバーできます(リトライループによって過負荷にならないかの問題には、指数関数的バックオフやサーキットブレーカーなどの別のパターンで対処できます)。

アクターシステムが使えるツール

前述しましたが、この2つのモデルはアクターシステムに組み込まれています。以下のツールを使えばリアクティブシステムを構築可能です。

個人的な調査したところでは、Golangのproto.actorも有望そうです5

これらのツールには弾力性を実現するためにアクターの位置透過性をサポートしています。アクターの位置情報がローカルであってもリモートであってもリモートにある前提としてメッセージの送受信を行います。メッセージ送信側からは、送信先のすべてがリモートに見えます。(メッセージを受信したアクターでは、リモートからの呼び出しをローカルからの呼び出しにみせます。メッセージ受信側からは、送信元のすべてがローカルにみえます。これを透過的リモーティングといいます)

この仕組みによって、アクターがクラスタリングされたネットワークでは、開発者はマルチスレッディングとRPCの差異を意識することなく、メッセージ駆動という1つのプログラミングモデルでクラスタを横断する計算資源を比較的容易に扱えるようになります。

上記以外にもアクターモデルが扱えるツールがあります。例えばRustのActixやRuby3のRactor6です。しかしながら、これらのツールは位置透過性が標準でサポートされていませんので、そのままでリアクティブシステムの弾力性を実現することは難しいので注意してください。弾力性が必要であれば前述したツールの方がよいと思います。

Bulkheadingはモジュールレベルとシステムレベルがある

さて、どういう単位で失敗を受け入れるかという論点に関しては、以下の2つがあると考えています。

  • モジュール単位
  • システム単位

上記のError Kernel patternやlet-it-crash patternはアクターモデルの文脈で語られるのでアプリケーション内部のモジュールレベルの議論が多いのですが、マイクロサービスなどのシステムレベルでも最近は議論されることが増えてきています。以下のAWS 清水さんのスライド資料でもバルクヘッドという用語が紹介されています。つまるところ、サービス前提で俯瞰したときに、システムとしての区画をどう考えるかに繋がる話で、次の「自律性を表明する」とも関連しそうです。

モダンアプリケーションのためのアーキテクチャデザインパターンと実装

自律性を表明する/Assert Autonomy

design components that act independently and interact collaboratively

独立して行動し、協調的に相互作用するコンポーネントを設計する

Autonomy=自律性とは、簡単にいえば、それぞれのサービスが境界を維持し、独立して運用できるという考え方です。当該サービスの動作保証には、連携している他のサービスは関係ありません。常に自分のサービスの行動を保証するだけです。

自律性を保つにはアプリケーションを分離する必要があります。分離には主に以下の観点があります。

DDDの境界づけられたコンテキスト単位で分離する

DDD以外の様々な書籍に目を通すと、アプリケーションは論理的なDDDの境界づけられたコンテキストに沿って分離するとよい、とされています。特にマイクロサービスの文脈では境界づけられたコンテキストに注目することが多いようです。

その境界づけられたコンテキストは人間の活動に根ざしているので、解決手段の技術ではなく、ビジネスやユーザーの課題に向き合う必要があります。実際のビジネスの場では、コンテキストは複数あり、お互いに相互作用しています。DDDではこれをコンテキストマップと呼んでいて、組織と統合のパターンがいくつかあります。コンテキストマップには、システム間の関係を適切に把握できるというメリットがあります。既存システムとの連携方法を把握し、他のチームとのコミュニケーションの必要性を判断できるようになります。

コンテキストは人間の活動領域を表していて、それに沿ってシステム境界を引くと自然な形で関心を分離しやすいというわけです。システムやモジュールの境界をどこに引くとよいのかという議論はかれこれ50年ぐらいやっているはずで、最終的に辿りついたのはドメインの境界でした…。ドメイン境界で分割するとよいというのは分かっていましたが、実現コストが高かった。今はクラウドとDevOpsによって低コストで実現できるようになりました。それが再注目されている背景だと認識しています。

完全に蛇足で、長くなるのでここでは触れませんが、こういった組織とアーキテクチャの話になると必ずといってコンウェイの法則という言葉が話題にあがります。アーキテクチャとは結局人間の活動とか、場の力が作り出すもの なんですよね。興味がある方は以下の記事も読むとよいかもしれません。

medium.com

CQRS/Event Sourcingの観点でコマンドとクエリに分離する

システム分割の観点は境界づけられたコンテキストだけではありません。CQRSではコマンドとクエリを分離するわけですが、なぜそれが必要なのかという論点があります。理由は表 コマンドとクエリの非対称性に示すとおり要求の非対称性にあります。

※CQRS/Event Sourcingという用語をはじめて聞いた方は、以下のスライドや資料をみていただくとよいです。

表 コマンドとクエリの非対称性

-コマンドクエリ
一貫性/可用性トランザクション整合性を使い一貫性を重視する結果整合を使い可用性を重視する
データ構造トランザクション処理を行い正規化されたデータを保存することが好まれる(集約単位など)非正規化したデータ形式を取得することが好まれる(クライアント都合のレスポンスなど)
スケーラビリティ全体のリクエスト比率とごく少数のトランザクション処理しかしない。必ずしもスケーラビリティは重要ではない全体のかなりのリクエスト比率を占める処理を行うため、クエリ側はスケーラビリティが重要

更新系のコマンド側は業務単位で行うことが多いと思います。例えば「商品を注文する」「注文を取り消す」「注文数を変更する」などは「注文」というモデルを意識します。いわゆる正規化されたデータ単位で更新動作を行います。しかし、閲覧系のクエリ側は業務横断的な探索的な行為です。「同じ発注者の注文一覧を確認する」「注文日時で注文一覧を確認する」などです。一覧を構成するために、「注文」以外に「注文者」というモデルに含まれる「注文者名」を必要するかもしれません。つまり、コマンド側とは異なり、一覧を要求するユーザーに併せて、非正規化されたデータが必要になります。

CQRSとEvent Sourcingを組み合わせることで、システムをコマンド側とクエリ側に分割します。こうすることで、回復力があり、より弾力性のあるアプリケーションを構築することができます。LightbendによるとCQRSシステムのほとんどはEvent Sourcingを採用しているとのことですが、私もCQRSとEvent Sourcingを組み合わせることは必須だと考えています。その理由は以下の記事をみると分かります。構造上の問題なのでEvent Sourcingは避けづらいのだと思います。

blog.j5ik2o.me

このような便利な仕組みもタダで導入できるわけではありません。それなりのコストが伴うことを理解しておく必要があります。高いスケーラビリティが必要な場合や、アプリケーションに非常に強い耐障害性が必要な場合に、CQRS/Event Sourcingはマッチするでしょう。

外部システムへの依存をどう解決するか

また、システムどうしがお互いに依存し合っていると、全体として脆弱になってしまいます。自律性を保つには、依存先の外部システムからのデータをローカルにコピーし保持する方法があります。これはネットワーク分断が生じた際に、一貫性を捨てて可用性を選択するトレードオフですが、外部システムが消失しても一時的であれば当該システムの独立性を維持できます。

つまるところ、CAP定理の問題とも言えるわけですが、分断耐性、一貫性、可用性の3つから2つ選ぶという考え方はミスリードを招くの要注意です。データ指向アプリケーションデザインという書籍でも以下のように述べられています。

ネットワークの分断はフォールトの一種なので、選択に関係するようなものではなく、好むと好まざるとに関わらず生じるものです

また、ネットワーク分断が生じた場合、一貫性もしくは可用性のどちらを選ぶのかという決定の問題があると言及されています。

ネットワークにフォールトが生じると、線形化可能性と完全な可 用性のどちらかを選択しなければなりません。したがって、CAP を表現するならネットワー ク分断が生じた時に一貫性と可用性のどちらを選ぶのか、という方が良いでしょう

外部システムへの依存も、どのような方針で障害に備えるのか、事前に設計に組み込んでおきたいものです。

一貫性を調整する/Tailor Consistency

individualize consistency per component to balance availability and performance

可用性とパフォーマンスのバランスをとるために、コンポーネントごとに一貫性を個別に調整する

パーフォーマンス vs スケーラビリティ

パフォーマンスという用語はスケーラビリティと一緒に語られることが多いので、意味合いを確認します。

パフォーマンスの改善とは、レイテンシを最適化することを意味します。0時間に近づくことはできますが、けっして0時間にはできません。レイテンシを小さくするにはそれなりの労力が掛かります。ビジネスやユーザビリティの観点から十分な値であればよいことが多く、過剰に最適化する必要はないかもしれません。

スケーラビリティの改善とは、スループットを最適かすることを意味します。理論的には限界がないが、通常はソフトウェアの設計やコストなどの問題によって制限されます。リアクティブシステムでは、理論的限界のないスケーラビリティを追求する傾向が強いです。

強い一貫性(Strong Consistency) vs 結果整合性(Eventual Consistency)

一貫性には以下の2種類あるといわれています。

  • 強い一貫性(Strong Consistency)
  • 結果整合性(Eventual Consistency)

強い一貫性(Strong Consisitency)とはデータが可視になる前に、すべてのコンポーネントからの同意を得ることです。分散システムにおいては情報を転送するにはタイムラグがあるため、強い一貫性(Strong Consisitency)をそのまま実現できないのでロック機構を使うことが多いです。RDBMSのトランザクションは強い一貫性に含まれる概念です。このロック機構は非分散環境を作り出すことで一貫性を持たせることができる反面、パフォーマンスの悪化やスケーラビリティが犠牲になりやすいです。

結果整合性(Eventual Consistency)では、データの更新がなければ、あるデータへの全てのアクセスが最終的には最新の値を返すようになります。データの更新がある間は、整合性は保証されません。つまり、データの整合性を保証するには、一定の期間 データの更新を止めなければならないのですが、ロックのような問題を回避することができ、パフォーマンスやスケーラビリティの最適化に寄与します。

コマンド側とクエリ側で一貫性を調整する

CQRSのほとんどのケースでは、コマンド側とクエリ側で一貫性を調整することが多いです。

コマンド側ではシステムの現在の状態に基づいて判断や計算を行うことが多いので、現在の状態が正しいか確認するために強い一貫性(Strong Consisitency)が必要になります。仮に古いデータで判断した場合は、誤ったコマンド結果になってしまう可能性があります…。

コマンドを処理するには、必ず前のコマンドで更新した結果を取り込み、それを新しいコマンドに適用する必要があります。そのためにトランザクション、ロック、シャーディングに関連する技術を利用します。ロックは非分散環境を作り出すため、同期化のコストがボトルネックになりやすいので、ロック自体を無くすかロックを小さしなければスケーラビリティが得られなくなります。AkkaではCluster Shardingという技術でそれを可能にしています。

一方でクエリ側で扱われるリードモデルでは、一般的に強い一貫性(Strong Consisitency)は必要ありません。クエリ側の処理では読み込んだ後に更新動作がないからです。ほとんどは画面や帳票などの形式で表示するだけです。

リードモデルを読み込んだ後に、新しいコマンドが処理されてリードモデルが古くなるかもしれません。その意味ではクエリ系は常に古いデータに基づいて動作します。しかし、コマンド側の更新処理が止まれば、最終的に正しい状態になります。クエリ側は結果整合性(Eventual Consistency)を実現できればよいわけです7

少し古いデータが見えたとしても結果的に整合性が整うのであれば、前述の強いロックは不要になりスケールしやすくなります。また、結果整合性(Eventual Consistency)のモデルはキャッシュやレプリケーションも効果的になります。この特徴によって高い可用性を実現できます。AkkaではDistributed Dataという技術でそれを可能にしています。

一般的なウェブサービスではあれば、コマンドよりクエリのほうがリクエスト量が多くなる傾向にあり、キャッシュ戦略が有効に働けば貴重な計算資源を効率的に使うことができるようになります。

時間を分離する/Decouple Time

process asynchronously to avoid coordination and waiting

調整と待ち時間を避けるために非同期に処理を行う

これは、システム(アクターのようなコンポーネントでも同様)間の通信において、お互いに待つべきではない。リクエストは非同期・ノンブロッキングであるべきという考え方です。

メソッド駆動の考え方では、相手のメソッドを呼び出すと処理を実行し戻り値などを含めて制御を戻しますが、この考え方は違います。リクエストを送信後に応答を待つ間にスレッドをブロックせずにその場を立ち去るFire and Forgetになります。貴重な計算資源を占拠せず、より効率的にリソースを使うことができるようになります。これによって、スループットの最適化がしやすくなり、結果的にスケーラビリティが向上します。

空間を分離する/Decouple Space

create flexibility by embracing the network

ネットワークを取り入れることで柔軟性を生み出す

これは、マイクロサービスは、他のマイクロサービスがどこにデプロイされているか気にすべきではない。どこのマシンでもデータセンターであってもよいという考え方です。疎結合であるからこそ、需要に応じてサービスをスケールアップしたりスケールダウンしたりできます。

サービスやシステムの空間だけではなく、内部のモジュール間も空間を分離できるとよいと考えています。モジュラモノリスを採用しても内部モジュールが密結合では、その後マイクロサービスに発展することは難しくなってしまいます。なので、モジュール間を疎結合にするためにメソッド駆動ではなくメッセージ駆動を採用するという考え方もあると思います。

ダイナミクスを処理する/Handle Dynamics

continuously adapt to varying demand and resources

需要とリソースの変化に継続的に適応する

アプリケーションはワークロードが変動しても応答性を維持しなければなりません。それには弾力性が必要です。「空間を分離する」とも関連します。

まとめ

リアクティブ原則を公表したReactive Foundationは発足されてまだ2年ですが、ボードメンバーがAlibaba Cloud、Facebook、Lightbend、VMWareということもあり、それになりに規模の大きな動きになっています。参画しているプロジェクトは R2DBC, Reactive Streams に加えて、RSocketが追加されました。おそらく今後も増えていくと思われます。

あと、あまり知られていませんが、リアクティブ界隈のリーダーはLightbendScala/Akkaのコミュニティなので、興味があればそちらからも情報を収集するとよいかもです。一方、日本ではリアクティブプログラミングについて多少話を聞く程度で、今回紹介したリアクティブシステムに関する情報や事例はまだまだ少ないですね…。増えて欲しい…。

まぁ、機能を実現するだけでも大変なのに「いつでも使える」を最初から考えるのは難しいので、手が出しにくい分野かもしれない…。逆説的にいえば、誰もが手を出しにくい分野だからこそ、希少性がでてくるとも言えるので精進したいところです。コロナ禍のご時世も考えると、ウェブサービスやソフトウェアに対する期待は「いつでも使えて当たり前」になっていく気がします。その意味では、「リアクティブ原則」の観点は少なからず求められるでしょうし、エンジニアとしてもこの先10年ぐらい戦っていくために使える技術分野の1つではないかと思っています。そして、DDD, CQRS, Event Sourcing, アクターモデルなどの高度な技術も、リアクティブのために必要になるので、スキルの掛け合わせ具合がちょうどよいと個人的には思っています。というわけで、リアクティブは難しいですがきっと役に立つので、学びと実践のサイクルを回していきたいところですね。すべてはユーザーのために。

Chatworkのプロダクトにおいても、2016年ぐらいから徐々にリアクティブシステムの考え方を取り入れていますし、次世代のプロダクトにも全面的に採用する予定です。そのためにも一緒にプロダクトを創り上げる同志を募集中です。興味があるという方は、@j5ik2oまでDMください(DMは解放しています)!

長い記事を読んでいただきありがとうございました!


  1. AkkaはJonasさんがErlangからインスピレーションを受け、JVMにアクターモデルを輸入するために開発しました。Akkaの生みの親です

  2. すでに本日時点で29148署名されているようです。

  3. 余談ですが、JavaのリアクティブプログラミングためのツールであるReactor、のサイトでは"Create Efficient Reactive Systems"と記されていますが、仕様を見た限り耐障害性(回復力)のための機構がないので、どうあがいてもリアクティブシステムになりそうにないです…。"Reactive Systems"という用語は、リアクティブ原則でいうリアクティブシステムとは違う意味で使われてそうですね。要注意

  4. Bulkheadと呼ばれることもある

  5. proto.actorにはGolang, Kotlin, C#のサポートがありますが、位置透過性をサポートしているのはGolang版のみのようです。Kotlin, C#は普通にAkkaを使ったほうがよさそうです。

  6. typoしなかった。

  7. もちろん、ここで挙げた考え方は典型的な要求によるもので、コマンド側でも可能性性重視、クエリ系でも強い一貫性が必要とされる場合もあります。