CQRSとは? ― 「書く」と「読む」を分離するアーキテクチャパターン
CQRS(Command Query Responsibility Segregation)とは、データの書き込み(コマンド)とデータの読み取り(クエリ)の責務を明確に分離する設計パターンです。従来のCRUDアーキテクチャでは同じモデル(テーブル、エンティティ)を使って読み書きの両方を行いますが、CQRSでは書き込み用のモデルと読み取り用のモデルを別々に設計します。
レストランの厨房で例えると、注文(コマンド)を受け付ける窓口と、料理の提供(クエリ)を行う窓口が別々に分かれているようなものです。厨房内部は注文に最適化された動線で、提供窓口は配膳に最適化されています。それぞれの窓口が独立しているため、混雑時でも効率的に対応できます。
なぜCRUDの「一つのモデルで全部やる」方式に限界があるのか
シンプルなアプリケーションでは、1つのデータモデルで読み書きを兼ねても問題ありません。しかし、システムが大規模化すると次のような課題が浮上します。
読み取りと書き込みの負荷バランスが異なる:多くのシステムでは読み取りが書き込みの10倍〜100倍の頻度で発生します。1つのモデルでは、書き込みに最適化すると読み取りが遅くなり、その逆も起こります。
読み取り用のクエリが複雑化する:画面表示に必要な情報が複数テーブルにまたがると、JOINの嵐になりパフォーマンスが低下します。書き込み用の正規化されたテーブル構造は、読み取りに最適とは限りません。
セキュリティ要件が異なる:書き込み操作には厳格な権限チェックやバリデーションが必要ですが、読み取りはより緩やかでよい場合があります。同じモデルでこれらを処理すると、コードが複雑になります。
CQRSの基本構造を図解的に理解する
CQRSの構造はシンプルです。システムを大きく2つの側面に分けます。
コマンド側(Write Side)は、データの作成・更新・削除を担当します。ドメインロジック(ビジネスルール)のバリデーションを厳密に行い、データの整合性を保証します。正規化されたデータベース設計が適しています。
クエリ側(Read Side)は、データの参照・表示を担当します。画面表示に最適化された非正規化テーブル(リードモデル)を持ち、JOINなしで必要な情報を一度に取得できます。
両者のデータ同期は、書き込みが完了した後にイベントやメッセージを使ってリードモデルを更新する仕組みで行います。このため、書き込み直後に最新データが読めない「結果整合性(Eventual Consistency)」が生じる場合があります。
CQRSの3つの実装レベル
CQRSは「全か無か」ではなく、段階的に導入できます。
レベル1:コード上の分離
同じデータベースを使いつつ、コマンドハンドラーとクエリハンドラーをコード上で分離します。最も手軽で、既存プロジェクトへの導入が容易です。
レベル2:データベースの分離
書き込み用DBと読み取り用DBを物理的に分け、イベントやメッセージキューでデータを同期します。読み取り側を独立してスケールアウトできるため、大量の読み取りリクエストに対応できます。
レベル3:イベントソーシングとの統合
書き込み側でイベントソーシングを採用し、すべての状態変化をイベントとして記録します。クエリ側はイベントから構築されたリードモデルを参照します。最も強力ですが、複雑さも最大です。
CQRSが効果を発揮する具体的なシーン
ECサイトの商品一覧と注文処理:商品一覧(読み取り)は大量のアクセスに耐える必要がありますが、注文処理(書き込み)は整合性が最優先です。リードモデルをElasticsearchやRedisに配置することで、検索・表示を高速化できます。
ダッシュボード・レポーティング:複雑な集計クエリはトランザクション用DBに負荷をかけます。専用のリードモデルに集計済みデータを持たせることで、レポート生成を高速化しつつ、本番DBへの影響をゼロにできます。
マイクロサービス間のデータ参照:サービスAのデータをサービスBから参照したい場合、直接APIを呼ぶと依存が生まれます。サービスAがイベントを発行し、サービスBが自分用のリードモデルを構築すれば、疎結合を保てます。
導入前に知っておくべき注意点
結果整合性の許容:書き込み直後に最新データが表示されないタイミングが生じます。ユーザー体験に影響する場面では、楽観的UI更新(書き込み成功を前提に先に画面を更新する手法)で対処するのが一般的です。
複雑さのトレードオフ:小規模なCRUDアプリに無理にCQRSを適用すると、設計・実装・運用すべてが複雑になります。「読み取りと書き込みの要件が明確に異なる」場合にのみ採用を検討しましょう。
データ同期の信頼性:コマンド側からクエリ側への同期が失敗すると、データの不整合が生じます。メッセージキュー(RabbitMQ、Kafka等)によるリトライ機構や、障害時のリプレイ手段を設計に組み込む必要があります。
よくある質問(FAQ)
Q. CQRSはマイクロサービスでしか使えませんか?
A. いいえ。モノリシックアプリケーションでもCQRSは有効です。特にレベル1(コード上の分離)なら、既存のモノリスに段階的に導入できます。
Q. CQRSとイベントソーシングは必ずセットで使う必要がありますか?
A. いいえ。CQRSはイベントソーシングなしでも使えますし、その逆も可能です。両者は相性が良いですが、独立した概念です。まずはCQRS単体で導入し、必要に応じてイベントソーシングを追加するのが現実的です。
Q. 結果整合性はどの程度の遅延がありますか?
A. システム構成によりますが、一般的にはミリ秒〜数秒程度です。メッセージキューの処理速度とリードモデルの更新速度に依存します。リアルタイム性が求められる機能だけ同期的に処理する、というハイブリッドアプローチも可能です。
まとめ
CQRSは、読み取りと書き込みの要件が大きく異なるシステムにおいて、それぞれを最適化するための設計パターンです。段階的に導入でき、イベントソーシングやマイクロサービスとも相性が良い一方、結果整合性やデータ同期の複雑さというトレードオフがあります。「本当にCQRSが必要か」をビジネス要件から見極めることが、成功の第一歩です。

コメント