Robustech
One app many customers: Keep their data apart. A tower of separate glass units held in one orange frame

Custom CRM & Micro-SaaS

Multi-Tenant SaaS Architecture: Silo, Pool or Bridge?

Compare silo, pool and bridge tenancy models, learn how Postgres row-level security works and fails, handle noisy neighbors and choose a model for your SaaS.

By Issa Badran · Published · 10 min read

Multi-tenant SaaS architecture means one product serves many customer organizations (tenants), sharing some or all of its infrastructure while keeping each customer's data strictly separate. There are three broad models: silo (dedicated resources per tenant), pool (everything shared) and bridge (a mix). Most new SaaS products should start pooled, with a tenant_id on every row and database-level safeguards like PostgreSQL row-level security, then promote large or regulated customers to dedicated resources when they need it. This guide compares the models, explains row-level security and its pitfalls, and gives you a decision guide.

Key Takeaways

  • AWS defines three models: silo (dedicated per tenant), pool (shared) and bridge (a mix of the two).
  • A shared database gives the highest density and lowest cost, according to Microsoft, but brings noisy neighbors and harder per-tenant restores.
  • PostgreSQL row-level security is a strong safety net, but superusers, BYPASSRLS roles and (unless forced) table owners bypass it.
  • Index the columns your security policies use; Supabase's own benchmark shows the difference can be dramatic.
  • Design for "promotion" from day one so a big tenant can move to its own database without a rewrite.

What Multi-Tenancy Means in Plain English

Think of an apartment building. Every tenant has a private, locked unit, but they share the foundation, plumbing and elevators. Sharing keeps rent down. The risks are a neighbor who hogs the hot water (performance) or, much worse, a door that opens into the wrong apartment (a data leak).

In SaaS, a tenant is usually a customer company. A tenant has its own users, records, settings and bill. The architecture question is how much you share: the application servers, the database, the tables, or nothing at all.

The stakes are high. AWS's 2020 tenant isolation whitepaper, now marked as historical reference, describes a cross-tenant breach as "a significant and potentially un-recoverable event" (AWS whitepaper). For a young SaaS company, one customer seeing another's data can end the business.

If you're still deciding whether to build a SaaS product at all, start with our pillar guide on how to build a SaaS platform.

Silo, Pool and Bridge Models Compared

AWS describes three models: silo, where each tenant gets dedicated resources; pool, where tenants share resources; and bridge, a mix, such as siloing some services while pooling others (AWS SaaS Lens). At the database level, these usually translate to a database per tenant, a shared database with a tenant_id column, or a combination including schema per tenant.

Silo (database per tenant) Pool (shared database, tenant_id) Bridge (mixed)
What's shared Application code and operations; data stores are dedicated Everything, including tables Some components shared, others dedicated
Isolation strength Strongest; separation is physical Logical; depends on correct filtering and policies Varies by component
Cost per tenant Highest; cost scales roughly with tenant count Lowest; highest density In between
Noisy neighbor risk Lowest (none if each tenant's database has dedicated compute) Highest Contained for siloed parts
Per-tenant backup and restore Simple Harder; may require restoring and extracting one tenant Depends on where the tenant lives
Schema changes Must roll out to every database One change for everyone Mixed
Best fit Few large, regulated or enterprise tenants Many small to mid-size tenants Mostly small tenants plus a few large ones

Microsoft's guidance puts the trade-off bluntly. For single-tenant deployments, "If a single tenant requires a specific infrastructure cost, 100 tenants probably require 100 times that cost," though the benefits include strong isolation, no noisy neighbors and the ability to roll out updates progressively (Microsoft Learn). A shared database offers the highest density and lowest cost, but brings scale limits, noisy neighbors, harder per-tenant metering, difficulty serving tenants with different requirements and hard schema customization. Restoring a single tenant may mean restoring the whole database to a separate resource and extracting that tenant's data (Microsoft Learn).

Microsoft also reminds builders that choosing a tenancy model "isn't only a technical decision. It's also a commercial decision" (Microsoft Learn). Your model determines your margins and what isolation you can sell to enterprise buyers.

Comparison of three ways to run one app for many customers: separate (silo), shared (pool) and mixed (bridge), by what is shared, how data is kept apart, cost per customer and best fit
Separate, shared or mixed: the three ways to keep customers' data apart.

Schema per Tenant

Schema per tenant gives each customer its own set of tables inside a shared database. It offers clearer separation than a tenant_id column and makes per-tenant export easier, but every migration must run once per schema. Microsoft's guide to PostgreSQL on Azure describes schema-based sharding as "well-suited for workloads that have between 1 and 10,000 tenants," alongside row-based sharding by tenant ID (Microsoft Learn). That guidance is specific to Azure's elastic clusters, but it's a useful signal that schema per tenant suits moderate tenant counts rather than very large ones.

Antipatterns to Avoid

Microsoft lists several data antipatterns for multi-tenant systems (Microsoft Learn):

  • Creating a separate table for each tenant inside a shared database.
  • Adding columns to shared tables for one tenant's needs.
  • Making manual schema changes.
  • Depending on a single schema version, which makes it hard to roll changes out gradually.

If customers need their own fields, store them in a structure designed for it. Our CRM data model guide compares columns, JSONB and EAV for exactly this problem.

PostgreSQL Row-Level Security, Explained

In a pooled database, every query must include "and only for this tenant." If one developer forgets that filter in one query, data leaks. Row-level security (RLS) moves that rule into the database itself: PostgreSQL silently adds the tenant condition to every query on a protected table, so a query that forgets the tenant filter still returns only the current tenant's rows, and a request with no tenant set returns no rows rather than every tenant's.

Here's an illustrative setup for a deals table:

-- Illustrative only
alter table deals enable row level security;
alter table deals force row level security;  -- apply to the table owner too

create policy tenant_isolation on deals
  using (tenant_id = nullif(current_setting('app.tenant_id', true), '')::uuid)
  with check (tenant_id = nullif(current_setting('app.tenant_id', true), '')::uuid);

create index deals_tenant_id_idx on deals (tenant_id);

-- In the application, inside each transaction (third argument true = transaction-scoped):
select set_config('app.tenant_id', $1, true); -- inside the transaction, parameterized

Custom settings like app.tenant_id can be set by any database session, so this pattern is only safe when your trusted application server is the only thing connecting to the database (no direct client access).

The using clause controls which rows a query can see. The with check clause stops anyone writing a row tagged with another tenant's ID. Setting the tenant ID with set_config(..., true) keeps it scoped to the current transaction, so a pooled connection doesn't carry one tenant's setting into the next request.

RLS Pitfalls That Cause Real Leaks

RLS behaves differently from what many developers assume. The PostgreSQL documentation spells out the rules:

  • It's off by default. You must enable it on each table. Forget one table and that table is unprotected.
  • Enabled with no policy means default deny. That's safe, but it can confuse developers who see empty results.
  • Superusers and roles with BYPASSRLS always bypass it. Your application should never connect as either.
  • Table owners bypass it unless you use FORCE ROW LEVEL SECURITY. Many apps connect as the role that created the tables, which quietly disables protection.
  • Unique, primary-key and foreign-key checks bypass RLS. A uniqueness error can reveal that a value exists in another tenant's data. Making unique constraints include tenant_id (for example, unique per tenant email) reduces that risk.

If you use Supabase, its docs add platform-specific rules: enable RLS on every table in an exposed schema, keep the service_role or secret key on the server because it bypasses RLS, write auth.uid() as (select auth.uid()) inside policies, and index the columns your policies reference (Supabase Docs).

RLS Performance

Policies run on every query, so their cost adds up. In Supabase's own vendor benchmark, indexing the policy column took a sample query from 171 ms to under 0.1 ms, and wrapping a function call in a select took one case from 179 ms to 9 ms (Supabase troubleshooting guide). Your results will differ, but the lesson holds: index tenant_id and keep policies simple.

Why Some Teams Skip RLS

Microsoft notes that RLS requires passing user and tenant identity to the data store on every query, and that "Many multitenant solutions don't use row-level security because of those complexities" (Microsoft Learn). That's a fair warning, not a reason to skip it. Treat RLS as defense in depth: your application still enforces tenant and role checks, and RLS catches what the application misses. Whichever approach you choose, write automated tests that sign in as Tenant A and try to read, update and delete Tenant B's records.

The Noisy Neighbor Problem

A noisy neighbor is a tenant whose activity degrades performance for others. Microsoft notes it can also happen when many small tenants peak at the same time (Microsoft Learn). Picture a customer running a giant report export at 9 a.m. Monday while everyone else is trying to log calls.

Microsoft's recommended mitigations include per-tenant monitoring, throttling and rate limiting, query limits, rebalancing tenants and quality-of-service controls. Building on that guidance, here's how those translate in practice:

  1. Per-tenant monitoring, so you can see which tenant is causing load.
  2. Throttling and rate limiting per tenant.
  3. Query limits that cap expensive operations.
  4. Rebalancing tenants across deployment stamps (separate copies of your infrastructure).
  5. Quality-of-service controls that prioritize interactive work over background jobs.

In PostgreSQL specifically, each connection runs as its own server process, which makes connection pooling with a tool like PgBouncer important (Microsoft Learn).

In practice, moving heavy reports and exports to background queues or a read replica also helps.

Tenant Lifecycle: Onboarding to Offboarding

Architecture isn't only about steady state. Plan each stage of a tenant's life:

  • Onboarding. Creating a tenant should be fully automated: a tenant record, default settings, the first admin user and, in a silo model, provisioned infrastructure. AWS notes that shared onboarding is part of what defines SaaS even in silo models (AWS SaaS Lens).
  • Migrations. Keep schema changes in version control and apply them by script to every tenant, recording which version each tenant is on.
  • Backup and restore. Decide in advance how you'd restore one customer's data after they delete something by mistake. In a pooled database, that usually means restoring to a separate server and copying one tenant's rows back.
  • Offboarding. Be able to export a tenant's data in a usable format and then delete it completely, including from search indexes, file storage and analytics.

Promoting Big Tenants to Dedicated Resources

The most practical path for many products is to start pooled and promote large tenants later, which is the bridge model in action. That only works if you prepare for it early:

  1. Put tenant_id on every table, including join tables and logs.
  2. Route connections through a tenant catalog that records where each tenant's data lives.
  3. Script export and import for a single tenant, and test it.
  4. Use the same schema and code for pooled and dedicated tenants, so promotion moves data, not logic.

With those pieces in place, moving an enterprise customer to its own database is an operational task you can schedule, not a re-architecture.

Decision Guide: Which Tenancy Model Fits?

If this describes you Start with
Many small or mid-size customers, price-sensitive product Pool: shared database, tenant_id, RLS as backup
A handful of large enterprise customers Silo: database per tenant
Contracts require per-tenant encryption keys, data residency or backup rules Silo for those tenants
Mostly small customers plus a few large or regulated ones Bridge: pool by default, promote when needed
A moderate number of fairly large tenants wanting clear separation Schema per tenant
Only one customer: your own company No multi-tenancy needed; build a single-tenant app

Limitations and When Pooling Is the Wrong Choice

Pooling isn't always right. If you serve only a handful of enterprise customers, or contracts require per-tenant keys, residency or backup terms, silo can be simpler to sell and support despite the cost. If tenants need heavily different data structures, a shared schema fights you at every step.

RLS also has limits. It's a backstop, not your authorization system, and it doesn't protect data you copy into caches, search indexes, file storage or analytics tools. Each of those needs its own tenant checks. Finally, none of this matters if your billing, admin tools or background jobs run with a role that bypasses the protections you've designed.

Getting Tenancy Right Early

Tenancy is the hardest decision to reverse in a SaaS product, so make it deliberately: pool by default, enforce isolation in both code and database, monitor per tenant and keep a tested path to promote big customers. Connect it to billing early too, since plan limits and per-tenant usage feed directly into your Stripe subscription billing setup. If you want a second opinion on your tenancy design, Robustech's custom CRM and SaaS development team reviews and builds multi-tenant platforms that clients own.

Frequently asked questions

Should I use a database per tenant, a schema per tenant or a shared database with a tenant_id?

Most new SaaS products should start with a shared database and a tenant_id on every row, because it is the cheapest to run and the simplest to update. Use a separate database for customers with strict isolation, data residency or backup requirements. Schema per tenant sits in between and suits products with a moderate number of fairly large tenants.

Is Postgres row-level security enough to isolate tenants?

No. Row-level security is a strong second layer, but it should back up tenant checks in your application code rather than replace them. It can be bypassed by superusers, roles with BYPASSRLS and, unless you force it, table owners. Test isolation explicitly by trying to read another tenant's data in automated tests.

How do I stop one big customer from slowing down everyone else?

Monitor load per tenant so you can see who is causing it, then apply rate limits, query limits or throttling per tenant. If one customer consistently needs far more capacity, move them to their own database or a separate deployment. Connection pooling also matters in PostgreSQL, because each connection uses a separate server process.

Can I move a large customer to its own database later?

Yes, if you planned for it. Put a tenant_id on every row, route database connections through a tenant catalog that records where each tenant lives, and script the export and import process. With those in place, promoting a large tenant from the shared pool to a dedicated database becomes a routine operation rather than a rewrite.

How do I run database migrations across many tenant schemas or databases?

Automate them. Keep migrations in version control, run them through a script that applies each change to every tenant and records which version each tenant is on, and roll changes out in batches so a failure affects a few tenants rather than all of them. Never apply schema changes by hand to individual tenants.

Keep reading

Ready to own your revenue engine?

Book a free strategy call. We will audit your ads, site, CRM and follow-up and show you where leads are leaking.