By Techosoul Editorial Team

“Keep every customer's data separate” sounds like a complete requirement. It is actually the beginning of several decisions. A product may need to prevent unauthorized reads, protect one customer's performance, restore one tenant after a mistake, or place a workload in a particular region. Each requirement points to a different boundary.
Our guide to B2B SaaS architecture connects tenancy with deployment and operations. This article examines the storage decision more closely: shared tables, separate schemas, or separate databases. PostgreSQL provides the concrete examples; other engines and managed services need their own configuration review.
Write the isolation promise before choosing a model
Begin with outcomes the product can demonstrate. “A project manager cannot retrieve another tenant's documents” is an access requirement. “A large export must not make another tenant miss its response-time target” is a performance requirement. “Recover one tenant without reverting another tenant's recent work” is a recovery requirement.
Microsoft's multitenant storage guidance treats workload, geography, recovery, customization and operating complexity as separate considerations. An architecture can satisfy one while leaving another unresolved. A database name alone does not establish residency or physical separation.
For a hypothetical project-management product, ask the team to record the required boundary, the person who owns that requirement and the test that will demonstrate it. Include files, search indexes and exports in the inventory. Separating relational rows while sharing unprotected document links would leave the product requirement incomplete.
Shared tables: keep tenant identity in the data model
In a shared-table design, tenant-owned records occupy common tables and carry a tenant identifier. The application must consistently associate each operation with the correct tenant. Centralizing those tables also means that one schema change can affect every tenant using them.
PostgreSQL row-level security, or RLS, adds database policies that control which rows normal queries can read or modify. Once RLS is enabled, the absence of an applicable policy results in default denial. Policies can distinguish read access from permitted inserted or updated values.
The executing role matters. Superusers and roles with BYPASSRLS bypass these policies. Table owners normally bypass them too; FORCE ROW LEVEL SECURITY can subject an owner to policies. Whole-table operations such as TRUNCATE are outside RLS. Give the normal application role only the privileges its work requires, and evaluate policies using that role. Keep schema administration separate from normal application access.
For our example, a shared-table design is a candidate when customers use the same product model and the team can prove its access controls. Its appeal should be tested against real recovery and workload requirements, rather than inferred from a low initial infrastructure bill.
Separate schemas: organize objects and control privileges
A schema per tenant gives each customer a namespace containing its own tables and related objects within a database. This can make object ownership and tenant-specific operations easier to reason about, but the namespace itself is not an access policy.
PostgreSQL's schema documentation explains that users can access objects in other schemas when privileges allow it. It also warns that including a schema in search_path trusts users who can create objects there. Review schema and object grants, ownership and name resolution together.
In the project-management example, a schema name selected from an untrusted request would not be an adequate boundary. The trusted application must select the authorized tenant's namespace. The team also needs a repeatable way to provision schemas, track their versions and recover when an upgrade succeeds for some tenants but fails for others.
Separate databases: specify what remains shared
A database per tenant creates a different connection destination for each customer's relational data. Routing now needs an authoritative tenant-to-database mapping. Credentials that can access every database still require careful handling; changing the storage layout does not remove application authorization.
The AWS PostgreSQL bridge-model guidance explicitly distinguishes a PostgreSQL database from an RDS or Aurora database instance. Multiple tenant databases can share an instance. That arrangement can retain contention between workloads, just as separate schemas can. Dedicated compute is another architecture decision.
For our hypothetical product, separate databases might make tenant-specific export or schema work easier to organize. The trade-off is a fleet of destinations to provision, monitor and upgrade. Ask who will detect an incomplete migration, prevent connections to the wrong destination and maintain a usable inventory as tenants move.
Protect relationships as well as individual rows
Consider a shared tasks table containing tenant_id and project_id. A foreign key on project_id alone can prove that a project exists without proving that it belongs to the task's tenant. Even a globally unique project identifier does not express the relationship's tenant requirement.
PostgreSQL supports constraints across multiple columns. In this example, one design is to make the pair tenant_id and project_id unique in projects, then reference that same pair from tasks. Require both values where every task must belong to a tenant's project. This makes tenant membership part of the relationship being checked.
RLS remains a separate access control. PostgreSQL's RLS documentation notes that referential-integrity checks bypass row policies and can expose information through constraint behavior. Review error handling as well as successful queries. For the example product, include an attempt to attach a task to another tenant's project in the isolation test suite.
Make tenant context safe for connection reuse
Identifying a tenant and authorizing access to it are separate operations. Microsoft's request-mapping guidance requires authorization even when a request contains a tenant domain or identifier. Resolve the requesting user's permitted tenant before selecting a schema, database or RLS context.
Connection pooling adds another boundary to test. An AWS comparison of PgBouncer and RDS Proxy demonstrates how session-scoped tenant state can leak between clients in a misconfigured PgBouncer transaction pool. It describes transaction-scoped context, including SET LOCAL within an explicit transaction, as a mitigation.
For our product, trusted application code should establish the authorized context before tenant queries and reject a missing context. Test the actual driver, pooler and transaction behavior. A custom session variable is not proof of identity, and its value must not simply be accepted from the caller. Include requests from alternating tenants on reused connections, failed transactions and background jobs.
Match recovery promises to the real restore boundary
Separate databases do not automatically provide independent point-in-time recovery. PostgreSQL's continuous-archiving documentation describes physical recovery of an entire database cluster, rather than an arbitrary subset. Check the actual backup and restore unit of the selected engine, deployment and managed service.
Microsoft's storage guidance also explains that recovering one tenant from shared storage may require restoring to another resource and selectively extracting its data. Plan how recovered records will relate to current files, identifiers and other services before offering a recovery commitment.
In the hypothetical product, rehearse a mistaken project deletion. Decide which records return, which later changes must survive and who validates the result. Measure the time and operator effort. A backup that exists and a tenant recovery process that works are two different pieces of evidence.
Compare the models against one realistic workload
Suppose the project-management product serves a modest number of organizations in one region. They share the same features, but one prospective customer requests a dedicated recovery procedure. Another regularly runs large exports. These are illustrative requirements, not measured customer results.
- Shared tables: prototype the tenant policy, relationship constraints and selective recovery. Measure interactive requests while the large export runs.
- Separate schemas: demonstrate the privilege boundary and repeatable schema upgrades. Check whether the recovery procedure is actually simpler on the chosen infrastructure.
- Separate databases: prove correct routing, fleet upgrades and tenant recovery. Determine whether shared compute still misses the performance requirement.
Compare infrastructure cost together with operational work: provisioning, releases, incident investigation and rehearsed recovery. Use the product's own response-time and recovery targets. An option that needs daily manual intervention may be a poor fit even when its resource bill appears attractive.
For this example, the outcome could be shared tables for the common workload and a separately operated deployment for a customer whose requirements justify it. It could also be one consistent model. Mixing models creates another configuration and support burden, so it should solve an identified requirement.
Keep the decision testable as the product grows
Before selecting a model, run a small set of checks through the actual application identity:
- Attempt reads, writes, exports and search across tenant boundaries, including guessed identifiers.
- Exercise privileged maintenance separately from ordinary tenant operations.
- Verify that connection reuse and missing tenant context cannot select another tenant's data.
- Restore a tenant's example dataset and validate its relationships and external dependencies.
- Run competing workloads and an interrupted schema upgrade.
Record what passed, what remains uncertain and which conditions would trigger a new decision. For our example, those conditions might include a failed recovery exercise, a sustained workload that exceeds agreed limits, or a new placement requirement. They should be observable events rather than a vague intention to “scale later.”
The useful deliverable is a short architecture decision with a demonstrated access boundary, an operational owner and a recovery plan. That gives the product team a basis for customer commitments and gives engineering a clear reason to keep or change the chosen model.
Sources
- PostgreSQL: Row Security Policies
- PostgreSQL: Schemas
- PostgreSQL: Constraints
- PostgreSQL: Continuous Archiving and Point-in-Time Recovery
- AWS: PostgreSQL bridge model
- AWS: Understanding the differences between PgBouncer and Amazon RDS Proxy for PostgreSQL connection pooling
- Microsoft: Architectural approaches for storage and data in multitenant solutions
- Microsoft: Map requests to tenants in a multitenant solution
Sources checked on 1 November 2026. The PostgreSQL pages rendered the PostgreSQL 18 documentation at review. The AWS pooling article was published on 12 October 2026; Microsoft's storage guidance lists an update on 21 August 2026. The remaining documentation supplies established architectural and database behavior, not new-release claims. The project-management scenario and proposed checks are editorial examples.
Related reading
SaaS Architecture for B2B Products: Tenancy, Data, and Deployment
