デメリット 1. マイグレーション(テーブルスキーマ変更)コスト 2. アプリからのデータベース接続コスト a. データベースユーザーが異なる場合, セッションも異なる 3. テナント作成時に、毎回データベースの初期化処理が必要 a. テーブル作成、データベースユーザーの認証設定 4. テナントとデータベースのリレーションが別途必要 Tenant B Tenant A
マイグレーション(テーブルスキーマ変更)コスト 2. テナント作成時に、毎回テーブルの初期化処理が必要 a. テーブル作成 3. テナントとテーブル名のリレーションが別途必要 4. テナントのテーブル間で外部キーが煩雑になる 3. データベースは共有、テーブルをテナント毎に作成 Global Tenant A tables Tenant B tables
Foo 2 B Bar メリット 1. マイグレーション(テーブルスキーマ変更)コストが低い 2. データベース側での設定コストが低い デメリット 1. 他テナントのレコードへアクセスが容易 a. 同一テーブルの為, 最もカジュアルにアクセス可能 2. ロジックを実装する必要がある a. アプリ側で制御する場合 i. WHERE 句 b. データベース側で制御する場合 i. Row-Level Security での制御 ii. ストアドプロシージャを実装しての制御 4. テーブルにテナント識別カラムを持ち, 行単位で制御 Global tables
子テーブルでは従来, 親テーブルへの外部キー制約だけで充分. 冗長にカラムを付ける理由は, 1. ORM などで, WHERE句に TenantID を機械的に付けれる a. カラム有無を考えなくて良い 2. JOIN 時にも TenantID を機械的に付けれる 3. 子テーブルから自テナントデータのみを取得可能 4. 親, 子テーブルで異なる TenantID のデータを排除 5. シャーディングが必要になった際, キーに使える テーブルスキーマ ID TenantID Name 1 A Foo 2 B Bar parents ID ParentID TenantID Name 11 1 A Foo 22 2 B Bar children
起きてはいけない不整合データを取得時に排除可能. ※ INSERT時に検知すべきかつ, 従来のリレーショナルモデル では考慮しなくてもよい問題ではある... 例.リレーションのあるテーブル間で異なる TenantID のレコード テーブルスキーマ ID TenantID Name 1 A Foo 2 B Bar parents ID ParentID TenantID Name 11 1 A Foo 22 2 XXX Bar children SELECT * FROM parents INNER JOIN children ON parents.ID = children.ParentID AND parents.TenantID = children.TenantID WHERE parents.TenantID = "B";
ID 指定でリソース取得するケースでも, 機械的に TenantID を付けてバリデーションできる. テーブルスキーマ ID TenantID Name 1 A Foo 2 B Bar parents ID ParentID TenantID Name 11 1 A Foo 22 2 B Bar children SELECT * FROM children WHERE TenantID = "B"; SELECT * FROM children WHERE TenantID = "B" AND ID IN ("ID1", "ID2", ...);
で, 分散データベースの階層型のデータモデル(The Cluster Hierarchical Model)が触れられている. リレーショナルモデルでは, 親テーブルへの外部キーを持つカラムだけでリレーションを表 現可能. しかし, 分散データベース環境化では従来のリレーショナルモデルの外部キーだ けではトランザクション, ジョインなどのコストが高価になる. 先祖(親)の ID をプライマリキーに含めることで, 物理的にも同じマシンでの処理が行え る. • Google F1 • Designing your SaaS Database for Scale with Postgres / Citus Data • Sharding a multi-tenant app with Postgres / Citus Data テーブルスキーマ
TenantID という型を新たに定義して使用 underlying type は, プリミティブな string 型 A defined type の主なメリット • 関数引数, 返り値の間違いや代入ミスをコンパイル時にエラーにできる • 独自メソッドを実装できる 型(Type)の実装 type TenantID string
database table schema テーブル定義から, Go の Struct 生成を行う. xo というツールで自動生成. TenantID カラム生成時には, 独自定義した型を使用している. 型(Type)の実装 type TableA struct { ID string TenantID TenantID Name string }
Raw, Exec は, 記述したSQLをそのまま実行できる機能 • 基本的に使用させない.(CIでの検知) • 管理画面など, 例外的には使用可能 ORM var result Result err := db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)