マイクロサービスデータ所有権:領域 owner を先に決めてから、DB 分割と移行を考える
技術ノート
公開 / DavidHLPL
結論優先:マイクロサービス分割の最初のステップは、データベースを複数台に分けることではなく、bounded context、最終書き込み owner、データアクセス契約を決めることだ。サービスデータは API/メッセージ経由でアクセスする。共有物理データベースサーバーは移行期のデプロイとして許容できるが、複数サービスが同じ業務 schema/テーブルを読み書きするのが主な結合だ。移行は expand → migrate → contract の順に進め、contract は旧オブジェクトの削除前に rollback window を閉じなければならない。
1. 四層の順序で owner を判定する
ビジネス能力 / bounded context
-> owner service
-> private schema/table
-> API/event contract
-> consumer と移行ゲート
「今だれがこのテーブルを直接参照しているか」から owner を逆算してはならない。それはたいてい歴史的な結合を表すだけだ。一つの領域には最終書き込み owner が一つあるべきだ。他のサービスの複製テーブル、read model、cache は派生データだと明示し、リフレッシュ・無効化・再構築の方法を定義しなければならない。
2. database-per-service の境界
database-per-service の核心は、永続化データがサービスに属し、他のサービスがサービス API/メッセージを迂回して業務テーブルへ直接アクセスしてはいけない、ということだ。データベースはサービス実装の一部なので、schema と migration も owner の変更責任に含めるべきだ。
見落とされがちな区別がある:
| 形態 | 自動的に共有データ境界になるか |
|---|---|
| 複数サービスが同じ物理データベースサーバーを共有 | ならない。移行期として schema/database を分離できる |
| 複数サービスが同じ業務 schema/テーブルを読み書き | なる。開発期と実行期の結合を生む |
| モジュラーモノリスのモジュール私有テーブル | 必ずしも DB 分割は不要。まずモジュール境界を保つ |
共有 schema の変更はすべてのアクセス側の調整を要し、クエリやロックは実行時にも互いに影響する。だから「複数プロセスに分割済み」は「データ自律が完了した」ことを意味しない。
3. クロスサービス一貫性:saga と outbox はそれぞれ一つの課題を解く
Saga
複数サービスや複数リソースにまたがるフローを複数のローカルトランザクションに分け、イベント/コマンドで進め、失敗時には補償トランザクションを実行する。Saga は中間状態の可視性、補償の冪等性、失敗の再試行可能性を要求する。ローカルトランザクションを透明な 2PC に変えるものではない。
本当に複数サービス・複数リソースにまたがり、境界を引き直せない場合にだけ saga を導入する。単一サービス内の更新が「将来分割するかもしれない」からといって、前倒しで分散トランザクションにしてはいけない。
Transactional outbox
業務状態と outbox レコードを同じローカルトランザクションで書き込む。コミットすれば両方が存在し、ロールバックすれば両方が消える。relay がイベント公開を担う。これは業務更新とメッセージ記録の間の原子性の断絶を解決する。
Outbox でも consumer の冪等性、重複配信処理、順序ポリシー、監視が必要だ。イベント駆動の公開経路がないなら導入する必要はない。
4. 段階的移行の三つのフェーズ
Expand
テーブル、フィールド、API を追加し、旧リード/ライトが動き続けるようにする。まず互換面を作り、最初のステップで旧カラムを削除したり唯一の writer を切り替えたりしない。
Migrate
ダブルライトまたは CDC での複製を使い、新旧データとイベント結果を検証しつつ、読み取り経路を徐々に新 owner へ切り替える。ダブルライトの失敗、遅延、リプレイには観測と補償が必要だ。
Contract
新システムが system of record になった後、旧オブジェクトと互換ロジックを削除する。削除は rollback window の閉鎖を意味するので、まず検証、バックアップ、アラート、代替ロールバック案を確認する。リクエストを途中で受けて新旧システムへ振り分けられない、旧システムを変更できない、検証可能な同期経路がない場合は、strangler fig を機械的に適用すべきではない。
最小領域監査
分割や移行の前に、領域ごとに記録する:
- bounded context と業務 owner;
- 現在の system of record と最終 writer;
- 同じテーブルを直接読み書きするすべてのサービス、スクリプト、レポート;
- API、イベント、outbox がすべての変更経路をカバーしているか;
- saga が本当に必要か、補償は冪等か;
- expand/migrate/contract の検証ゲート;
- contract 後のバックアップとロールバック代替;
- 共有物理インスタンスと共有 schema の違い。
このチェックリストは判定基準であって、特定プロジェクトの事実ではない。プロジェクト側は固定コミット内の schema、クエリ、設定、呼び出し元を読み続けなければならない。
よくある誤解
- 「一サービス一データベースサーバーでなければ所有権ではない。」 所有権はまずアクセス境界と最終 writer で定義される。物理的な分離は段階的に完了できる。
- 「共有テーブルは開発の便利さにすぎない。」 それは同時に schema 変更の調整と実行時のロック/クエリ結合を生む。
- 「Saga は分散トランザクションの痛みのない代替だ。」 補償、中間状態、冪等性の要求を追加する。
- 「outbox を足せばメッセージが一度だけ送られる。」 outbox はレコードとローカルトランザクションの一致を保証するが、重複配信をなくすものではない。
- 「contract は最後に旧コードを削除するだけだ。」 それは rollback window を閉じることであり、独立した移行ゲートとして扱わなければならない。
証拠の境界
revision-pinned ドキュメントと live ページの階層はリポジトリ src/content/raw/zh-cn/microservice-domain-data-ownership-correction.md にある。初期スナップショットも訂正スナップショットも、現在のプロジェクトが実際にどのサービス、テーブル、owner を持つかを証明しない。実際の帰属には対象リポジトリのソースと schema の証拠が必要だ。
出典と関連文書
研究記録
- 元の文書: src/content/note/ja/microservice-data-ownership.md
- 出典リビジョン: f49953fb0d4bd52a39bf81167b0c4b7dd7d78a64
- 証拠の状態: 有効