テック技報最新号へ →
インフラ

複数プロダクトで 1 つの DB を共有する: テナント分離・履歴・削除・権限を「1 行で付く」ようにする

顧客データをプロダクトごとの DB ではなく単一の集約 DB に置き、テナント分離などの決まりごとを ORM ラッパーで強制する設計。estie の事例。

@matsu7874 · 2026-10-13
#infra#database#postgresql#multitenant#saas#security#design

どんなときに使う?

  • 1 社で複数の SaaS を出していて、セキュリティ水準をプロダクトごとにばらつかせたくない
  • 同じ顧客データ(ビル、取引先など)を複数プロダクトで使い回したい
  • 共通基盤を「専任チームが作るもの」ではなく「各チームがテーブルを足していく置き場」として運用したい

集約 DB で全テーブルに強制すること

要件実装
テナント分離全レコードに企業 ID を記録し、トランザクション開始時に認証情報から企業 ID を宣言、DB 設定で一致する行だけ読めるようにする(PostgreSQL)
操作ログ閲覧・編集・削除・権限変更のたびにログを出す
変更履歴行をイミュータブルにし、更新は新しいバージョンの insert にする
退会時の一括削除全データの ID を 1 つのメタデータ表で管理し、「ある企業の全データ ID」をすぐ引けるようにする
データごとの権限全データ ID に閲覧できる企業・部署・ユーザーを記録し、操作時に確認する

各チームに意識させない工夫

  • メタデータ表: データ ID を全テーブル横断でユニークにすれば、権限や ID 管理は 1 つの表で済む
  • API 内部: Rust のマクロで ORM(sea-orm)のラッパーを生成。テーブルごとに private_data_type!(table_name); を 1 行書くと、find() の代わりに find_visible_and_latest(org_id, user_id)(権限表の join と、ID ごとの最新版の取得)、insert() の代わりに create_object(...)(権限表への書き込み・ID 発行・メタ列の設定・操作ログ)が使える
  • ミドルウェア: 企業 ID の宣言は 1 回作れば全 API で共通
  • 実テーブル定義: ここはまだ仕組み化できず、雛形のコピーとレビューで担保している

権限モデルはあえて単純に

  • ファイルシステムのような「親の権限が子に及ぶ」モデルは、ビル・区画・募集・商談のように種類が多く親子関係が木にならないデータでは破綻しやすい
  • そこで「データごとに見てよい主体を直接記録する」単純な方式を選び、業務を理解してから作り直せる余地を残した

注意点

  • PostgreSQL 17 + Rust + sea-orm での実装例。別スタックなら同じ考え方を ORM のラッパーや Row Level Security で置き換える
  • 記事は 2026 年 6 月時点の設計

出典

関連

X の元の投稿を開く