ArmorDB Logo
ArmorDB
Postgresql Saas Tenancy Models
PostgreSQL SaaS Tenancy Models: Shared Tables, Schemas, or Databases?
Back to Blog
Data-Specs
June 27, 2026
8 min read

PostgreSQL SaaS Tenancy Models: Shared Tables, Schemas, or Databases?

Compare shared-table, schema-per-tenant, and database-per-tenant PostgreSQL designs for SaaS apps, including isolation, migrations, pooling, backups, and when to switch models.

AE
ArmorDB EngineeringArmorDB engineering
PostgreSQLSaaSMulti-tenancy

Most SaaS products have to answer the same database question earlier than expected: should every customer share the same PostgreSQL tables, get a separate schema, or get a separate database? The answer affects security reviews, query performance, migrations, backups, connection pooling, and the price of operating the product.

There is no single tenancy model that stays best forever. A small product often needs speed, simple migrations, and low operational overhead. A larger product may need tenant-specific restore, stronger blast-radius boundaries, or a way to move high-volume customers away from the shared workload. The practical goal is to choose the model that matches the next stage of the business without painting the team into a corner.

The three common PostgreSQL tenancy models

A shared-table model stores many tenants in the same tables and uses a tenant key such as tenant_id, organization_id, or workspace_id on tenant-owned rows. It is the default shape for many SaaS apps because it keeps the schema compact and lets application features ship without provisioning new database objects per customer. PostgreSQL row-level security can add a database-enforced guardrail, but the application still needs clear tenant context and authorization.

A schema-per-tenant model keeps one PostgreSQL database but creates a separate schema for each tenant. PostgreSQL schemas are namespaces inside a database, so tables with the same names can exist in different schemas. This can make tenant boundaries more visible and can simplify some per-tenant exports. It also multiplies migration work because each schema has its own copy of the application tables.

A database-per-tenant model provisions a separate PostgreSQL database, and sometimes a separate cluster or instance, for each tenant. This gives the strongest operational boundary among the three common patterns. It can also turn every routine task into fleet management: migrations, monitoring, connection limits, backups, extensions, and incident response now happen across many databases instead of one.

Comparison matrix

ModelBest fitMain strengthMain cost
Shared tables with tenant keysEarly and mid-stage SaaS with mostly similar customersLowest operational overhead and easiest global analyticsIsolation depends on careful queries, constraints, roles, and tests
Shared tables plus RLSSaaS teams that want database-enforced tenant filteringStronger defense against missed tenant predicatesRequires disciplined role design, transaction-scoped tenant context, and pooling tests
Schema per tenantModerate tenant counts with custom data lifecycle needsClear namespace boundary inside one databaseMigrations, search_path handling, and object count grow with tenants
Database per tenantEnterprise customers, strict restore boundaries, or noisy high-value tenantsStrong backup, restore, and blast-radius separationMore provisioning, monitoring, pooling, upgrade, and cost management
Hybrid modelProducts with a long tail plus a few large tenantsKeeps the common path simple while isolating exceptionsRequires tooling to move tenants and support two operating modes

The table hides one important detail: these models are not only security choices. They are also migration and operations choices. A design that looks clean in an entity-relationship diagram can become painful if every deploy has to update 2,000 schemas, or if every customer database opens its own pool of idle application connections.

Shared tables are usually the best starting point

For most new SaaS products, shared tables with an explicit tenant key are the best default. The schema is easy to understand, migrations run once, indexes can be designed around real query patterns, and cross-tenant product analytics are straightforward. This model also fits managed PostgreSQL well because one database can use one connection pool, one backup policy, and one observability surface.

The risk is that isolation is not automatic. Every tenant-owned table should have a required tenant key, and common access paths should include that key in indexes where it matters. Foreign keys should preserve tenant boundaries rather than allowing an invoice for one tenant to point at a customer from another. For example, a SaaS billing table often wants a uniqueness rule scoped by tenant, such as one external invoice ID per tenant, not one global invoice ID unless the upstream system guarantees global uniqueness.

If the product has strong isolation requirements, PostgreSQL row-level security is a useful next layer. RLS lets PostgreSQL attach visibility and write checks to tables, so a missing WHERE tenant_id = ... predicate in application code does not automatically expose every row. The safest RLS designs keep policy logic boring: set tenant context at the start of a transaction, connect through a non-owner application role, and use explicit USING and WITH CHECK policies on tenant-owned tables. The related ArmorDB guide on PostgreSQL row-level security for SaaS covers those mechanics in more depth.

Schema per tenant is a middle ground, not a free boundary

Schema-per-tenant designs appeal because they make tenant separation visible without creating a separate database for every customer. A support engineer can inspect one namespace. A tenant export can target a smaller set of tables. A customer with custom table-level extensions or migration timing may feel easier to handle.

The tradeoff is object multiplication. Every tenant adds another copy of tables, indexes, constraints, functions, and migration state. A migration that adds a column no longer changes one table; it changes one table per tenant schema. Failed migrations need tenant-aware retry logic. Application code must set search_path safely or fully qualify objects, and connection pooling must not allow one request's schema context to leak into the next request.

That last point matters with PgBouncer and application pools. If tenant routing depends on session state, transaction pooling can break assumptions because a client transaction may not keep the same server connection beyond the transaction boundary. Session pooling preserves more session state but reduces the amount of connection reuse. For teams using PgBouncer, tenancy state should be explicit and tested with the actual pooling mode. The ArmorDB article on PgBouncer pooling modes is a useful companion before choosing this model.

Schema-per-tenant is most attractive when the tenant count is moderate, the product genuinely benefits from namespace-level separation, and the team already has migration automation. It is less attractive when the expected customer count is high, feature releases are frequent, or most queries need global reporting across all tenants.

Database per tenant buys operational isolation

Database-per-tenant is the clearest isolation story for backups, restores, noisy-neighbor control, and customer-specific maintenance. If one enterprise customer needs a point-in-time restore, a dedicated database is easier to reason about than restoring a shared database and surgically extracting one tenant. If one customer generates far more write volume than the rest, moving that tenant to its own database or cluster can protect the long tail.

The cost is that the database layer becomes a fleet. You need consistent provisioning, secrets, extension policy, monitoring, backup verification, upgrade scheduling, and migration orchestration across many databases. Application connection management also becomes more complex. A naive pool per tenant can exhaust PostgreSQL connection limits quickly, especially in serverless or horizontally scaled application environments. PgBouncer helps, but it does not remove the need to budget connections intentionally.

This model works best when tenant isolation is part of the product promise, when customers pay enough to justify dedicated operations, or when compliance and restore requirements make shared storage difficult to defend. It is often excessive for an early product whose main risk is still finding repeatable usage.

A practical decision path

Start by separating tenant isolation requirements from tenant customization requirements. If tenants use the same product with the same schema, shared tables are likely enough at first. Add database-level guardrails such as constraints, non-owner roles, and RLS before jumping to a more operationally expensive shape. If tenants need custom lifecycle controls, exports, or maintenance windows, schema-per-tenant may be worth testing. If tenants need dedicated restore boundaries, dedicated performance envelopes, or contractual separation, database-per-tenant becomes easier to justify.

Then model the operational path, not just the data model. Ask how a migration runs, how a failed migration rolls back, how backups are restored, how a tenant is moved, how many database connections the app opens at peak, and how support staff access data without bypassing controls. These questions usually reveal the real cost of the model.

Decision questionShared tablesSchema per tenantDatabase per tenant
Can one migration update the product quickly?Yes, usually once per tableOnly with tenant-aware migration loopsOnly with fleet migration tooling
Can one tenant be restored independently?Hard without careful export/import workflowsEasier than shared tables, still inside one databaseCleanest operational boundary
Does global analytics stay simple?YesRequires cross-schema queries or ETLRequires cross-database aggregation
Is connection pooling straightforward?UsuallyDepends on search_path and pooling modeRequires per-tenant routing and connection budgeting
Can one noisy tenant be isolated?Not without moving dataPartially, but shared database resources remainYes, especially with separate clusters

For many teams, the right answer is hybrid over time. Keep most customers in shared tables, design tenant keys and constraints carefully, and build a migration path for the few tenants that later need dedicated databases. That path might include tenant export jobs, routing metadata, and application code that can resolve a tenant to a database location. Building those seams early is cheaper than pretending the first model will last forever.

Implementation details that prevent regret

Use stable tenant identifiers everywhere. Human-readable slugs change, billing customer IDs may come from another system, and organization names are not keys. A generated tenant UUID or similarly stable identifier should appear consistently in tenant-owned tables, logs, audit records, and routing metadata.

Keep roles boring. Application traffic should not connect as a table owner or superuser. Migration roles, support roles, and background worker roles need separate privileges because they represent different risks. In shared-table designs, this makes RLS and privilege reviews meaningful. In schema-per-tenant and database-per-tenant designs, it prevents operational shortcuts from becoming the normal access path.

Test restores before the first incident. Shared-table restores require a plan for extracting or replaying one tenant without corrupting relationships. Schema-per-tenant restores still need dependency checks if shared lookup tables exist. Database-per-tenant restores are cleaner, but only if backups are actually verified and routing can point the application at the restored database safely.

Finally, design for movement. Even if the product starts with shared tables, add enough metadata to know where a tenant lives and enough discipline that tenant-owned data is identifiable. The moment a large customer needs isolation, the hard part should be copying and validating data, not discovering which rows belong to that customer.

Recommendation

Choose shared tables with required tenant keys for the first version unless you already have a contractual or operational reason not to. Add constraints, indexes, non-owner roles, and RLS where the risk justifies it. Consider schema-per-tenant only when namespace-level separation clearly helps and migration automation is ready. Use database-per-tenant for high-value customers that need independent restore, dedicated performance, or stronger blast-radius boundaries.

ArmorDB is a good fit for the common path: managed PostgreSQL, backups on paid plans, and PgBouncer included without making a small team operate database servers. If your product is still deciding how much isolation each customer needs, start simple, keep tenant boundaries explicit, and leave yourself a clean route to split out demanding tenants later.

Sources and further reading

Topic

Data-Specs

Updated

Jun 27, 2026

Read time

8 min read

About the author

ArmorDB Engineering writes about PostgreSQL operations, security, and infrastructure decisions for teams building production apps on ArmorDB.