Save 37% off PRO during our Black Friday Sale! »

ChatworkDevDay_リアクティブシステムと次世代基盤について_加藤

 ChatworkDevDay_リアクティブシステムと次世代基盤について_加藤

Chatworkでは、リアクティブシステムとCQRS / Event Sourcingを反映した次世代基盤を構想しています。

まだ検討段階ではありますが、なぜそれらを採用するのかメリット・デメリットも含めてご説明します。

- リアクティブなソフトウェア・アーキテクチャを実現するためには?
- なぜCQRS(Command Query Responsibility Segregation)を用いるのか?
- なぜEvent Sourcingを用いるのか?
- 次世代基盤の実現に向けて

933291444e456bfb511a66a2fa9c6929?s=128

かとじゅん

May 26, 2021
Tweet

Transcript

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

  2. None
  3. 自己紹介 • @j5ik2o • Chatwork のテックリード • 10 歳で初めてプログラミングに触れる。SIer としてさまざまな現場での業務を経験した後、

    2011 年より某d社、2013 年より大手ソーシャ ルゲーム企業で、それぞれ Scala やドメイン 駆動設計を採用したシステム開発に従事 • 2014 年 7 月より Chatwork に参画。現在は Chatwork 次期アーキテクチャのプランニン グや設計、開発に携わる • 最近はまっていること。オートミールうまいす よ
  4. AGENDA 1. リアクティブシステムと CQRS/Event Sourcingについて 2. 新アーキテクチャの概要

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

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

  7. アーキテクチャ刷新(Sagrada)の目的と手段 • 目的 ◦ 2025年の事業計画に合わせて、生産性を維持・向上できるプロダクトと 組織を構築する • 手段 ◦ プロダクトの健全性を維持・向上させること

    ◦ チームが独立して改善活動をおこなえること ◦ 大規模なアジャイル開発が実践できること ◦ いつでもレスポンスを返せる状態になっていること ▪ リアクティブシステムを反映したアーキテクチャを実現する ▪ CQRS/Event Sourcingシステムを実現する 社会インフラを担うからこそ必要になる手段 この課題に対する考え方を解説します
  8. ところで…ユーザの前でこういう言い訳できますか… • 「サーバ台数が足りなくて…」とか…。確かに難しい問題だが… • 「いやー、よくあることですよ」とは口が裂けても言えない。それは提供側の理屈 • 使いたいときに使えないシステムは、利用者から見限られる • 競合は探せばいくらでも見つかる時代、利用者は競合へ乗り換え可能

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

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

  11. 「止まらないシステム」ではなく「回復力があるシステム」が求められている • 東京証券取引所, 株式売買システムで全銘柄で売買不能になった (2020/1) ◦ NASのフェイルオーバーができなかったことが原因 ▪ NASのマニュアルでは待機系への切替の初期設定が「15秒で切替」と なっていたが、実際の設定は「OFF」だった。人為的な要素が強い…

    ▪ NASが故障することで売買システムの全機能を失ってしまった ◦ 恒久対策は回復力を向上させること。異常を起こした部分を切り離し障害か ら回復できるアーキテクチャに変更していくために、MSAに移行していく、 とのこと 止まらないシステム(全く障害を起こさない完全なシステム)を目指すのではなく 【障害から回復する能力を設計すること】に価値がある
  12. ここからは、 リアクティブな ソフトウェア・アーキテクチャ を実現するにはどうすればよいか という論点になります

  13. リアクティブシステムとは 支える原理 手段 届けたい価値 即応性(Responsive) メッセージ駆動(Message-driven) 伸縮性(Elastic) 耐障害性(Resilient) 最終的な目的 非同期・ノンブ

    ロッキング 、位置透過性 回復力のある設計 Resilient By Design 必要な手段
  14. メッセージ駆動とは リアクティブシステムは【⽴同期・ノンブロッキング】なメッセージ・パッシングによってコン ポーネント間の境界を確⽴する リアクティブシステムは【⽴同期・ノンブロッキング】なメッセージ・パッシングによってコン ポーネント間の境界を確⽴する CartClient<Actor> Cart<Actor> AddCartItem { cartId

    = 1, cartItemId = 4, itemId = 1, itemNum = 1, } CartItem { 1, 1, … } CartItem { 1, 2, … } CartItem { 1, 3, … } タスクの完了を待 たない AddCartItemSucceeded AddCartItemFailed タスクの成否をメッセージで 返答する タスクがなければリ ソースを消費しない メッセージに反応する かどうか受信コンポー ネント次第 メッセージが届くなら ばリモートでもローカ ルでもよい タスクを依頼するた めにメッセージを送 信する
  15. 【FYI】「リアクティブシステム」と「リアクティブプログラミング」は同じ概念ではない • リアクティブシステムとリアクティブプログラミングという用 語に頻繁に遭遇するが。これらは等価ではない • リアクティブシステムはアーキテクチャレベルでリアクティブ 原則を適用する。リアクティブシステムを実現する手段として Future/Promise, Reactive Streams,

    アクターモデルなどのリ アクティブプログラミングが利用されます。だからといって、 自動的にリアクティブシステムになりません • 例えば、アプリケーションを1ノードだけにデプロイした場 合、そのノードが故障したら全システムを失う。これではリア クティブ宣言の耐障害性(回復力)がないので、リアクティブプ ログラミングを使っていても、リアクティブシステムではない Node 1 リアクティブプログラミン グを使っていても1台の みで運用すると、この 1台 が故障すると全システム を失う 故障
  16. リアクティブ原則(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点を解説
  17. 失敗を受け入れる/Embrace Failure • 物事がうまくいかないことを期待し、回復力のために構築する • キーとなる考え方はBulkheading ◦ Bulkheadingは船舶由来の用語。大型貨物船の船倉は隔壁によって多くの区画に分 割される。船底が何らかの原因で破損した場合でも、影響を受けた区画だけが浸水 し、他の区画は適切に密閉された状態を維持できるため浮力を維持できる

    • 以下の図はReactive Design Patternsで紹介されている
  18. アクターモデルでどのようにBulkheadingするか • スーパーバイザであるコンポーネントは簡 単に故障するような仕事はせずに、失敗し やすい仕事はヒエラルキー下層の専門のコ ンポーネントに任せる。このような構造を 採用することで障害が発生しても、全体に 障害が波及することを抑制する • 障害発生時はスーパーバイザに判断を委任

    し、その指示に従ってコンポーネントを再 起動して復旧する。 このような階層的な再 起動を用いる障害処理によって、障害モデ ルを大幅に簡素化でき、予期しない障害に 直面しても生き残る可能性が高める
  19. なぜアクターモデルなのか • 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つの言語はマルチスレッドプログラミングおける、多くの苦しみを取り除いてくれると期待され ていた

  20. ChatworkのAkka導入 • 2016年末 メッセージング基 盤にて、Akka,HBase,Kafkaを 使って、CQRS+ESシステム を構築・運用開始
 • 他のマイクロサービスでも Akkaを積極的に採用


    • SagradaではCore Applicationを刷新する計画

  21. なぜCQRSなのか

  22. 自律性を表明する/Assert Autonomy • 独立して行動し、協調的に相互作用するコンポー ネントを設計する • 自律性とは、各マイクロサービスが境界を維持し 独立して運用できること ◦ 当該サービスの動作保証には、連携している

    他のサービスは関係がない。常に自分のサー ビスの行動を保証するのみ • 自律性を保つにはアプリケーションを分離する必 要があります。分離には主に以下の観点がある ◦ DDDの境界づけられたコンテキスト単位で分 離する ◦ CQRS/Event Sourcing観点でのコマンドと クエリに分離する 在庫 EC 在庫 予測 Command Query Commandに障害が起きてもQueryできるよう にするにはお互いに分離する必要がある ドメイン境界で分割 C/Qで分割
  23. 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
  24. 書き込み(コマンド)と読み込み(読み 込み)の要件が違うのに 一つのシステムで解決しようとして 無理が生じる例

  25. 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
  26. 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を引くことになる… 転置インデックス
  27. そもそもコマンドとクエリでは要件が異なる • データ構造だけではなく他の要件も異なるので、コマンドとクエリをそれぞれを隔離する コマンド クエリ 一貫性/可用性 トランザクション整合性を使い強い一 貫性を重視する 結果整合を使い可用性を重視する データ構造

    トランザクション処理をおこない正規 化されたデータを保存することが好 まれる(集約単位など) 非正規化したデータ形式を取得するこ とが好まれる(クライアント都合のレス ポンスなど) スケーラビリティ 全体のリクエスト比率とごく少数のト ランザクション処理しかしない。必ず しもスケーラビリティは重要ではない 全体のかなりのリクエスト比率を占め る処理をおこなうため、クエリ側はス ケーラビリティが重要
  28. 【FYI】「注文するコマンド要求」と「注文情報クエリ結果」の違い • コマンド要求はシステムに送られるメッセージ ◦ システム向けのデータは正規化される • クエリ結果はシステムから返されるメッセージ ◦ 人間向けのデータは非正規化される 注文ID

    注文日時 商品ID 注文数 購入者ID 注文ID 注文日時 商品ID 商品名 注文数 購入者ID 購入者名 商品 アカウント 注文するコマンド要求 注文情報のクエリ結果 system
  29. 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) // アプリケーション空間で結合及びデータを捨てる } ドメインはドメインの、クエリはクエリの都合で最適化が求められる
  30. CQRSではない場合の問題(2/2) • コマンドを意識しないデータ指向では、エンドポイント、アプリケーションサービ ス、ドメインがCRUDの用語に汚染されてしまう、という仮説がある ◦ 商品の注文がcreatePurchaseItem? ◦ 注文のキャンセルがupdatePurchaseItem? • ドメインの動詞を重視するコマンド指向では、orderItem,

    cancelOrderなどユビキタ ス言語にフォーカスできるようになる。コマンドの表現によって意図が明白なイン ターフェイスを作ることができる ◦ ただこの考え方は、CRUDであっても注意深く設計すれば可能…。 ◦ 実装というより分析の段階でコマンドを使うことのメリットが強い CQRSは非機能の観点が注目されがちだが、本来の目的はコマンド指向の ドメインモデリングにある…
  31. CQRSではない場合の問題(2/2) • 利点 ◦ コマンドとクエリが分離しているため、耐障害性が高くなる(耐障害性に 寄与する)。別々にデプロイできる ◦ コマンドとクエリを必要に応じて個別に最適化できる(弾力性に寄与す る)。別々にスケールさせることもできる •

    欠点 ◦ 非CQRSと比べてコストが掛かる。目的ごとにサブシステムを分けるの で構成要素が多くなる ◦ CQRSではC/Qごとにモデルが分離するため、単一モデルとしてシンプ ルだがネットワーク全体としては複雑になる。
  32. なぜEvent Sourcingなのか

  33. 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 } 最新のエンティティを上書きする そのときのイベントを追記する
  34. 【FYI】ドメインイベントとは • イベントは過去に起きた出来事を意味する • ドメインイベントは、ドメイン上のイベント を意味する • 一般的には過去形の動詞として表現される ◦ CargoShipped

    ◦ CustomerRelocated • イベントからコマンドが想起可能 ◦ ShipCargo ◦ RelocateCustomer • イベントとコマンドは似ているが別概念 ◦ コマンドは拒否されることがある ◦ イベントはすでに起こったことを示す ショッピングカートのイベント
  35. なぜイベントを使うのか • CQRSはコマンド側からクエリ側に変更を伝 える必要がある ◦ コマンド側のドメインイベントをクエ リ側に伝える • 上記の現実的な実現手段として以下がある ◦

    Event Sourcing ◦ CDC(変更データキャプチャ)+ Outbox ▪ ミドルウェアレベルではESと酷似 • 結局はEvent Sourcing以外に現実的な選択 肢はない ◦ 詳しくは CQRSはなぜEvent Sourcing になってしまうのか を参照 35 C Q C Q 変更がないときも ポーリングで負荷 をかけてしまう 変更があるとき だけイベントを 通知する ポーリングはスケールしない EventをPub/Subする
  36. Event Sourcingの利点と欠点 • 利点 ◦ イベントは更新されず追記のみなので、スケーラビリティが確保しやすい ◦ 特定の時点のリードモデルをイベントから導出することができる ◦ ドメインイベントがあれば、リードモデルの設計をいつでもやり直せる

    ▪ データマイグレーションコストではゼロではないが ◦ 監査ログや行動履歴の分析に利用することができる • 欠点 ◦ 大量のイベントから状態をリプレイする際に時間がかかる ▪ 最新状態を保存したスナップショットを使うとリプレイ時間を短縮できる ◦ 原則的にすべてのイベントをストレージに保存する必要がある ▪ スナップショット保存時に、古いイベントを消すことも可能
  37. 2 新アーキテクチャの概要

  38. 新アーキテクチャの概要 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
  39. 【FYI】InfoQ Architecture and Design 2021 • 本日紹介した技術はアメリカではキャズムを超えていることになっている…。日本で も、当社の事例がキャズムを超える事例の一つとなれるように!

  40. まとめ • リアクティブシステムを目指すにはそれなりのアーキテクチャが必要。当 たり前を実現するには、高い技術力が求められる。ただし、全体が複雑に なりすぎないように、濃淡をつけていけるようにしたい • 今回触れなかった、モジュラモノリスをどのようにマイクロサービスに分 割していくかという、組織的にも大きな課題がある。組織戦略の問題と併 せて改善が求められる •

    まだまだ これから事業的にも技術的にも成長の余地があるので、プロダ クトを共に発展させていくエンジニアを募集中です!!!何かあれば気軽 にご相談ください!
  41. 本日はご静聴ありがとうございました