$30 off During Our Annual Pro Sale. View Details »

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

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

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

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

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

かとじゅん
PRO

May 26, 2021
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

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

    View Slide

  2. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. リアクティブシステムとは
    どんな状況でも即応性を確保し「いつでも使えるシステム」を実現
    する

    即応性とは
    ○ 状況に応じてすばやく行動すること。「事故に即応した処置」
    ○ 状況・情勢にあてはまること。「現実に即応した考え」
    「そんなの当たり前だ」といえばそうだが、実現することはそれなりに難しい…

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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点を解説

    View Slide

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

    View Slide

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

    View Slide

  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つの言語はマルチスレッドプログラミングおける、多くの苦しみを取り除いてくれると期待され
    ていた


    View Slide

  20. ChatworkのAkka導入
    ● 2016年末 メッセージング基
    盤にて、Akka,HBase,Kafkaを
    使って、CQRS+ESシステム
    を構築・運用開始

    ● 他のマイクロサービスでも
    Akkaを積極的に採用

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


    View Slide

  21. なぜCQRSなのか

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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を引くことになる…
    転置インデックス

    View Slide

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

    View Slide

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

    View Slide

  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) // アプリケーション空間で結合及びデータを捨てる
    }
    ドメインはドメインの、クエリはクエリの都合で最適化が求められる

    View Slide

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

    View Slide

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

    View Slide

  32. なぜEvent Sourcingなのか

    View Slide

  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 }
    最新のエンティティを上書きする そのときのイベントを追記する

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide