← Back
AI & product development

Multi-Tenant SaaS Architecture: Which Model Should You Choose?

Published on October 1, 2026 • Written by RM JDG team • Updated on October 1, 2026

For most early-stage SaaS products, the right starting point is a shared database with a shared schema: every customer's rows live in the same tables, each row carries a tenant_id, and the database itself enforces isolation with PostgreSQL row-level security. Move individual customers to schema-per-tenant or database-per-tenant only when something concrete forces it: a contract, a regulation, a customer large enough to disrupt everyone else, or a per-customer restore requirement.

That is the short answer. The rest of this guide explains the three models, how to build the default one safely, and the signals that tell you when to change course. It is written for founders and CTOs making this decision early, when it is cheap to get right and expensive to fix later. It also feeds directly into budget; see how much it costs to build a SaaS product.

What multi-tenancy actually means

In a multi-tenant SaaS, one running application serves many customers (tenants) from shared infrastructure, while each tenant's data and configuration stay invisible to the others. The alternative, single-tenant, gives each customer their own deployment. Single-tenant is simpler to reason about but multiplies your hosting and operations work with every new customer, which is why most SaaS products start multi-tenant.

The real design question is how you separate tenants' data. There are three common models.

The three tenancy models

1. Shared database, shared schema (the "pool" model). All tenants use the same tables. Every tenant-owned row has a tenant_id column.

  • Strengths: cheapest to run, simplest to migrate (one schema change applies to everyone), scales to thousands of small tenants, easiest to build analytics across tenants.
  • Weaknesses: isolation depends on your code and policies getting every query right; one heavy tenant can slow the others; restoring a single tenant's data from a backup is awkward.

2. Shared database, schema per tenant (the "bridge" model). One database, but each tenant gets its own PostgreSQL schema containing its own copy of the tables.

  • Strengths: stronger logical separation, cleaner per-tenant export and deletion.
  • Weaknesses: every migration must run once per tenant, so 500 tenants means 500 migrations; catalog size and tooling get heavier as tenant count grows. Some teams recommend avoiding this model entirely, because it carries much of the operational cost of database-per-tenant without the isolation guarantees regulators or enterprise security teams often ask for.

3. Database per tenant (the "silo" model). Each tenant gets a separate database, sometimes separate infrastructure.

  • Strengths: strongest isolation, per-tenant backup and restore, easy data residency, customer-specific tuning.
  • Weaknesses: highest cost per tenant, provisioning and migration complexity, harder cross-tenant reporting, and connection management gets difficult at scale.

Which one should you choose?

Start with shared schema unless one of these is true on day one:

  • You sell to regulated industries where contracts or auditors require physical data separation.
  • Your first customers are large enterprises that will demand dedicated databases as a condition of the deal.
  • Data residency rules require different tenants' data in different regions.

If none of those applies, the pool model keeps your costs and complexity low while you find product-market fit. A common mistake is the opposite one: choosing database-per-tenant "to be safe" before there are ten customers, then spending months on provisioning and migration tooling instead of product.

The best long-term setup is usually tiered: most tenants share a database, and a few large or regulated tenants are moved to their own. To make that possible later, add a small tenant registry now (a table mapping each tenant to where its data lives) and route every request through it, even if every tenant currently points to the same database.

Building the shared-schema model safely

Shared schema is only as safe as its enforcement. Relying on developers to remember WHERE tenant_id = ... in every query will fail eventually. Add the database as a second line of defense.

1. Put tenant_id on every tenant-owned table, and lead indexes with it

Almost every query will filter by tenant, so make tenant_id the first column of your composite indexes. Make uniqueness tenant-scoped too: a unique constraint on email alone means one customer's user can block another customer's sign-up. Use UNIQUE (tenant_id, email) instead.

2. Enforce isolation with PostgreSQL row-level security

When row-level security (RLS) is enabled on a table, normal reads and writes must be permitted by a policy, and with no policy the default is to deny everything. A minimal setup looks like this:

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON projects
  USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid)
  WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid);

Two details matter here, and both come straight from how PostgreSQL works:

  • Table owners normally bypass RLS, and superusers and roles with the BYPASSRLS attribute always do. FORCE ROW LEVEL SECURITY makes the table owner subject to policies, but the safest approach is for your application to connect as a dedicated, unprivileged role that is neither the owner nor BYPASSRLS. Keep migrations on a separate, privileged role.
  • The NULLIF(...) guard avoids a cast error when the setting is empty, and means a request with no tenant context sees no rows rather than crashing or, worse, seeing everything.

3. Set the tenant context per transaction, not per connection

Applications use connection pools, and a connection is reused by many requests. If you set the tenant once on a connection, the next request on that connection can inherit it. Set it inside each transaction, scoped to that transaction only:

import { Pool, PoolClient } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

export async function withTenant<T>(
  tenantId: string,
  fn: (client: PoolClient) => Promise<T>
): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    // third argument true = local to this transaction
    await client.query("SELECT set_config('app.tenant_id', $1, true)", [tenantId]);
    const result = await fn(client);
    await client.query("COMMIT");
    return result;
  } catch (err) {
    await client.query("ROLLBACK");
    throw err;
  } finally {
    client.release();
  }
}

The tenant ID must come from the authenticated session or token on the server, never from a request parameter the client can change.

4. Test isolation explicitly

Write automated tests that create two tenants, then assert that tenant A cannot read, update or delete tenant B's rows through every API route. Run them in CI. Isolation bugs rarely show up in manual testing.

Where tenant leaks actually happen

The database is usually the best-protected layer. Leaks tend to happen elsewhere:

  • Caches. A cache key like user:42:settings without the tenant in it is a leak waiting for an ID collision.
  • Background jobs. Jobs that run outside a request need the tenant context passed in explicitly and applied the same way.
  • File storage. Use a tenant-specific prefix in object storage paths and generate signed URLs per request.
  • Search indexes and vector stores. If you build AI search or retrieval, every document must carry tenant metadata and every query must filter on it. See our guides on building a production RAG pipeline and how to choose a vector database, and how to add AI to an existing SaaS product for permission-aware retrieval.
  • Logs and analytics. Make sure sensitive tenant data does not end up in logs that every engineer or vendor can read.
  • Admin and support tools. These deliberately cross tenant boundaries, so they need their own access controls and audit trail.

Handling the noisy-neighbor problem

In a shared database, one tenant running a heavy report can slow everyone else. The standard mitigations are cheap to add early:

  • Per-tenant rate limits on API requests
  • Quotas on storage, records or expensive operations, tied to plan tier
  • Statement timeouts and connection limits so a single request cannot hog the pool
  • Moving heavy reporting to a read replica or a separate worker queue
  • Fair scheduling in background job queues, so one tenant's bulk import does not starve everyone's emails

If a single tenant keeps causing trouble despite these, that is the signal to move them to their own database.

Signals that it is time to isolate a tenant

Treat these as triggers, not theory:

  • A signed contract or security questionnaire requires dedicated data storage
  • A regulation or customer requires data to stay in a specific region
  • One customer's load is a large share of your database and quotas are not enough
  • A customer needs point-in-time restore of only their data
  • A customer needs custom schema changes or extensions

When one of these appears, you migrate that tenant only, using the registry you set up earlier. Everyone else stays on the shared database.

Common mistakes

  1. Choosing the heaviest model first. Database-per-tenant before product-market fit trades product progress for infrastructure work.
  2. Relying on application filters alone. One forgotten WHERE clause is a data breach. Use RLS as a backstop.
  3. Connecting as a role that bypasses RLS. Policies that exist but never apply give a false sense of safety.
  4. Tenant-blind unique constraints and caches. Both fail in subtle, intermittent ways.
  5. Retrofitting tenancy later. Adding tenant_id to a mature schema, backfilling it, and auditing every query is slow, risky work. It is far cheaper to build it in from the first migration.

Bottom line

Start with a shared schema, enforce isolation in the database with row-level security, set tenant context per transaction, and build a tenant registry so you can move individual customers to dedicated databases when a real requirement appears. That gives you low cost now and a clean path to enterprise isolation later.

If you are designing a SaaS product and want an architecture review before you commit to a tenancy model, RMJDG's team can look at your requirements and help you choose and implement the right approach.

Services

Not sure where to start? Tell me what you want the product to do.

Related work

    Multi-Tenant SaaS Architecture: Which Model Should You Choose? | RM JDG