Slide 1

Slide 1 text

SESSION リアクティブシステムと 次世代基盤について 2021/05/26 Chatwork Dev Day Chatwork株式会社 加藤潤一

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

自己紹介 ● @j5ik2o ● Chatwork のテックリード ● 10 歳で初めてプログラミングに触れる。SIer としてさまざまな現場での業務を経験した後、 2011 年より某d社、2013 年より大手ソーシャ ルゲーム企業で、それぞれ Scala やドメイン 駆動設計を採用したシステム開発に従事 ● 2014 年 7 月より Chatwork に参画。現在は Chatwork 次期アーキテクチャのプランニン グや設計、開発に携わる ● 最近はまっていること。オートミールうまいす よ

Slide 4

Slide 4 text

AGENDA 1. リアクティブシステムと CQRS/Event Sourcingについて 2. 新アーキテクチャの概要

Slide 5

Slide 5 text

1 リアクティブシステムと CQRS/Event Sourcingについて

Slide 6

Slide 6 text

次世代基盤のためのキーワード ・リアクティブシステム ・CQRS/Event Sourcing

Slide 7

Slide 7 text

アーキテクチャ刷新(Sagrada)の目的と手段 ● 目的 ○ 2025年の事業計画に合わせて、生産性を維持・向上できるプロダクトと 組織を構築する ● 手段 ○ プロダクトの健全性を維持・向上させること ○ チームが独立して改善活動をおこなえること ○ 大規模なアジャイル開発が実践できること ○ いつでもレスポンスを返せる状態になっていること ■ リアクティブシステムを反映したアーキテクチャを実現する ■ CQRS/Event Sourcingシステムを実現する 社会インフラを担うからこそ必要になる手段 この課題に対する考え方を解説します

Slide 8

Slide 8 text

ところで…ユーザの前でこういう言い訳できますか… ● 「サーバ台数が足りなくて…」とか…。確かに難しい問題だが… ● 「いやー、よくあることですよ」とは口が裂けても言えない。それは提供側の理屈 ● 使いたいときに使えないシステムは、利用者から見限られる ● 競合は探せばいくらでも見つかる時代、利用者は競合へ乗り換え可能

Slide 9

Slide 9 text

そこで【リアクティブシステム】

Slide 10

Slide 10 text

リアクティブシステムとは どんな状況でも即応性を確保し「いつでも使えるシステム」を実現 する ● 即応性とは ○ 状況に応じてすばやく行動すること。「事故に即応した処置」 ○ 状況・情勢にあてはまること。「現実に即応した考え」 「そんなの当たり前だ」といえばそうだが、実現することはそれなりに難しい…

Slide 11

Slide 11 text

「止まらないシステム」ではなく「回復力があるシステム」が求められている ● 東京証券取引所, 株式売買システムで全銘柄で売買不能になった (2020/1) ○ NASのフェイルオーバーができなかったことが原因 ■ NASのマニュアルでは待機系への切替の初期設定が「15秒で切替」と なっていたが、実際の設定は「OFF」だった。人為的な要素が強い… ■ NASが故障することで売買システムの全機能を失ってしまった ○ 恒久対策は回復力を向上させること。異常を起こした部分を切り離し障害か ら回復できるアーキテクチャに変更していくために、MSAに移行していく、 とのこと 止まらないシステム(全く障害を起こさない完全なシステム)を目指すのではなく 【障害から回復する能力を設計すること】に価値がある

Slide 12

Slide 12 text

ここからは、 リアクティブな ソフトウェア・アーキテクチャ を実現するにはどうすればよいか という論点になります

Slide 13

Slide 13 text

リアクティブシステムとは 支える原理 手段 届けたい価値 即応性(Responsive) メッセージ駆動(Message-driven) 伸縮性(Elastic) 耐障害性(Resilient) 最終的な目的 非同期・ノンブ ロッキング 、位置透過性 回復力のある設計 Resilient By Design 必要な手段

Slide 14

Slide 14 text

メッセージ駆動とは リアクティブシステムは【⽴同期・ノンブロッキング】なメッセージ・パッシングによってコン ポーネント間の境界を確⽴する リアクティブシステムは【⽴同期・ノンブロッキング】なメッセージ・パッシングによってコン ポーネント間の境界を確⽴する CartClient Cart AddCartItem { cartId = 1, cartItemId = 4, itemId = 1, itemNum = 1, } CartItem { 1, 1, … } CartItem { 1, 2, … } CartItem { 1, 3, … } タスクの完了を待 たない AddCartItemSucceeded AddCartItemFailed タスクの成否をメッセージで 返答する タスクがなければリ ソースを消費しない メッセージに反応する かどうか受信コンポー ネント次第 メッセージが届くなら ばリモートでもローカ ルでもよい タスクを依頼するた めにメッセージを送 信する

Slide 15

Slide 15 text

【FYI】「リアクティブシステム」と「リアクティブプログラミング」は同じ概念ではない ● リアクティブシステムとリアクティブプログラミングという用 語に頻繁に遭遇するが。これらは等価ではない ● リアクティブシステムはアーキテクチャレベルでリアクティブ 原則を適用する。リアクティブシステムを実現する手段として Future/Promise, Reactive Streams, アクターモデルなどのリ アクティブプログラミングが利用されます。だからといって、 自動的にリアクティブシステムになりません ● 例えば、アプリケーションを1ノードだけにデプロイした場 合、そのノードが故障したら全システムを失う。これではリア クティブ宣言の耐障害性(回復力)がないので、リアクティブプ ログラミングを使っていても、リアクティブシステムではない Node 1 リアクティブプログラミン グを使っていても1台の みで運用すると、この 1台 が故障すると全システム を失う 故障

Slide 16

Slide 16 text

リアクティブ原則(The Reactive Principles) ● 応答性を維持する/Stay Responsive ● 不確実性を受入る/Accept Uncertainty ● 失敗を受け入れる/Embrace Failure ● 自律性を表明する/Assert Autonomy ● 一貫性を調整する/Tailor Consistency ● 時間を分離する/Decouple Time ● 空間を分離する/Decouple Space ● ダイナミクスを処理する/Handle Dynamics @see https://principles.reactive.foundation/principles/index.html 今回はこの2点を解説

Slide 17

Slide 17 text

失敗を受け入れる/Embrace Failure ● 物事がうまくいかないことを期待し、回復力のために構築する ● キーとなる考え方はBulkheading ○ Bulkheadingは船舶由来の用語。大型貨物船の船倉は隔壁によって多くの区画に分 割される。船底が何らかの原因で破損した場合でも、影響を受けた区画だけが浸水 し、他の区画は適切に密閉された状態を維持できるため浮力を維持できる ● 以下の図はReactive Design Patternsで紹介されている

Slide 18

Slide 18 text

アクターモデルでどのようにBulkheadingするか ● スーパーバイザであるコンポーネントは簡 単に故障するような仕事はせずに、失敗し やすい仕事はヒエラルキー下層の専門のコ ンポーネントに任せる。このような構造を 採用することで障害が発生しても、全体に 障害が波及することを抑制する ● 障害発生時はスーパーバイザに判断を委任 し、その指示に従ってコンポーネントを再 起動して復旧する。 このような階層的な再 起動を用いる障害処理によって、障害モデ ルを大幅に簡素化でき、予期しない障害に 直面しても生き残る可能性が高める

Slide 19

Slide 19 text

なぜアクターモデルなのか ● Erlang 1998年 初版リリース(OSSとして公開された) 
 ○ 1986年にエリクソンがアクターモデルをベースにしたErlangを開発(当初はOSSではなく企 業内で開発・利用されていた)。電話交換機にて稼働率99.9999999%を実現した。2011年 にWhatAppが1台のサーバで100万クライアントをさばいた実績がある 
 ● Akka/Scala 2010年に1.0、2012年に2.0と進化 
 ○ Akkaはリアクティブ原則をサポートしたツールキット。AkkaはErlangからインスピレーショ ンを受け、Lightbend社のCTO Jonas Bonér氏によって開発。 非同期・ノンブロッキングな メッセージ駆動でC10K問題を解決。2011年の記事ではErlangの2倍のスループットを発揮 したという事例もある
 ● マルチコア危機の解決をターゲットにした言語として、Erlang(1998)やScala(2004)が登場。この 2つの言語はマルチスレッドプログラミングおける、多くの苦しみを取り除いてくれると期待され ていた


Slide 20

Slide 20 text

ChatworkのAkka導入 ● 2016年末 メッセージング基 盤にて、Akka,HBase,Kafkaを 使って、CQRS+ESシステム を構築・運用開始
 ● 他のマイクロサービスでも Akkaを積極的に採用
 ● SagradaではCore Applicationを刷新する計画


Slide 21

Slide 21 text

なぜCQRSなのか

Slide 22

Slide 22 text

自律性を表明する/Assert Autonomy ● 独立して行動し、協調的に相互作用するコンポー ネントを設計する ● 自律性とは、各マイクロサービスが境界を維持し 独立して運用できること ○ 当該サービスの動作保証には、連携している 他のサービスは関係がない。常に自分のサー ビスの行動を保証するのみ ● 自律性を保つにはアプリケーションを分離する必 要があります。分離には主に以下の観点がある ○ DDDの境界づけられたコンテキスト単位で分 離する ○ CQRS/Event Sourcing観点でのコマンドと クエリに分離する 在庫 EC 在庫 予測 Command Query Commandに障害が起きてもQueryできるよう にするにはお互いに分離する必要がある ドメイン境界で分割 C/Qで分割

Slide 23

Slide 23 text

CQRSとは ● Command and Query Responsibility Segregation = コマンド・クエリ責務分 離 のこと。分離というより隔離という解 釈が正しい ● コマンド(書き込み)とクエリ(読み込み)を スタックごとにそれぞれに隔離すること を意味する。単にドメインモデルをコマ ンド用・クエリ用に分割することではな い ● CQRSはDDDを前提としています(ドメイ ンから本質的ではないクエリ責務を排除 するための設計パターンです)。詳しくは CQRS Documents by Greg Young を参照 のこと Write DB Read DB Interface Adaptor Command Processor Domain Interface Adaptor Interface Adaptor Read Model Updater Query Processor Command Side Query Side Read Model Updater Client

Slide 24

Slide 24 text

書き込み(コマンド)と読み込み(読み 込み)の要件が違うのに 一つのシステムで解決しようとして 無理が生じる例

Slide 25

Slide 25 text

RDBへの書き込みがスケールしない問題 ● Writerをスケールアウトしたいが無理スジ ○ Write Sharding: Writer, Read Repclica のセットを分割して、書き分ける ○ Vitess(https://vitess.io/) ● いずれにしてもアプリケーション(もしくはミド ルウェア)で書き込みを分割する。柔軟なクエリ ができなくなるという代償を払う必要がある ○ 書き込むデータからヒントを得て、どの DBに書き込むかを決める(ヒントを間違え ると…) ○ 分割されたデータどうしでは結合するク エリはできない ○ 2台→4台→8台と手動でWriterを増やす さいデータの移動が必要になる そもそもRDBが向かない要件をRDBで解決しようと して複雑化する… 25 Writer アプリケーション A-1 B-1 A-2 B-2 ReadReplica ReadReplica Writer アプリケーション A-1 A-2 ReadReplica ReadReplica B-1 A-1 B-2 A-2 Writer B-1 B-2 A-1 B-1 A-2 B-2 A-1 B-1 A-2 B-2

Slide 26

Slide 26 text

NoSQLでRDBのクエリのような使い方をしてしまう問題 ● NoSQLはハッシュキーでスケールアウトできる ○ ハッシュキーで自動的にシャーディング(パー ティショニング)される ○ ただしキーでしかエンティティを解決できない ● エンティティの属性でもクエリしたい… ○ GSIを多用する ■ 個数の上限がある ○ 上限があるなら、転置インデックスを作ろう ■ エンティティの更新以外にインデックス データも更新する必要がある ■ エンティティの取得の前にインデックス の解決が必要になる NoSQLで複雑なクエリ要件を取り込もうとしてシステムが複 雑化する… DynamoDB アプリケーション A-1 B-1 A-2 B-2 DynamoDB { A-1, 技術部, KATO } B-1 { A-2, 総務部, SATO } B-2 GSIで部署名で検索できるように … EMP { A-1, 技術部, KATO } { A-2, 総務部, SATO } EMP_DEPT_IDX { 技術部, [ A-1, A-3 ] } { 総務部, [ B-2, B-4 ] } 逆引きする際は、EMP_DEPT_IDXでIDを解決 してからEMPを引くことになる… 転置インデックス

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

【FYI】「注文するコマンド要求」と「注文情報クエリ結果」の違い ● コマンド要求はシステムに送られるメッセージ ○ システム向けのデータは正規化される ● クエリ結果はシステムから返されるメッセージ ○ 人間向けのデータは非正規化される 注文ID 注文日時 商品ID 注文数 購入者ID 注文ID 注文日時 商品ID 商品名 注文数 購入者ID 購入者名 商品 アカウント 注文するコマンド要求 注文情報のクエリ結果 system

Slide 29

Slide 29 text

CQRSではない場合の問題(1/2) ● クエリ要件を満たすことでリポジトリが複雑になる。クエリするだけでドメインロ ジックを呼び出さない。他にもページングやソートも扱うケースがある…。 ● レスポンス用DTOをリポジトリで組み立てるため、非効率なN+1クエリが発生する。 もはやリポジトリは適切な手段ではない…。適切な方法でやろうよ… val employees = employeeRepository.findByDeptIdsWithEmpNamePatterns(deptIds, empNamePatterns) // このあとに、ドメインロジックはない。 DTOに詰め直してクライアントに返すだけ。 // ドメインロジックを起動するためではなく、データを閲覧するためだけに使っていることがある val reservationDtos = reservationRepository.findByIds(ids) // SQL発行 .map{ reservation => val hotel = hotelRepository.findById(reservation.hotelId) // SQL発行 val customer = cusotmerRepository.findById(reservation.customerId) // SQL発行 new ReservationDto(reservation, hotel.name, customer.name) // アプリケーション空間で結合及びデータを捨てる } ドメインはドメインの、クエリはクエリの都合で最適化が求められる

Slide 30

Slide 30 text

CQRSではない場合の問題(2/2) ● コマンドを意識しないデータ指向では、エンドポイント、アプリケーションサービ ス、ドメインがCRUDの用語に汚染されてしまう、という仮説がある ○ 商品の注文がcreatePurchaseItem? ○ 注文のキャンセルがupdatePurchaseItem? ● ドメインの動詞を重視するコマンド指向では、orderItem, cancelOrderなどユビキタ ス言語にフォーカスできるようになる。コマンドの表現によって意図が明白なイン ターフェイスを作ることができる ○ ただこの考え方は、CRUDであっても注意深く設計すれば可能…。 ○ 実装というより分析の段階でコマンドを使うことのメリットが強い CQRSは非機能の観点が注目されがちだが、本来の目的はコマンド指向の ドメインモデリングにある…

Slide 31

Slide 31 text

CQRSではない場合の問題(2/2) ● 利点 ○ コマンドとクエリが分離しているため、耐障害性が高くなる(耐障害性に 寄与する)。別々にデプロイできる ○ コマンドとクエリを必要に応じて個別に最適化できる(弾力性に寄与す る)。別々にスケールさせることもできる ● 欠点 ○ 非CQRSと比べてコストが掛かる。目的ごとにサブシステムを分けるの で構成要素が多くなる ○ CQRSではC/Qごとにモデルが分離するため、単一モデルとしてシンプ ルだがネットワーク全体としては複雑になる。

Slide 32

Slide 32 text

なぜEvent Sourcingなのか

Slide 33

Slide 33 text

Event Sourcingとは ● 唯一信頼できる情報源(Single Source Of Truth)は、状態(ステート)ではなく(ドメイ ン)イベントという考え方 ○ CRUDは、従来からの最新状態を常に上書きする ● コマンドとクエリを統合するために使う Event Sourcing CRUD(State Sourcing) Account { ID=1, NAME=KATO } Account { ID=1, NAME=SATO } AccountCreated{ ID=1, NAME=KATO } AccountRenamed{ ID=1, NAME=SATO } 最新のエンティティを上書きする そのときのイベントを追記する

Slide 34

Slide 34 text

【FYI】ドメインイベントとは ● イベントは過去に起きた出来事を意味する ● ドメインイベントは、ドメイン上のイベント を意味する ● 一般的には過去形の動詞として表現される ○ CargoShipped ○ CustomerRelocated ● イベントからコマンドが想起可能 ○ ShipCargo ○ RelocateCustomer ● イベントとコマンドは似ているが別概念 ○ コマンドは拒否されることがある ○ イベントはすでに起こったことを示す ショッピングカートのイベント

Slide 35

Slide 35 text

なぜイベントを使うのか ● CQRSはコマンド側からクエリ側に変更を伝 える必要がある ○ コマンド側のドメインイベントをクエ リ側に伝える ● 上記の現実的な実現手段として以下がある ○ Event Sourcing ○ CDC(変更データキャプチャ)+ Outbox ■ ミドルウェアレベルではESと酷似 ● 結局はEvent Sourcing以外に現実的な選択 肢はない ○ 詳しくは CQRSはなぜEvent Sourcing になってしまうのか を参照 35 C Q C Q 変更がないときも ポーリングで負荷 をかけてしまう 変更があるとき だけイベントを 通知する ポーリングはスケールしない EventをPub/Subする

Slide 36

Slide 36 text

Event Sourcingの利点と欠点 ● 利点 ○ イベントは更新されず追記のみなので、スケーラビリティが確保しやすい ○ 特定の時点のリードモデルをイベントから導出することができる ○ ドメインイベントがあれば、リードモデルの設計をいつでもやり直せる ■ データマイグレーションコストではゼロではないが ○ 監査ログや行動履歴の分析に利用することができる ● 欠点 ○ 大量のイベントから状態をリプレイする際に時間がかかる ■ 最新状態を保存したスナップショットを使うとリプレイ時間を短縮できる ○ 原則的にすべてのイベントをストレージに保存する必要がある ■ スナップショット保存時に、古いイベントを消すことも可能

Slide 37

Slide 37 text

2 新アーキテクチャの概要

Slide 38

Slide 38 text

新アーキテクチャの概要 38 state Shard Shard ShardR egion RoomAggregateActor Journal DB (DynamoDB) SnapshotStore (S3) Message Bus RMU Read DB Read API Read API Read API Controller UseCase Write API Server Write API Server Write API Server コマンドサイド akka-clsuter 他のMSへ logic Client ID = 1 クエリサイド サーバーサイド・チーム クライアントサイド・チーム リアクティブシステムとCQRS/ESを反映したアーキテクチャへ変更する ● 非同期・ノンブロッキング ● スーパービジョン ● 位置透過性 ● ステートフル ● DBとの完全な同期によって読み込み不要 ● ワークロードのパーティショニング ● 正規化されたデータ構造を扱う ● ネットワーク分断時は一貫性を重視 コマンドサイドではドメインロジックを実行してドメイン状 態を変える機能のみを提供する クエリサイドはドメインイベント をもとにクライアントにとって 都合のよいリードモデルを構 築する ● 非同期・ノンブロッキング ● ステートレス ● 非正規型データを扱う ● ネットワーク分断時は可能性を重視 ● ラムダアーキテクチャでも十分可能 MessagePosted MessageDTO PostMessage MessageDTO ID = 2 ID = 3

Slide 39

Slide 39 text

【FYI】InfoQ Architecture and Design 2021 ● 本日紹介した技術はアメリカではキャズムを超えていることになっている…。日本で も、当社の事例がキャズムを超える事例の一つとなれるように!

Slide 40

Slide 40 text

まとめ ● リアクティブシステムを目指すにはそれなりのアーキテクチャが必要。当 たり前を実現するには、高い技術力が求められる。ただし、全体が複雑に なりすぎないように、濃淡をつけていけるようにしたい ● 今回触れなかった、モジュラモノリスをどのようにマイクロサービスに分 割していくかという、組織的にも大きな課題がある。組織戦略の問題と併 せて改善が求められる ● まだまだ これから事業的にも技術的にも成長の余地があるので、プロダ クトを共に発展させていくエンジニアを募集中です!!!何かあれば気軽 にご相談ください!

Slide 41

Slide 41 text

本日はご静聴ありがとうございました