DBAtlas · データベース運用アトラス

リファレンスアーキテクチャ: マイクロサービスのデータ層

マイクロサービスで最も重要な判断はデータの所有権: 各サービスは自らのデータを所有し (database-per-service)、他のサービスのテーブルに直接アクセスしてはならない。このツリーは、共有 DB のアンチパターン、分散トランザクションなしでのサービス間の一貫性の実現 (saga + outbox)、CDC によるデータの同期、サービス間のクエリ (API composition vs CQRS の読み取りモデル) を段階的に説明する。目的は、サービスを真に独立させ、個別にデプロイ可能な状態に保つこと。

サービスはどのようにデータを所有しているか: 共有 DB か、サービスごとか?

データの分離はマイクロサービスアーキテクチャの根幹。サービスは単一の共有データベースを使用している (隠れた依存関係) のか、各サービスが独自のデータベースを持っている (正しいパターン) のか、それとも本当の課題はサービス間の一貫性/クエリなのか? まず現状を把握する。

  • すべてのサービスが単一の共有 DB を使用している
  • 各サービスが独自の DB を持つが、処理にサービス間の一貫性が必要
  • 各サービスが独自の DB を持つが、結合したクエリ/レポートが必要

サービス間のワークフローの一貫性をどう保つか?

複数のサービスにまたがる処理 (注文作成 → 決済 → 在庫の減算) では、従来の分散トランザクション (2PC) は脆弱でスケールしない。代わりに、フローをローカルトランザクション + イベントのステップに分割し (saga)、イベントを確実に発行する (outbox)。どの問題に取り組んでいるか?

  • 複数サービスのワークフローをアトミックに保とうとしている (2PC?)
  • ローカルの書き込みとイベント発行の一貫性を保てない (二重書き込み)

複数のサービスのデータを表示するクエリをどのように提供しますか?

データがサービス間に分割されると、単一の結合で統合画面を構築することはできない。主に 2 つの方法がある: 要求時に関連サービスの API を呼び出して結果をマージする (API コンポジション)、またはイベントで更新される事前結合済みの読み取りモデルを維持する (CQRS)。クエリの頻度と複雑さによって選択が決まる。

  • 単純で頻度の低い結合。いくつかのサービスを呼び出してマージすれば十分
  • 複雑/頻繁な統合クエリ。既成の読み取りモデルが必要

推奨: Database-per-service (共有 DB を分割する)

複数のサービスが 1 つのデータベースを共有することは、隠れた危険な依存関係: あるサービスのスキーマ変更が他のサービスを壊し、テーブルの所有権が不明確になり、独立したデプロイが不可能になる。各サービスが自らのデータを所有する必要がある。

推奨: Saga パターン (分散トランザクションを避ける)

複数のサービスにまたがる処理を、単一の分散トランザクション (2PC) でアトミックにしようとしないこと。2PC はロックを保持し、単一点の脆弱性と低い可用性をもたらす。代わりに、フローをそれぞれのサービス内のローカルトランザクションであるステップに分割し、失敗時には補償ステップで取り消す。

推奨: トランザクショナルアウトボックス (二重書き込みの落とし穴)

サービスが自らの DB への書き込みとイベントの発行の両方を行う場合、2 つを別々に行うのは「二重書き込み」の落とし穴: 一方が成功し、もう一方が失敗すると、システムは不整合な状態になる。解決策は、業務データと同じローカルトランザクション内でイベントを outbox テーブルに書き込み、別のプロセスで発行すること。

推奨: API composition (リクエスト時に結合)

結合したデータが必要な場合、呼び出し元 (例: ゲートウェイ/BFF) が該当するサービスの API を呼び出し、結果をメモリ上で結合する。少数のサービスにまたがる単純な結合には最もシンプルな解決策であり、追加のデータコピーを保持しない。

推奨: CQRS の読み取りモデル (イベントで供給)

複雑で頻繁な結合クエリには、サービスが発行するイベントで供給される、事前に結合されクエリに最適化された読み取りモデル (マテリアライズドビュー/非正規化コピー) を保持する。書き込み側はサービスにとどまり、読み取り側は 1 か所から準備済みのデータを高速に提供する。

DBAtlas で開く →