Multi-Tenant SaaS Architecture: How to Get Data Isolation Right From the Start
Multi-tenancy is one of those architectural decisions that's easy to defer and painful to retrofit. Get it wrong early, and you'll spend months untangling data isolation bugs that only surface in production — usually in a customer's data, usually at the worst possible time. Here's the framework we use when advising startups on this exact problem.
What Is Multi-Tenancy?
A multi-tenant system serves multiple customers — tenants — from a single deployed instance. Each tenant's data must be isolated so they can never see each other's records, even if they share the same database server, application server, or infrastructure.
The Three Isolation Strategies
Schema per Tenant
Each tenant gets a dedicated PostgreSQL schema within the same database. You set the search_path at the start of each request to route queries to the right schema. Migration tooling becomes complex, but data isolation is strong and restoring a single tenant is straightforward.
Row-Level Security (RLS)
All tenants share the same tables, but PostgreSQL enforces access policies at the row level via RLS policies. This is our default recommendation for early-stage SaaS. It's operationally simple, migrations run once, and PostgreSQL's query planner optimizes RLS-filtered scans efficiently.
Separate Databases
Each tenant gets their own database instance. Strongest isolation and the cleanest compliance story, but the operational overhead is significant. Reserved for regulated industries — healthcare, finance — or enterprise contracts that require it contractually.
Row-Level Security in Practice
Here is a minimal but production-ready RLS setup in PostgreSQL. The key insight is that you set a session variable once per request, and every query automatically filters by it:
-- 1. Add tenant column to every table
ALTER TABLE projects ADD COLUMN tenant_id UUID NOT NULL;
-- 2. Enable RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- 3. Create policy — reads and writes are restricted to the current tenant
CREATE POLICY tenant_isolation ON projects
USING (tenant_id = current_setting('app.tenant_id')::UUID);
-- 4. Set the tenant on each connection before running queries
SET app.tenant_id = '550e8400-e29b-41d4-a716-446655440000';In your Node.js/Prisma layer, wrap every request in a transaction that sets the session variable first:
Stay in the loop
Get weekly insights on startup tech, cloud, and engineering. No spam, unsubscribe anytime.
async function withTenant<T>(tenantId: string, fn: () => Promise<T>): Promise<T> {
return prisma.$transaction(async (tx) => {
await tx.$executeRaw`SELECT set_config('app.tenant_id', ${tenantId}, true)`;
return fn();
});
}Which Strategy to Choose
- Early-stage / fewer than 500 tenants — Row-level security. Low operational overhead, simple migrations, well-proven at production scale.
- Mid-stage / compliance requirements — Schema per tenant. Stronger isolation story for enterprise sales, manageable if you invest in migration tooling.
- Enterprise / regulated data — Separate databases. Operationally expensive, but sometimes the only answer procurement will accept.
Start with RLS. You can migrate to schema-per-tenant later if a customer demands it. You can't easily go the other direction.
Common Pitfalls
Forgetting to index tenant_id. Every table scan will be slow without a composite index on (tenant_id, id) or (tenant_id, created_at). Add these when you add the column, not after you're in production wondering why queries are slow.
Leaking tenant context in background jobs. Worker queues run outside the request cycle — make tenant_id an explicit field on every job payload. Don't assume session context carries through.
Cross-tenant analytics. Reporting queries need to bypass RLS. Use a dedicated read replica with a superuser role and aggregate before returning results to the application layer.
Not testing isolation. Write integration tests that explicitly assert tenant B cannot read tenant A's data. This belongs in your CI pipeline before you onboard your second customer, not after.
The Decision That Shapes Everything Else
The isolation strategy you choose determines your data model, your query patterns, your migration approach, and how you handle compliance conversations with enterprise prospects. Pick the simplest strategy that satisfies your current requirements. Instrument it properly. Test the boundaries.
The startups that get this wrong don't usually pick the wrong strategy — they pick no strategy, and the defaults fill in the gaps.
---
If you're scoping a multi-tenant SaaS product and want to get the data model right before you're committed to it, Specrova's engineering team can help.
Enjoyed this article? Share it!