バックエンド

【システムデザイン】CQRS(コマンド・クエリ責務分離)

CQRSについて調べてみたのでまとめてみました!

CQRS

CQRS(Command Query Responsibility Segregation)は、システムのパフォーマンスとスケーラビリティを向上させるアーキテクチャパターンです。コマンド(書き込み)とクエリ(読み取り)を明確に分離することで、それぞれに最適化されたデータモデルやDBを使用できるようにします。これにより、複雑なビジネスロジックを管理しながら、システム全体の負荷を軽減し、スムーズな拡張性を実現します。

この動画では、クラウドだと負荷に応じてリソースをそれぞれをスケールするべきだし、書き込みと読み取りでは処理特性が異なるので、クラウドネイティブなアプリではCQRSが自然な考え方であり、単純なCRUD(書き込みと読み取りで同じDB)が例外パターンだと説明されています。CQRSにすることで独立してスケーリングでき、それぞれのスキーマを定義できるため、クラウドの特性を活かせるということです。
ただしデータの整合性は結果整合性となり、ある時点では不整合なデータとなっている可能性があります。結果整合性が許容されない場合は導入ができないですが、システム全体に導入することに拘らず、一部に導入するなど柔軟に設計することができます。

よくされる設計であるレプリケーションとの比較をしながら見ていきましょう。

CQRSレプリケーション
DB定義/データモデル書き込みと読み取りで異なる書き込みと読み取りで同一
パフォーマンス最適化書き込みと読み取りのそれぞれを最適化読み取り性能の向上
データ同期タイミング
データ整合性
非同期
結果整合性
リアルタイム同期
実装コスト煩雑比較的容易
CQRSとレプリケーションの比較

DB定義/データモデル

CQRSでは、更新用DBと参照用DBが異なるテーブル構成を持つことが一般的です。書き込みと読み取りがそれぞれの目的に最適化されるため、テーブル設計が異なることが多いです。例えば、書き込み用DBはデータの整合性を保つために正規化され、読み取り用DBはクエリパフォーマンスを向上させるために非正規化されたデータモデルを使用します。

レプリケーションでは、更新用のマスターDBと参照用のスレーブDBが基本的に同じテーブル構成を持つのが一般的です。レプリケーションは、マスターDBのデータをスレーブDBにコピーする仕組みであり、スレーブDBは主に読み取り専用の役割を果たします。マスターDBに書き込まれたデータがスレーブDBに複製され、整合性が保たれる一方で、テーブル設計そのものは同一で、クエリパフォーマンス向上はインデックスの最適化やキャッシングに依存することが多いです。

パフォーマンス最適化

CQRSは、書き込みと読み取りを完全に分離し、それぞれの用途に応じた最適化を行います。書き込みに関しては、データ整合性やトランザクション管理を重視し、通常は正規化されたDBを用いて効率的な書き込み処理を実現します。一方、読み取りは非正規化されたデータモデルを使用して、クエリのパフォーマンスを最大化します。これにより、書き込みと読み取りの双方に最適化された設計が可能です。

レプリケーションは主に読み取りパフォーマンスの向上を目的としています。マスターDBに書き込まれたデータがスレーブDBに複製され、スレーブDBが読み取り専用の負荷を引き受けることで、マスターDBの負担を軽減し、全体の読み取り性能を向上させます。しかし、書き込みはマスターDBにのみ行われるため、書き込み性能の最適化は行われません。あくまで同じDB構造を複製するため、読み取り性能の強化に特化しています。

データ同期タイミング/データ整合性

CQRSでは、書き込みと読み取りが異なるDBを使用するため、データの同期は非同期で行われることが一般的です。書き込みが発生した後、そのデータが読み取りに反映されるまでタイムラグが生じる可能性があります。このため、CQRSは結果整合性を採用しており、最終的にはデータの整合性が確保されますが、リアルタイムに最新のデータが取得できるとは限りません。非同期処理によるスケーラビリティの向上や、読み取りと書き込みのそれぞれに最適化されたデータモデルを使用することで、システム全体のパフォーマンスを高めることが可能です。しかし、一時的な整合性の欠如に対する考慮が必要です。

レプリケーションでは、マスターDBに対して行われた書き込みがリアルタイムまたはほぼリアルタイムでスレーブDBに複製されます。スレーブDBは読み取り専用の役割を担い、読み取り処理の負荷を分散させることで、システムのパフォーマンスを向上させます。レプリケーションは、スレーブDBがマスターDBと強い整合性を保つよう設計されていますが、レプリケーション遅延が発生する可能性があるため、データが完全に同期されるまでにわずかなズレが生じることがあります。それでも、レプリケーションは、データの一貫性を高いレベルで維持しつつ、読み取り性能を向上させる手法として有効です。

実装コスト

実装コストを比較すると、CQRSの方が高くなる傾向があります。

CQRSでは、書き込みと読み取りを完全に分離するため、異なるデータモデルやアーキテクチャをそれぞれに設計する必要があり、システム全体が複雑になります。書き込み用のDBと読み取り用のDBを別々に管理するため、インフラの設計・運用や、データの非同期同期を行うための仕組みの構築が求められます。このため、開発コストや運用コストが増大します。さらに、書き込みと読み取りに異なるソースコードを持つことが一般的で、それぞれを開発・テスト・保守するためのリソースも必要になります。

一方、レプリケーションの実装コストは比較的低くなります。レプリケーションは既存のDB構造をコピーする仕組みであり、書き込み用のマスターDBと読み取り専用のスレーブDBを持つことで読み取り性能を向上させますが、同じデータモデルを使用するため、新たに設計や開発が必要になる部分は少ないです。DBMSが提供するレプリケーション機能を活用すれば、設定と管理が比較的容易で、システムの設計もシンプルに保たれます。そのため、開発やメンテナンスにかかるコストはCQRSと比べて低くなります。

まとめ

関連事項としてイベントソーシング(書き込みDBから読み取りDBへの同期)があります。冒頭の動画では、書き込みDBに対して更新すらも許容せず追記だけにして履歴テーブルとして扱い、読み取りDBにデータを同期する手法が紹介されていました。
こちらの記事などもわかりやすく書いてあり、イベントソーシングについてはまたアウトプットしていきたいと思います!

-バックエンド
-,