Lightbend Academy で Reactive Architecture(リアクティブ・アーキテクチャ)について学んだ内容をメモっとく。どうやら同じコースが Cognitive Class でも公開されているみたいなので、Lightbend Academy 無料期間に受講できなかった人はこちらをどうぞ。
本稿は、個人的に思い出すために書き留めているノートなので、言葉の正しさは保証できない。どんな内容がコースに含まれているかを俯瞰するにはいいかと思う。
コースの3つ目まではノートを書いてなかったので、気が向いたら時間のあるときに復習してまとめようと思う。
Introduction to Reactive
Reactive(リアクティブ)の章。リアクティブの意味するところと、高 Responsive(即応性)なシステムを構築するための原則について。
Domain Driven Design
Domain Driven Design(ドメイン駆動設計)の章。
Reactive Microservices
Microservices(マイクロサービス)の章。
Scalable Systems(スケーラブル・システム)
永続化の章。Consistency(整合性)と Availability(可用性)が Scalability(スケーラビリティ)に与える影響について。
Consistency(整合性)と Availability(可用性)と Scalability(スケーラビリティ)
- Consistency(整合性)
- Consistency(整合性)があるとは、システムのすべてのメンバーが同じ View(視点)や State(状態)を持っていること。
- Availability(可用性)
- Availability(可用性)があるとは、Failure(機能不全)があるにも関わらず Responsive(即応性)が維持されていること。
- Scalability(スケーラビリティ)
- Scalability(スケーラビリティ)があるとは、Responsive(即応性)を維持しながら需要の増加に対応できること。
Performance(パフォーマンス)対 Scalability(スケーラビリティ)
- Performance(パフォーマンス)
- Response Time(応答時間)を最適化すること。Load(負荷)への対処能力は変化しない。Response Time(応答時間)の最適化には限界がある。
- Scalability(スケーラビリティ)
- Load(負荷)への対処能力を最適化すること。Response Time(応答時間)は変化しない。Load(負荷)への対処能力の最適化には限界がない。
Distributed System(分散システム)における Consistency(整合性)
- Distributed System(分散システム)
- 空間によって分離されたシステムのこと。物理的に情報の伝達には光の速度を上限とした時間がかかる。
Eventual Consistency(結果整合性)
- Eventual Consistency(結果整合性)
- 結果整合性は、新しい更新がなければ、特定のデータへのすべてのアクセスが結果的に最新の値を返すことを保証する。(Eventual consistency guarantees that, in the absence of new updates, all accesses to a specific piece of data will eventually return the most recent value.) 現実は基本的に Eventual Consistency(結果整合性)である。
- Causal Consistency(因果整合性)
- 因果関係にもとづいて順番に処理することによって整合性を担保すること。
- Sequential Consistency(逐次整合性)
- 非並列に順番に処理することによって整合性を担保すること。
Strong Consistency(強整合性)
- Strong Consistency(強整合性)
- データを更新するには、それが可視化される前に、すべてのノードによる合意が必要である。(An update to a piece of data needs agreement from all nodes before it becomes visible.) Monolithic Architecture(モノリシック・アーキテクチャ)でよく使用される RDB などにおける整合性のこと。結果整合性の現実においては、Lock(ロック)によって強整合性をシミュレーションしている。Lock(ロック)は Contention(競合)を形成する。
Contention(競合)の影響
- Contention(競合)
- 2つのものが1つの限られた資源を巡って争い、どちらか一方が勝利する。
- Amdahl's Law(アムダールの法則)
- 並列処理によって得られる最大の効果を定義する法則。Contention(競合)によってその効果は減少する。
Coherency Delay(整合性遅延)の影響
- Coherency Delay(整合性遅延)
- 各ノードが同期して一貫した状態になるまでに時間がかかることによる遅延のこと。
- Gunther's Universal Scalability Law(ガンターの普遍的スケーラビリティの法則)
- Amdahl's Law(アムダールの法則)を Contention(競合)と Coherency Delay(一貫性遅延)を用いて一般化できる。この法則においては、スケールアウトの利益を上回るコストが発生し得ることを説明している。
Coherency Delay と Gunther's Universal Scalability Law の一般的な日本語訳を見付けることができなかった。英語版 Wikipedia の Neil J Gunther にある Universal Law of Computational Scalability も参考にするといいかも。
Law of Scalability(スケーラビリティの法則)
- Law of Scalability(スケーラビリティの法則)
- 前述の法則より線形のスケーラビリティは基本的に得られない。Stateless(ステートレス)による完全な Isolation(分離)が必要になる。Reactive System(リアクティブ・システム)はその限界を果敢に利用する。
- Reactive Microservices(リアクティブ・マイクロサービス)における Scalability(スケーラビリティ)
- ロックの分離、トランザクションの排除、ブロッキング処理の回避により Contention(競合)を削減する。Eventual Consistency(結果整合性)と Autonomy(自律)により Coherency Delay(整合性遅延)を緩和する。これによって高い Scalability(スケーラビリティ)を可能にする。
CAP Theorem(CAP 定理)
- CAP Theorem(CAP 定理)
- Distributed System(分散システム)は Consistency(整合性)・Availability(可用性)・Partition Tolerance(分断耐性)の2つより多くは提供できないことを示している。
- Partition Tolerance(分断耐性)
- ネットワークによって任意の数のメッセージがドロップ(または遅延)されているにもかかわらずシステムが稼働を継続すること。(The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network.)いわゆるネットワークの分断が発生したときに2つの選択肢を取ることができる。AP: Consistency(整合性)を犠牲にする。RDB のレプリケーションを組んだマスタースレーブ構成は AP システム。CP: Availability(可用性)を犠牲にする。RDB をシングルノードで動作させる構成は CP システム。CA: Partition Tolerance(分断耐性)を犠牲にするというシステムは一般に存在しない。
Consistency は一般的に「一貫性」と訳される気がするけど、Eventual Consistency は「結果整合性」と訳されるので後者に合わせて、「整合性」と訳してメモすることにした。
Consistency(整合性)と Scalability(スケーラビリティ)
- Isolating Contention(競合の分離)
- アプリケーション段階の Sharding(シャーディング)を用いて Contention(競合)のスコープを制限し、Crosstalk(混線)を削減することで、Consistency(整合性)と Scalability(スケーラビリティ)を両立させる。
Consistency(整合性)のための Sharding(シャーディング)
- Sharding(シャーディング)
- Entity(エンティティ)もしくは Actor(アクター)の ID に基づいて分離すること。Entity(エンティティ)のグループを Shard(シャード)と呼ぶ。各 Entity(エンティティ)は1つの Shard(シャード)に存在し、1つの Shard(シャード)は1箇所に存在する。Entity(エンティティ)は Consistency Boundary(整合性境界)として振る舞う。Sharding(シャーディング)する単位は Aggregate Root(集約ルート)が候補になる。Shard(シャード)が一様分布するように ID を選択する。
- Coordinator(コーディネーター)
- Entity(エンティティ)が存在すべき適切な Shard(シャード)に通信を配送する。
Sharding(シャーディング)の影響
- Contention(競合)
- Sharding(シャーディング)は Contention(競合)を排除せず分離するだけ。ひとつの Entity(エンティティ)の中で Contention(競合)が発生する。
- Sharding(シャーディング)
- CAP Theorem(CAP 定理)における CP システムなので Availability(可用性)を犠牲にする。
- Caching(キャッシング)
- Entity(エンティティ)ごとに Cache(キャッシュ)することで、Caching(キャッシング)の Distributed System(分散システム)における問題を解決。更新処理が来たときは Cache(キャッシュ)を更新して、データベースを更新する。参照処理が来たときは Cache(キャッシュ)を参照して、データベースを参照しない。
Availability(可用性)と Scalability(スケーラビリティ)
- CRDTs (Conflict-free Replicated Data Types)
- CRDTs (Conflict-free Replicated Data Types) によって Availability(可用性)重視のスケーラビリティを獲得できる。
Availability(可用性)のための CRDTs (Conflict-free Replicated Data Types)
- CRDTs (Conflict-free Replicated Data Types)
- High Availability(高可用性)で Eventual Consistency(結果整合性)である。データは複数の Replica(レプリカ)に格納される。1つの Replica(レプリカ)への更新は非同期にほかの Replica(レプリカ)にコピーされる。更新はマージされて最終状態に落ち着く。
- CvRDTs (Convergent Replicated Data Types)
- Replica(レプリカ)間で状態をコピーする。2つの状態について Merge Operation(マージ演算)が定義されていることが必須であり、それは Commutative(交換)と Associative(結合)と Associative(結合)を満たす。
- Commutative(交換)
- 被演算子の順番を交換しても演算結果が同じになる。a + b = b + a
- Associative(結合)
- 演算の順番を交換しても演算結果が同じになる。(a + b) + c = a + (b + c)
- Idempotent(羃等)
- 演算を複数回適用しても演算結果が同じになる。
- CmRDTs (Commutative Replicated Data Types)
- Replica(レプリカ)間で操作をコピーする。このコースでは論じない。
- Akka Distributed Data
- CvRDTs をサポートする。Counters・Sets・Maps・Registers というような型をサポートしている。
CRDTs (Conflict-free Replicated Data Types) の影響
- Akka Distributed Data
- Distributed Data(分散データ)の CRDTs はメモリに保存される。障害からのリカバリを速くするためにディスクに保存されることもある。通常の状態ではあくまでメモリにデータが存在する。CRDTs は CAP Theorrem(CAP 定理)において Availability(可用性)を意図した解決策。
- CRDTs の制限
- 適切な Merge Operation(マージ演算)が定義されているデータ型に限定される。データの削除には Tombstone(墓石)と呼ばれるマーカーが使用され、データは大きくなることはあっても小さくなることはない。このオーバーヘッドを CRDT Garbage(CRDTs ゴミ)と呼ぶ。
Consistency(整合性)と Availability(可用性)
- Consistency(整合性)と Availability(可用性)の選択
- 技術的判断ではなくビジネス的判断なので、ドメインエキスパートときちんと議論して検討する。二者択一ではなく、バランスの問題なので最適なバランスを判断する。最終的には会社の利益が最大になるようにバランスを取ることになる。
Distributed Messaging Patterns(分散メッセージング・パターン)
Message(メッセージ)の章。Reactive Manifesto(リアクティブ宣言)における Message Driven(メッセージ駆動)における Asynchronous(非同期)かつ Non-Blocking(ノンブロッキング)な Message(メッセージ)について。
Message Driven Architecture(メッセージ駆動アーキテクチャ)
- Message Driven Architecture(メッセージ駆動アーキテクチャ)
- Asynchronous(非同期)かつ Non-Blocking(ノンブロッキング)な Message(メッセージ)を使用する。Message(メッセージ)は、送信したら応答を待たない。Sender(送信者)は応答に興味があるかもしれないが、応答は Asynchronous(非同期)に得られる。
- Asynchronous(非同期)と Non-Blocking(ノンブロッキング)の利点と欠点
- CPU やメモリの資源をすぐに解放する。Contention(競合)を削減する。Receiver(受信者)がオフラインでも Queue(キュー)に格納できる。Synchronous Message(同期メッセージ)はドメインの要求であれば使用してもいいが最小限にし、デフォルトは Asynchrouns Message(非同期メッセージ)にする。Asynchrouns Message(非同期メッセージ)は技術的に難しいし、複数サービスにまたがる Transaction(トランザクション)を困難にする。
Sagas(サーガ)
- Saga Pattern(サーガ・パターン)
- 長期実行トランザクションを実現するためのパターンのこと。複数のリクエストは Saga(サーガ)によって管理される。逐次か並列かで各リクエストは処理され、すべてのリクエストが完了すると Saga(サーガ)も完了する。
- Compensate(補正)
- Saga(サーガ)におけるリクエストが失敗した場合、Saga(サーガ)の実行されたリクエストを遡り、Compensate(補正)が実行される。Compensate(補正)は Idempotence(羃等)でなければならない。Compensate(補正)に失敗したらリトライする。Rollback(ロールバック)とは異なる。Rollback(ロールバック)は失敗したリクエストが発生しなかったことにするので Evidence(証跡)が残らない。 Compensate(補正)は、リクエストについても、Compensate(補正)についても、両方の Evidence(証跡)が残る。
- Timeout(タイムアウト)
- Distributed System(分散システム)において Timeout(タイムアウト)は難しい問題を引き起こす。「リクエスト失敗」「リクエスト成功、応答失敗」「リクエストはキューの中で、いずれ成功か失敗する」
Two Generals(二人の将軍)
- Two Generals' Problem(二人の将軍問題)
- Wikipedia: 二人の将軍問題
Delivery Guarantees(配送保証)
- Delivery Guarantees(配送保証)
- Two Generals' Problem(二人の将軍問題)より、信頼性の低いネットワークでは配送を保証できない。At Most Once(0〜1回)か At Least Once(1回以上)のいずれかしか保証できない。At Least Once(1回以上)と Idempotence(羃等)を使用することで Exactly Once(ちょうど1回)をシミュレーションできる。Akka は At Most Once(0〜1回)がデフォルト。Akka Persistence は At Least Once(1回以上)を選択できる。Lagom は At Most Once(0〜1回)と At Least Once(1回以上)の両方をサポートしている。
Messaging Patterns(メッセージング・パターン)
- Point to Point(ポイント・トゥー・ポイント)
- サービスを直接知っていて、直接メッセージを送信するパターン。
- Publish/Subscribe(出版/購読)
- サービスは Message Bus(メッセージ・バス)にメッセージを Publish(公開)する。ほかのサービスはメッセージを Subscribe(購読)する。メッセージ形式に結合されるので、相手サービスを知らなくてよくなり、疎結合になる。
- Akka
- Actor(アクター)は Point to Point(ポイント・トゥー・ポイント)。Distributed Publish Subscribe と Persistence Query は Publish/Subscribe(出版/購読)で実装されている?
- Lagom
- サービス間の通信は Point to Point(ポイント・トゥー・ポイント)。Message Broker API は Publish/Subscribe(出版/購読)。
CQRS & Event Sourcing
CQRS(コマンドクエリ責任分離)と Event Sourcing(イベントソーシング)の章。CQRS(コマンドクエリ責任分離)による Read Models(読み取りモデル)と Write Models(書き込みモデル)の分離によって Elastic(弾力性)と Resilient(耐障害性)を確保し、イベントソーシング(ES)による State Based Persistence(状態ベース永続化)の排除によってモデルの Consistency(整合性)を確保することについて。
CQRS/ES とは?
- CQRS/ES
- Command Query Responsibility Segregation(コマンドクエリ責任分離)。Event Sourcing(イベントソーシング)。一緒に利用されることの多いテクノロジーではあるが、それぞれ独立したもので、必ずしも一緒に使わなければならないということはない。
State Based Persistence(状態ベース永続化)
- State Based Persistence(状態ベース永続化)
- 現在の状態を永続化する。過去の状態は置換される。
Event Sourcing(イベントソーシング)
- Audit Log(監査ログ)
- Audit Log(監査ログ)を永続化することで、State Based Persistence(状態ベース永続化)の制約を回避。すべての歴史が記録されている。真実の Source(ソース、源)が Audit Log(監査ログ)で、そこから State(状態)を回復することもできる。
- Event Sourcing(イベントソーシング)
- State Based Persistence(状態ベース永続化)ではなく、Audit Log(監査ログ)に記録されていること、Intent(意図)を記録すればいいという手法。Intent(意図)を Event(イベント)として記録する。 State(状態)は Event(イベント)の Replay(再生)で実現する。Undo(アンドゥ)しやすいのも利点。エラーを再現しやすいのも利点。ドメインの分析にも履歴が残るので便利。
- Snapshot(スナップショット)
- Event(イベント)の Replay(再生)にかかる時間を Snapshot(スナップショット)を取ることで削減する。バックアップにおける差分バックアップにみられる手法と類似してると思うとわかりやすいかな。まだ未熟な技術なので、性能問題が明らかになってから導入する方がいいみたい。すべてに導入すべきではない。
Model(モデル)の進化
- Event Integrity(イベント・インテグリティ)
- Event(イベント)は事実として記録されなければならない。Event(イベント)は不変であり、削除されたり、更新されたりしない。
- Event(イベント)の Versioning(バージョン管理)
- Event(イベント)の表現が変わっても、過去の Event(イベント)は変更しない。Versioning(バージョン管理)する。Event(イベント)の記録に使用する Log(ログ)フォーマットは柔軟なものにすべき。Java オブジェクトをシリアライズしたものではなく、Protobuf や JSON など。Akka Event Adapters が Event(イベント)の Versioning(バージョン管理)をサポートしている。
Command Sourcing(コマンドソーシング)
- Command Sourcing(コマンドソーシング)
- Event Sourcing(イベントソーシング)の Command(コマンド)永続化版。Command(コマンド)を永続化することで、実行を非同期にできる。Akka Persistence で Event Sourcing(イベントソーシング)と合わせて使用されている。
Read Models(読み取りモデル)対 Write Models(書き込みモデル)
- Event Sourcing(イベントソーシング)
- Aggregate(集約)を対象にすべき。ただ、予約をみたいという要望に Aggregate(集約)は応えるのは大変。顧客の情報も調べなきゃならないし、予約の一覧から情報も取得しなきゃならない。RDB のようにはできない。Aggregate(集約)横断で情報を Query(クエリ)するのは Event(イベント)として保存されているから困難。
CQRS(コマンドクエリ責任分離)
- CQRS(コマンドクエリ責任分離)
- Query(クエリ、読み取り)と Command(コマンド、書き込み)の要求は異なるので分離しようという考え方。Read Models(読み取りモデル)と Write Models(書き込みモデル)を分けるということ。データストアは単一でもいい。
- CQRS/ES
- Event Sourcing(イベントソーシング)を伴うと Event(イベント)を格納するストアと、Read Models(読み取りモデル)のためのストアを分離する。この分離によって前述の問題を解決する。
- Projection(射影)
- Event(イベント)を非正規化して Read Models(読み取りモデル)ために用意したデータのこと。
ちょうどいい粒度の Microservices(マイクロサービス)
- Microservices(マイクロサービス)としての Model(モデル)
- Read Models(読み取りモデル)と Write Models(書き込みモデル)を独立して Microservices(マイクロサービス)にできる。が、分離しすぎても、デプロイなどの運用コストが嵩むかもしれない。
CQRS での Consistency(整合性)と Availability(可用性)と Scalability(スケーラビリティ)
- Consistency(整合性)
- CQRS のみの場合、CQRS ではないシステムと同じ。CQRS/ES の場合、Read Models(読み取りモデル)と Write Models(書き込みモデル)で異なる。Write Models(書き込みモデル)では、現在の State(状態)が重要になってくるので Lock(ロック)や Transaction(トランザクション)、Sharding(シャーディング)などの Strong Consistency(強整合性)が求められることがある。Read Models(読み取りモデル)では、Strong Consistency(強整合性)存在しない。Strong Consistency(強整合性)は書き込みを前提としているから。読み込みの後にデータは Stale(新鮮でない)状態になる。読み込みの後に変化しないことを Lock(ロック)によって保証するとしても、どの程度の時間 Lock(ロック)すればいいかはわからない。
- Scalability(スケーラビリティ)
- 一般的なシステムでは、Read Models(読み取りモデル)の負荷が高くなる。CQRS/ES の場合、Read Models(読み取りモデル)をスケールさせることもできるし、Projection(射影)ごとにスケールさせることもできる。Write Models(書き込みモデル)は、Strong Consistency(強整合性)が必要だが Sharding(シャーディング)でスケールさせることができる。
- Availability(可用性)
- Write Models(書き込みモデル)では、Strong Consistency(強整合性)が求められているので、Availability(可用性)が犠牲になる。CAP Theorem(CAP 定理)による説明どおり。Read Models(読み取りモデル)では、Eventual Consistency(結果整合性)になっているので、High Availability(高可用性)が可能になる。障害シナリオにおいて、データの書き込みはできなくなるかもしれないが、データの読み込みはできる状態にはなる。システムの全体が動作しなくなるよりはよい。
CQRS のコスト
- Design(設計)
- CQRS/ES は従来のアーキテクチャに比べて複雑だと批判されることがある。見方によってはシンプルな Design(設計)と言える。まあ、きちんと責務が分離されてくしねぇ。
- CQRS/ES のコスト
- 一般的にクラスの数が増える。データストアの種類も増える。Eventual Consistency(結果整合性)を前提とした UI を実装する必要がある。古い Event(イベント)をサポートしなければならない。Event(イベント)の履歴や Read Models(読み取りモデル)の重複によって、より多くのストレージが必要になる。データの重複が同期問題を引き起こすが、Projection(射影)を再構築することで解決できる。