Skip to content
← All posts

SaaS PlatformBy 08 Apr 20264 min read

Multi-Tenant SaaS Architecture: Choosing the Right Isolation Model

Database-per-tenant, schema-per-tenant or shared tables? A practical comparison of SaaS isolation models, their trade-offs, and the safeguards that prevent cross-tenant data leaks.

Multi-Tenant SaaS Architecture: Choosing the Right Isolation Model

Multi-tenancy is the defining architectural decision of any SaaS product. It determines how fast you can onboard customers, how much infrastructure costs per tenant, how you run migrations — and, most importantly, how confident you can be that one customer never sees another customer's data.

Having worked on the backend of Nuxused, a multi-tenant school management platform, I've seen how this single decision shapes almost every feature built on top of it. This article compares the three standard isolation models and the safeguards that make shared infrastructure safe.

Shared-table multi-tenancyEvery request resolves its tenant once, and every query is scoped by it.

The Three Isolation Models

Database per tenant

Each customer gets a dedicated database. Isolation is as strong as it gets: a bug in one query cannot return another tenant's rows because the data physically lives elsewhere.

Strengths: strongest isolation, easy per-tenant backup and restore, straightforward data-residency compliance. Costs: provisioning on every signup, migrations multiplied by the number of tenants, idle capacity you still pay for, and cross-tenant reporting becomes a data-engineering project.

This model suits a small number of large, high-value customers — typical in enterprise or regulated industries.

Schema per tenant

One database, one schema per customer. It reduces infrastructure overhead while keeping logical separation.

Strengths: good isolation, fewer servers. Costs: migrations still run once per schema, connection pools and catalogs grow with the tenant count, and operational tooling becomes custom.

Shared tables with a tenant key

All tenants share the same tables; every row carries a tenant_id. This is the dominant model for product-led SaaS with self-service signup and free trials.

Strengths: near-zero cost per new tenant, one migration path, simple cross-tenant analytics and features such as franchises or organisations spanning multiple accounts. Costs: isolation depends entirely on correct query scoping — which is why the safeguards below matter.

Resolving the Tenant Correctly

With shared tables, the golden rule is: resolve the tenant once, from a trusted source, and pass it everywhere.

export async function requireTenant(req: Request): Promise<TenantScope> {
  const session = await verifySession(req);          // signed token or server session
  if (!session.tenantId) throw new ForbiddenError();
  return { tenantId: session.tenantId, userId: session.userId, role: session.role };
}

// Repositories only accept a scope — there is no unscoped variant
export const students = {
  list: (scope: TenantScope, classId: string) =>
    db.student.findMany({ where: { tenantId: scope.tenantId, classId } }),
};

Never accept a tenant identifier from the request body or query string. If the client can send it, the client can change it.

Where Cross-Tenant Leaks Actually Happen

List endpoints are rarely the problem — they're reviewed carefully. Leaks hide elsewhere:

  • Lookups by ID (GET /invoices/:id) that check existence but not ownership.
  • Nested includes and joins, where the parent is scoped but related records are not.
  • Background jobs, exports and scheduled reports that run without a request context.
  • Search indexes and caches keyed by record ID instead of (tenant, id).
  • Unique constraints defined globally when they should be per tenant, such as (tenant_id, admission_number).

Defence in Depth

Application-level scoping is the first line of defence. Add a second at the database layer so that a missing filter fails safely:

  • Row-level security (RLS) in PostgreSQL enforces tenant filters inside the database itself.
  • Composite indexes starting with tenant_id keep scoped queries fast as data grows.
  • Automated isolation tests — for every resource, create two tenants and assert that tenant A receives a 404 for tenant B's records.

Planning for Growth

Many successful SaaS products use a hybrid: shared tables for the majority of customers, with the option to move a large or regulated customer to a dedicated database. Designing your data access layer around a tenant scope from day one makes that migration possible later without rewriting business logic.

Key Takeaways

  • Choose the isolation model that matches how customers buy: self-service favours shared tables; enterprise contracts may justify dedicated databases.
  • Resolve the tenant from the authenticated session — never from client input.
  • Audit ID lookups, joins, background jobs, caches and unique constraints for leaks.
  • Add database-level protection such as row-level security, and test isolation for every resource.

Sources

More posts