B2BウェルネスSaaS向けマルチテナントDBスキーマ設計
スキーマ単位テナントモデルで厳格なデータ分離を確保する、B2B SaaSプラットフォーム向け安全でスケーラブルなPostgreSQLデータベース設計のアーキテクチャケーススタディ。
このページの内容
急速に成長するHealthTech セクターにおいて、B2BウェルネスSaaSプラットフォームを構築するには、独特の課題が発生します。法人クライアントは堅牢なセキュリティと、従業員の機微なウェルネスデータが他のクライアントから完全に隔離されていることを絶対的に確信できる状態を求めます。架空のB2Bウェルネスプロバイダー「WellnessCorp」のリードデータベースアーキテクトとして、私はセキュリティ、スケーラビリティ、パフォーマンスをcompromiso(土俵)ことなく、数百社の法人クライアントにサービスを提供できるPostgreSQLデータベースを設計する任務を負いました。
この事例研究では、マルチテナントデータベース設計のアーキテクチャ上の決定事項、トレードオフ、実施の詳細を詳しく解説します。私たちが**Schema-per-Tenant(テナントごとのスキーマ)**モデルを選んだ理由と、これがB2B SaaSプラットフォームにどのように強力なソリューションを提供するかを説明します。
前提条件:
- リレーショナルデータベースとSQLについての確かな理解
- PostgreSQLの基本的な知識
- ソフトウェアアーキテクチャとSaaSアプリケーション設計への関心
問題の理解
B2BウェルネスSaaSにおいて、データプライバシーは単なる機能ではなく、根本的な要件です。ある会社の従業員ウェルネスデータが別の会社に漏洩するデータ侵害は、信頼の喪失そして潜在的には法的責任をもたらし、壊滅的な結果につながるでしょう。したがって、私たちの主要な課題はテナント間の厳格なデータ隔離を実施することでした。
私たちは3つの一般的なマルチテナンシーモデルを評価しました:
-
Database-per-Tenant(テナントごとのデータベース): 各テナントが各自の専用データベースを取得
- 長所: 最大限の隔離とセキュリティ
- 短所: 特に多くのテナントがある場合、運用上のオーバーヘッドが高い。数 百または数千のデータベースのマイグレーション、バックアップ、接続の管理は、悪夢のような状況になり得る
-
Shared Database, Shared Schema with a
tenant_idColumn(共有データベース、共有スキーマ、tenant_id列を使用): すべてのテナントが同じデータベースとテーブルを共有し、tenant_id列でデータを区別- 長所: セットアップと管理がシンプルで、リソース消費が低い
- 短所: アプリケーションレイヤーでの人为的エラーが発生しやすい。クエリで1つの
WHERE tenant_id = ?句を忘れるだけで、大規模なデータ漏洩につながる可能性がある。PostgreSQLの行レベルセキュリティ(RLS)でこれを緩和できますが、すべてのクエリに複雑さが増す
-
Shared Database, Schema-per-Tenant(共有データベース、Schema-per-Tenant): すべてのテナントが同じデータベースインスタンスを共有하지만、各テナントが各自の専用スキーマを持つ
- 長所: 強力な論理的データ隔離。別のテナントのデータを誤ってクエリすることが不可能。また、テナント固有のカスタマイズが可能
- 短所: 共有スキーマより運用上の複雑さが高くなる可能性があり、非常に多くのスキーマでパフォーマンス上の影響がある場合がある
「WellnessCorp」には、Schema-per-Tenantモデルが最適なバランスを実現しました。これは、必要な強力なデータ隔離を提供しながら、データベースperテナントアプローチの圧倒的な運用コストを避けることができました。
Schema-per-Tenantアーキテクチャ
以下の図は、Schema-per-Tenantモデルがデータを隔離する方法を示しています:
各テナントのスキーマは論理的に隔離されており—たとえフィルタを忘れたとしても、クエリが別のテナントのデータに誤ってアクセスすることはできません。
前提条件
実装に入る前に、ここでは的内容について理解しておく必要があります:
- PostgreSQL: バージョン12以上
- PostgreSQLクライアント:
psqlまたはDBeaverなどのGUIツール
標準的なPostgreSQL機能を使用するため、このコア設定には特別な拡張機能は不要です。
中央テナント管理システムのセットアップ
最初のステップは、テナントを管理する中央の場所を確保することです。publicスキーマがその用途に最適です。
何をするか
publicスキーマにtenantsテーブルを作成します。このテーブルには、各法人クライアントの情報保存され、それぞれに専用のスキーマ名としても使用される一意の識別子が保存されます。
実装
-- src/sql/01_setup_public_schema.sql
-- publicスキーマにはテナント管理テーブルを配置します。
CREATE TABLE public.tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_name VARCHAR(255) NOT NULL,
schema_name VARCHAR(63) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 高速なルックアップのためにschema_nameに一意インデックスを作成します。
CREATE UNIQUE INDEX idx_tenants_schema_name ON public.tenants(schema_name);
仕組み
このtenantsテーブルは、すべてのクライアントのマスター記録として機能します。schema_nameは非常に重要で、テナントを各自の専用データスキーマにプログラム的にリンクします。 主キーにはUUIDを使用することは Distributed システムにとって良いプラクティスであり、テナントに関する逐次情報を隠すことができます。
PostgreSQLトリガーによるスキーマ作成の自動化
新しいクライアントごとに新しいスキーマとすべてのテーブルを手動で作成するのは面倒でエラーが発生しやすいです。このプロセスを自動化する必要があります。
何をするか
新しいテナントがpublic.tenantsテーブルに追加されるたびにトリガーするPostgreSQL関数を作成します。この関数は、テナントの新しいスキーマを動的に作成し、その中にコアアプリケーションテーブルを複製します。
実装
-- src/sql/02_automate_schema_creation.sql
-- 新しいテナントのスキーマと必要なテーブルを作成する関数。
CREATE OR REPLACE FUNCTION create_tenant_schema()
RETURNS TRIGGER AS $$
BEGIN
-- 新しいテナントレコードで指定された名前で新しいスキーマを作成します。
EXECUTE format('CREATE SCHEMA %I', NEW.schema_name);
-- コアアプリケーションテーブルを新しいスキーマに複製します。
EXECUTE format('CREATE TABLE %I.employees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
full_name VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', NEW.schema_name);
EXECUTE format('CREATE TABLE %I.wellness_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
employee_id UUID REFERENCES %I.employees(id) ON DELETE CASCADE,
activity_type VARCHAR(50) NOT NULL,
duration_minutes INTEGER,
log_date DATE NOT NULL,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', NEW.schema_name, NEW.schema_name);
-- アプリケーションの成長に合わせてここにテーブルを追加できます。
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 新しいテナントが挿入された後にトリガーするトリガーを作成します。
CREATE TRIGGER on_new_tenant_created
AFTER INSERT ON public.tenants
FOR EACH ROW
EXECUTE FUNCTION create_tenant_schema();
仕組み
このトリガーと関数の組み合わせは、自動化の中核です。アプリケーションがpublic.tenantsに新しいクライアントを挿入すると、このトリガーが発動し、新しいテナントのために完全にプロビジョニングされた隔離環境を作成します。このアプローチにより、すべてのテナントスキーマ間の一貫性が確保されます。
動的search_pathによる安全なテナント接続
別々のスキーマを持つと、アプリケーションが正しいテナントのデータに接続してやり取りするための安全な方法が必要です。
何をするか
アプリケーション専用の、非スーパーユーザーロールを作成します。特定の会社のユーザーがログインすると、アプリケーションはこのロールでデータベースに接続し、セッション期間中にsearch_pathをそのテナントのスキーマに設定します。これは重要なセキュリティ対策です。
実装
-- src/sql/03_security_setup.sql
-- ログイン権限を持つアプリケーション用のロールを作成します。
-- 実際のシナリオでは、より安全なパスワードを使用してください!
CREATE ROLE wellness_app LOGIN PASSWORD 'supersecretpassword';
-- アプリがtenantsテーブルをクエリできるようにpublicスキーマへのusageを許可します。
GRANT USAGE ON SCHEMA public TO wellness_app;
GRANT SELECT ON public.tenants TO wellness_app;
-- テナントスキーマへの権限は動的に付与します。
-- これを行う関数を追加しましょう。
CREATE OR REPLACE FUNCTION grant_tenant_permissions()
RETURNS TRIGGER AS $$
BEGIN
-- 新しいスキーマへのusageをアプリケーションロールに付与します。
EXECUTE format('GRANT USAGE ON SCHEMA %I TO wellness_app', NEW.schema_name);
-- 新しいスキーマ内のすべてのテーブルに対するすべての権限を付与します。
EXECUTE format('GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA %I TO wellness_app', NEW.schema_name);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- トリガーを更新して権限も付与するようにします。
-- 古いトリガーをドロップして新しいものを作成します。
DROP TRIGGER on_new_tenant_created ON public.tenants;
CREATE TRIGGER on_new_tenant
AFTER INSERT ON public.tenants
FOR EACH ROW
EXECUTE FUNCTION create_tenant_schema();
CREATE TRIGGER on_new_tenant_permissions
AFTER INSERT ON public.tenants
FOR EACH ROW
EXECUTE FUNCTION grant_tenant_permissions();
仕組み
ユーザーがログインすると、アプリケーションのバックエンドは認証し、テナントを特定してから、データベース接続を確立直後に次のSQLコマンドを実行します:
SET search_path TO 'tenant_schema_name', public;
この時点以降、SELECT * FROM employees;のようなクエリは自動的にtenant_schema_name.employeesに解決されます。これはアプリケーションのデータアクセスレイヤーに対して透過的であり、データベースレベルで強力なデータ隔離の保証を提供します。
すべてをまとめる:完全なワークフロー
新しい法人クライアント「Innovate Inc.」をオンボーディングするプロセスを説明します。
-
新しいクライアントがサインアップします。 アプリケーションのバックエンドがtenantsテーブルに情報を挿入します。
sql INSERT INTO public.tenants (company_name, schema_name) VALUES ('Innovate Inc.', 'innovate_inc'); -
トリガーが発動します。
create_tenant_schemaがinnovate_incスキーマとemployeesおよびwellness_logsテーブルを作成します。grant_tenant_permissionsが新しいスキーマとそのテーブルに対するwellness_appロールに必要な権限を付与します。
-
Innovate Inc.の従業員がログインします。
- アプリケーションは
wellness_appロールを使用してPostgreSQLに接続します。 - 次にセッションの
search_pathを設定します:sql SET search_path TO 'innovate_inc', public;
- アプリケーションは
-
アプリケーションがデータをクエリします。
SELECT * FROM wellness_logs;のようなクエリが実行されます。- PostgreSQLは
search_pathに従って、このクエリをinnovate_inc.wellness_logsに送信し、Innovate Inc.のデータのみが返されることを確保します。
パフォーマンスに関する考慮事項
- 接続プール: 多くのテナントがある場合、データベース接続の管理が重要です。PgBouncerのような接続プールャーは、接続オーバーヘッドがデータベースを圧倒するのを防ぐために不可欠です。
- インデックス作成: 各テナントのテーブルは小さいので、一般的にインデックススキャンが速くなります。しかし、各テナントのスキーマ内の外部キーと頻繁にアクセスされる列に適切なインデックスを持つことは 여전히重要です。
- 監視: データベースのパフォーマンスとリソース使用量を監視します。特定のテナントが他のテナントよりも著しく大きくなった場合((「うるさい隣人」問題)、このモデルでは将来的にそれを専用のデータベースインスタンスに移動することができるかもしれません。
代替アプローチ
Schema-per-Tenantモデルは強力な選択ですが、堅牢なセキュリティのための主要な代替案もご紹介します:
- 行レベルセキュリティ(RLS)を使用した共有スキーマ: RLSは、テーブルに対してポリシーを定義し、どのユーザーがどの行にアクセスできるかを制御できる強力なPostgreSQL機能です。这也是データ隔離を提供できますが、慎重なポリシー管理が必要であり、すべてのクエリにわずかなパフォーマンスオーバーヘッドを追加します。非常に多くのテナントがあり、多くのスキーマを管理する運用オーバーヘッドが懸念事項である場合、これは優れたオプションです。
結論
「WellnessCorp」には、Schema-per-Tenantモデルが強力なデータ隔離、運用スケーラビリティ、パフォーマンスの理想的なバランスを提供しました。スキーマ作成の自動化と細心の権限管理により、PostgreSQL上に堅牢で安全、スケーラブルなマルチテナントデータベースアーキテクチャを構築しました。このアプローチは、法人クライアントに機微な従業員ウェルネスデータを任せるために必要な信頼を与えます。
ビジネスインパクト: Schema-per-Tenantモデルは設計上テナント間のゼロデータ漏洩を可能にし、アプリケーションコードが別のテナントのデータに誤ってアクセスすることを防ぎます。このアプローチを採用している組織は、自動プロビジョニングによりテナントオンボーディング時間の60〜80%削減を報告しており、データベースperテナントモデルと比較してデータベース管理コストを50%削減しています。論理的隔離により、規制遵守(SOC 2、HIPAA) の達成と維持が容易になります。
この事例研究は、B2B SaaS開発における一般的な課題に対する実用的で効果的な解決策を示しています。ここで説明された原則は、幅広いマルチテナントアプリケーションに適応できます。
リソース
- PostgreSQL スキーマのドキュメント: https://www.postgresql.org/docs/current/ddl-schemas.html
- Crunchy Data Blog: マルチテナンシーのためのPostgreSQLデータベース設計: https://www.crunchydata.com/blog/designing-your-postgres-database-for-multi-tenancy
- AWS Database Blog: PostgreSQL行レベルセキュリティによるマルチテナントデータ隔離: https://aws.amazon.com/blogs/database/multi-tenant-data-isolation-with-postgresql-row-level-security/
- 関連記事:
- DDDでモノリスをマイクロサービスにリファクタリングする - サービスアーキテクチャパターン
- スケーラブルな分析のためのCQRSパターン - データベースクエリ最適化
クイックリファレンス
| 側面 | 主要情報 |
|---|---|
| トピック | 開発 |
| 関連性 | 患者教育と認知向上 |
| 行動 | 医療提供者と相談する |
| 頻度 | ケアチームが推奨する頻度 |
| 証拠 | 現在の臨床ガイドラインに基づく |
よくある質問
Q: これが自分に該当する方法を知るには? A: 個人的および家族の病歴について医療提供者と相談し、リスク因子を個別に評価し、適切な次のステップを決定してください。
Q: 医師に何聞くべきですか? A: 良い質問には:「結果はどういう意味ですか?」「私のオプションは何ですか?」「どんなライフスチャンジが役立ちますか?」「いつフォローアップすべきですか?」が含まれます。
Q: 幫助できるライフスチャンジはありますか? A: ほとんどの健康状態は、基本的な対策から benefits します:バランスの取れた栄養、規則的な身体活動(週150分以上)、質の良い睡眠(7〜9時間)、ストレス管理、および禁煙。
免責事項
この記事で説明されているアルゴリズムとテクニックは、純粋に技術的な教育目的のためのものです。臨床的な検証を受けておらず、医学的診断や治療決定に使用すべきではありません。医学的アドバイスについては常に資格のある医療専門家にご相談ください。