CQRS

データの読み取り (Query) と書き込み (Command) を別々のモデルで処理し、それぞれを独立して最適化するアーキテクチャパターン

設計パターン分散システム

CQRS とは

CQRS (Command Query Responsibility Segregation) は、データの書き込み操作 (Command) と読み取り操作 (Query) を異なるモデルで処理するアーキテクチャパターンである。Greg Young が 2010 年頃に体系化した。

従来の CRUD アプリケーションでは、同じデータモデル (同じテーブル、同じエンティティ) を読み書き両方に使う。しかし、読み取りと書き込みでは要件が大きく異なることが多い。書き込みはビジネスルールの検証と整合性の担保が重要であり、読み取りは複数テーブルの結合や集計が必要で、パフォーマンスが重視される。

CQRS はこの非対称性を認め、読み取り用と書き込み用のモデルを分離する。

動作の仕組み

クライアント ──Command──→ Write Model ──→ データベース (正規化)
                                              │
                                         (イベント/同期)
                                              ↓
クライアント ←──Query───← Read Model  ←── 読み取り用ストア (非正規化)

Write Model はドメインロジックを含む豊かなモデルで、ビジネスルールの検証を行う。Read Model は表示に最適化された非正規化データで、JOIN なしで高速にクエリできる。

イベントソーシングとの関係

CQRS はイベントソーシングと組み合わせて語られることが多いが、両者は独立した概念だ。CQRS だけを採用し、Write Model と Read Model を同じデータベースの異なるテーブル (またはビュー) で実現することも可能だ。

イベントソーシングと組み合わせる場合、Write 側はイベントストアにイベントを追記し、Read 側はイベントを購読して読み取り用のビューを構築する。この構成では読み取りデータは結果整合性 (Eventual Consistency) となる。

実務での適用判断

CQRS が有効なケース:

  • 読み取りと書き込みのスケーリング要件が大きく異なる (読み取りが書き込みの 100 倍以上)
  • 複雑な集計クエリが必要で、正規化されたデータモデルではパフォーマンスが出ない
  • 複数のマイクロサービスのデータを結合した読み取りビューが必要

CQRS が過剰なケース:

  • 単純な CRUD アプリケーション
  • 読み取りと書き込みの要件に大きな差がない
  • 強い整合性が必須で、結果整合性を許容できない

よくある落とし穴

最大の落とし穴は、結果整合性の扱いだ。Write Model に書き込んだデータが Read Model に反映されるまでにタイムラグがある。ユーザーが注文を確定した直後に注文履歴を見ると、まだ反映されていない可能性がある。この問題には「書き込み直後は Write Model から直接読む」「楽観的 UI 更新で即座に画面に反映する」などの対策がある。

「マイクロサービスパターン」(Chris Richardson 著) 第 7 章で、API コンポジションと並ぶクエリパターンとして詳しく解説されている。DDD の文脈では「実践ドメイン駆動設計」(Vaughn Vernon 著) が参考になる。

CQRS の構造

責務 データストア
Command データの変更 DynamoDB
Query データの取得 OpenSearch, GSI

CQRS vs 従来の CRUD

観点 CRUD CQRS
モデル 読み書き共通 読み書き分離
複雑さ 低い 高い
スケーラビリティ 高い (読み書き独立)
用途 シンプルな CRUD 読み書きの負荷が異なる
// Command
await db.put({ TableName: "orders", Item: order });
// DynamoDB Streams → Lambda → 読み取り用ビューを更新

// Query (最適化されたビュー)
const results = await opensearch.search({ index: "orders", body: { query } });

CQRS の関連書籍も参考になる。

関連用語