モダンな SaaS アプリケーションは、一般的に同じインフラを使って複数の顧客にサービスを提供する必要があります。このモデルは マルチテナントアーキテクチャ として知られています — 異なる組織(テナント)が同じシステムを共有しつつ、それぞれのデータは完全に分離されています。本記事では、シンプルさ、スケーラビリティ、セキュリティに焦点を当てて、このパターンを PostgreSQL でどう構築するかを紹介します。
マルチテナントアーキテクチャとは何か
マルチテナントアプリケーションでは、複数の顧客が同じアプリケーションとデータベースを利用しますが、各顧客はそれぞれ分離された独自のデータを持ちます。システムは、ある企業のユーザーが他の企業の情報にアクセスできないことを保証する必要があります。このモデルは、管理プラットフォーム、CRM、マーケットプレイス、企業向けシステムといった SaaS 製品で広く利用されています。
マルチテナンシーの主な戦略
データベースでマルチテナンシーを実装するには、一般的に 3 つのアプローチがあります:
- Database per tenant: 各顧客が分離されたデータベースを持ちます。
- Schema per tenant: 各顧客が同じデータベース内に独自のスキーマを持ちます。
- Shared database(tenant_id 方式): すべてのデータが同じデータベースとテーブルに格納されますが、各レコードがテナント識別子を持ちます。
ほとんどのモダンな SaaS 製品では、最も多く使われるアプローチは 3 番目の Shared database(tenant_id 方式) です。これはスケーラビリティ、運用のシンプルさ、インフラコストの間で最良のバランスを提供します。
データベースのモデリング
基本的な原則はシンプルです:ビジネスデータに関連するほぼすべてのテーブルが tenant_id カラムを持つべきです。このフィールドが、そのデータがどの組織に属するかを識別します。
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
total NUMERIC NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);このパターンにより、すべてのデータが特定のテナントに紐づくことが保証されます。これ以降、すべてのクエリは常に tenant_id でフィルタリングする必要があります。
SELECT * FROM orders
WHERE tenant_id = 'tenant-uuid';バックエンドでの分離の適用
アプリケーションのバックエンドでは、tenant_id は通常、認証済みユーザーから特定されます。つまり、システムが実行するすべてのクエリが自動的にこのフィルタを適用する必要があります。
const orders = await db.orders.findMany({
where: {
tenantId: session.tenantId
}
});このパターンにより、ユーザーが他の組織のデータにアクセスするのを防ぎ、システムの一貫性を保ちます。
PostgreSQL の Row Level Security
追加のセキュリティレイヤーは、PostgreSQL の Row Level Security (RLS) を使って実装できます。RLS を使えば、データベース自体が正しいテナントのレコードだけにアクセスできることを保証します。
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy
ON orders
USING (tenant_id = current_setting('app.current_tenant')::uuid);このアプローチにより、バックエンドで適切なフィルタを付けずにクエリが実行されたとしても、データベースが依然としてデータへのアクセスを保護します。
マルチテナントシステムにおけるスケーラビリティ
tenant_id をベースとしたアーキテクチャは、システムのスケールも容易にします。一般的な戦略には次のようなものがあります:
- クエリのパフォーマンス向上のための tenant_id によるインデックス作成。
- tenant_id をベースとしたテーブルのパーティショニング。
- プラットフォームの成長に応じて、大規模なテナントを専用データベースに分離。
- データベースの負荷を軽減するためのテナントごとのキャッシュの利用。
SaaS 製品における実際の応用
このパターンは、企業管理プラットフォーム、予約システム、マーケットプレイス、CRM など、さまざまな種類の SaaS 製品で利用されています。私が最近開発したプロジェクトでは、マルチテナントアーキテクチャによって、データの分離を維持しつつ運用コストを削減しながら、複数の顧客にサービスを提供できました。
結論
マルチテナントアーキテクチャは、多くのモダンな SaaS 製品の基盤です。PostgreSQL と tenant_id をベースとした一貫した設計を用いれば、スケーラブルで安全、かつ比較的メンテナンスしやすいシステムを構築できます。バックエンドのベストプラクティスとデータベースのセキュリティと組み合わせることで、このアプローチはマルチクライアントプラットフォームのための堅牢なソリューションになります。
