SaaS Architecture

Multi-Tenant SaaS Architecture: How to Choose the Right Tenancy Model

Multi-Tenant SaaS Architecture: How to Choose the Right Tenancy Model illustration

Every B2B SaaS product eventually has to answer the same question from a prospect’s security team: how do you keep our data separate from everyone else’s? The answer depends on your tenancy model, and that choice shapes cost, operations, migrations and which customers you can sell to for years.

There is no single right model. This guide compares the three common approaches, explains the supporting patterns that make any of them safe, and shows how to move between them when your customer base changes.

The three tenancy models

Database per tenant

Each customer gets its own database, sometimes on its own server. Isolation is strong and easy to explain: a query simply cannot reach another tenant’s rows because they live somewhere else.

The cost is operational. Every schema migration runs once per tenant, connection pools multiply, and monitoring, backups and restores all scale with tenant count. It works well for a modest number of large customers and poorly for thousands of small ones.

Schema per tenant

All tenants share a database server, but each gets its own schema (namespace) with an identical set of tables. Isolation is better than shared tables and the infrastructure footprint is smaller than separate databases.

In practice, schema-per-tenant inherits many of the migration headaches of database-per-tenant. Catalog bloat becomes a real concern as schemas grow into the thousands, and tooling such as ORMs and migration frameworks often needs custom work to handle dynamic schemas.

Shared tables with row-level security

All tenants share the same tables, and every row carries a tenant_id. The database enforces separation through row-level security (RLS) policies, so a query only returns rows matching the tenant set on the current connection.

This is the most efficient model to run: one schema, one migration, one connection pool. The risk is that a single mistake in policy design or connection handling can expose data across tenants, so the surrounding discipline matters more here than anywhere else.

Comparing the trade-offs

Concern Database per tenant Schema per tenant Shared tables + RLS
Isolation Strongest, physical separation Strong, logical namespaces Enforced by database policies
Infrastructure cost Highest per tenant Moderate Lowest per tenant
Operations Many databases to monitor and back up One server, many schemas One database to operate
Migrations Run per tenant, slow rollouts Run per schema, partial failures possible Run once
Noisy neighbors Contained by design Shared server resources Shared everything, needs quotas
Compliance and residency Easy to place per region or per customer Possible with regional servers Needs regional deployments or partitioning
Per-tenant restore Straightforward Manageable Hard, needs row-level export tooling

A few points deserve more detail:

  • Noisy neighbors. In shared models, one tenant running a heavy report can slow everyone down. Mitigate with per-tenant rate limits at the gateway, statement timeouts, separate queues for heavy jobs and read replicas for analytics.
  • Compliance. Some regulated buyers require that their data sit in a dedicated store. Others only need contractual assurance and evidence that isolation is enforced by the database rather than by application code alone.
  • Data residency. If customers need data kept in the EU or another region, plan for regional deployments (a “cell” per region) regardless of the model you choose inside each one.

Tenant context propagation

Whatever model you pick, isolation fails if the tenant identity gets lost between the request and the query. Treat tenant context as a first-class value:

  1. Resolve once, at the edge. The gateway or auth layer derives the tenant from a verified token claim or API key, never from a request parameter the client controls.
  2. Carry it explicitly. Pass it through middleware into a request-scoped context object, and add it to logs, traces and outgoing messages.
  3. Set it on the connection. With Postgres RLS, set a session variable such as app.tenant_id at the start of each transaction and reference it in policies. Reset it when the connection returns to the pool.
  4. Include it in background work. Every queued job and event should carry tenant_id, and workers should set context before touching data.
  5. Fail closed. If no tenant is set, queries should return nothing rather than everything. Use a non-superuser database role so RLS cannot be bypassed accidentally.

Add automated tests that log in as one tenant and try to read another tenant’s resources. These tests catch regressions that code review misses.

Per-tenant encryption keys

Encryption at rest protects disks, not tenants. For stronger guarantees, use envelope encryption with a data key per tenant, wrapped by a master key in a managed key service. This gives you:

  • The ability to crypto-shred a tenant’s data at offboarding by destroying their key
  • A path to customer-managed keys for enterprise buyers who ask for them
  • A smaller blast radius if a single key is ever exposed

Apply this to sensitive fields and files first. Encrypting every column adds latency and makes querying harder, so be selective.

Caches and search indexes are where isolation quietly breaks. A cache key like user:42:profile works until two tenants have a user 42.

  • Caching: Prefix every key with the tenant ID and build keys through a shared helper, so no developer constructs them by hand. Invalidate per tenant when needed.
  • Search: Either use an index per tenant (clean isolation, more overhead) or a shared index with a mandatory tenant filter applied by a wrapper the application cannot skip.
  • Files and object storage: Use tenant-scoped prefixes and signed URLs, and check tenant ownership before generating a link.

A hybrid model for enterprise tiers

Many mature SaaS platforms end up with a hybrid. Most tenants live in shared tables with RLS, while enterprise customers who pay for it get a dedicated database or a dedicated regional cell.

To make this workable, keep a single codebase and a tenant routing table that maps each tenant to its data location. The application resolves the tenant, looks up where its data lives and connects accordingly. Because the schema is identical everywhere, the same migrations apply to shared and dedicated stores alike.

Our multi-tenant SaaS billing platform blueprint uses shared tables with RLS, and our AI multi-tenant HRMS platform blueprint shows how the same principles extend to sensitive employee data.

Migrating between models

Tenancy decisions are rarely permanent. Common moves include consolidating hundreds of small databases into shared tables, or lifting one large tenant out of a shared database into a dedicated one.

A safe migration usually follows these steps:

  1. Add tenant_id everywhere first, even in a database-per-tenant setup. It makes every later move possible.
  2. Introduce the routing layer so the application no longer assumes where a tenant’s data lives.
  3. Copy data per tenant with change data capture or dual writes, then verify row counts and checksums.
  4. Cut over one tenant at a time, starting with internal or low-risk accounts, with a tested rollback path.
  5. Decommission the old store only after a defined observation period.

Moving tenants one by one turns a risky big-bang migration into a series of small, reversible changes.

How to decide

Start with shared tables and RLS if you expect many small and mid-sized customers and need to move quickly. Choose database-per-tenant if you serve a small number of large, regulated customers from day one. Avoid schema-per-tenant unless you have a specific reason, since it often combines the downsides of both. In every case, build tenant context propagation and a routing layer early so you can change course later.

If you’re designing a new SaaS platform or reworking an existing one, our custom software development and cloud services teams can help you pick a model and build the isolation around it. Contact us to talk through your architecture.

Reference Architecture

Tenant context from request to data

Tenant context from request to dataThe gateway resolves the tenant from the authenticated session and passes it with every call. Services use it to set the Postgres session variable for row-level security, pick the tenant's encryption key and scope cache and search lookups. Connections: Web App to API Gateway; API Client to API Gateway; Web App to Identity Provider (sign in); API Gateway to Core Services (tenant context); API Gateway to Background Workers; Core Services to PostgreSQL (SET tenant_id); Core Services to Key Management; Core Services to Redis Cache; Core Services to Search Index; Background Workers to PostgreSQL.CLIENTSGATEWAY & AUTHAPP SERVICESDATA LAYERWeb Apptenant user sessionAPI Clienttenant API keyAPI Gatewayrate limits per tenantIdentity ProviderJWT with tenant_id claimCore Servicestenant context middlewareBackground Workerstenant_id on every jobPostgreSQLrow-level securityKey Managementper-tenant data keysRedis Cachetenant-prefixed keysSearch Indextenant filter enforcedsign intenant contextSET tenant_id
The gateway resolves the tenant from the authenticated session and passes it with every call. Services use it to set the Postgres session variable for row-level security, pick the tenant's encryption key and scope cache and search lookups.
Start Your Digital Evolution

Let’s Build Something Extraordinary Together

Tell us about your software initiative, timeline, or technical challenge. Our principal solutions architect will respond within 4 business hours.

Project Consultation Request

Please enter your full name.
Please enter a valid work email address.
Please select a service domain.
Please select an estimated budget range.
Please describe your project (minimum 15 characters).

We reply by email and never share your details. See our Privacy Policy.

This site is protected by reCAPTCHA and the GooglePrivacy Policy andTerms of Service apply.

Headquarters & Direct Channels

Ahmedabad HeadquartersVandemataram City, Gota, Ahmedabad, Gujarat, India - 382481
Enterprise Inquirieshello@evoxsoft.com
Direct Consultation Line+91 98981 85028
EvoxSoftAhmedabad Headquarters
Get Directions