When designing a multi-tenant B2B SaaS platform, the single most critical architectural decision is how customer data is isolated within your database layer.
A data breach where one customer views or modifies another tenant's confidential files is fatal to a software company. At the same time, an overly complex data isolation model can dramatically increase operational overhead, complicate database migrations, and skyrocket infrastructure costs.
In this guide, we evaluate the three primary PostgreSQL multi-tenancy patterns, analyze the trade-offs, and provide real implementation code using PostgreSQL Row-Level Security (RLS).
Multi-Tenancy Architecture Comparison
| Model | Isolation Level | Migration Complexity | Infrastructure Cost | Max Tenants (Single Node) | Best For |
|---|---|---|---|---|---|
| Shared DB + Row-Level Security (RLS) | High (Enforced at DB Engine) | Very Low (Single schema migration) | Lowest (Shared connection pool & cache) | 100,000+ | Fast-moving B2B SaaS, Startups, MVPs |
| Schema-per-Tenant | Very High (Separate DB schemas) | High (Run migrations across N schemas) | Moderate (Higher catalog memory overhead) | 500 – 2,000 | Mid-market SaaS with custom field requirements |
| Database-per-Tenant | Complete (Separate physical DB instances) | Very High (Manage N distinct databases) | Highest (Dedicated CPU & RAM per client) | 50 – 200 | Enterprise / Healthcare / Gov (HIPAA / SOC2 Type II) |
Model 1: Shared Database with PostgreSQL Row-Level Security (RLS)
For 95% of modern SaaS platforms built with Next.js, Node.js, or Go, Shared Schema with Row-Level Security (RLS) is the recommended gold standard.
How It Works:
Every table contains a tenantid column. When a user authenticates, your application sets a PostgreSQL session configuration variable (app.currenttenant_id). PostgreSQL then enforces security rules natively for all SELECT, INSERT, UPDATE, and DELETE queries.
-- Step 1: Create the multi-tenant table
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
amount_cents INTEGER NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Step 2: Enable Row-Level Security on the table
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
-- Step 3: Create the isolation policy
CREATE POLICY tenant_isolation_policy ON invoices
AS RESTRICTIVE
FOR ALL
TO authenticated_app_user
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);Application Connection Flow:
Before executing any business queries inside a database transaction, your backend runs a parameterized SET LOCAL:
// Next.js / Node.js Transaction Wrapper Example
import { pool } from "@/lib/db";
export async function executeTenantQuery<T>(
tenantId: string,
queryFn: (client: any) => Promise<T>
): Promise<T> {
const client = await pool.connect();
try {
await client.query("BEGIN;");
// Set tenant context for this specific transaction
await client.query("SET LOCAL app.current_tenant_id = $1;", [tenantId]);
const result = await queryFn(client);
await client.query("COMMIT;");
return result;
} catch (error) {
await client.query("ROLLBACK;");
throw error;
} finally {
client.release();
}
}Why We Recommend RLS:
- Developer Ergonomics: Even if an engineer writes
SELECT * FROM invoices;without aWHEREclause, PostgreSQL strictly filters rows to the activetenant_id. Cross-tenant leaks are mathematically prevented. - Effortless Migrations: Running schema migrations (adding a column, creating an index) executes once across the whole database in seconds.
- Optimized Resource Utilization: All tenants share a single buffer pool, connection pool, and query cache.
Model 2: Schema-per-Tenant
In this pattern, all tenants share a single PostgreSQL database instance, but each customer gets a dedicated schema namespace (tenantacme.invoices, tenantglobex.invoices).
Pros:
- Clean data isolation.
- Easy to back up or delete a single tenant's data (
DROP SCHEMA tenant_acme CASCADE;). - Ability to add bespoke custom tables for high-paying clients.
Cons:
- Migration Nightmare: If you have 1,000 tenants and apply a new migration, your migration runner must execute 1,000 DDL operations sequentially. If a migration fails on tenant 450, you enter a partial failure state.
- PostgreSQL System Catalog Bloat: PostgreSQL maintains metadata in memory for all tables and indices. Having thousands of schemas can degrade query planning speed.
Model 3: Database-per-Tenant
Each tenant is provisioned an entirely separate PostgreSQL database (or container instance).
When to Use It:
- Strict regulatory mandates where customer agreements state data cannot share physical storage with competitors.
- Enterprise clients paying $50,000+ annually who demand custom backup schedules and dedicated point-in-time recovery (PITR).
Essential Performance Optimizations for Multi-Tenant PostgreSQL
1. Compound Indexing with tenant_id Prefix
Every index on a multi-tenant table should almost always include tenant_id as the leading column:
-- Optimal index for tenant-scoped date queries
CREATE INDEX idx_invoices_tenant_date ON invoices (tenant_id, created_at DESC);2. PgBouncer Connection Pooling
Serverless application environments (like Next.js on Vercel or AWS Lambda) spin up hundreds of transient database connections. PostgreSQL allocates ~10MB of RAM per connection. Always place PgBouncer in transaction pooling mode between your application servers and PostgreSQL.
Read our complete guide on PostgreSQL Performance Tuning for SaaS to master connection limits and query plans.
Summary & Next Steps
For modern SaaS teams, PostgreSQL with Row-Level Security offers the best balance of ironclad security, architectural elegance, and rapid development speed.
If you are designing a new SaaS data architecture or looking for a Code Audit to verify your multi-tenant security, the engineering team at Dazzcode is ready to help.
- Learn more about our SaaS Development Services
- Explore SaaS Scaling & Database Engineering
- Book a Technical Architecture Call