Pattern 1: Database per Tenant
Maximum isolation — each tenant has a physically separate database. A bug that exposes one tenant’s data cannot reach another’s. Appropriate for enterprise contracts with strict data residency requirements or highly regulated data. Cost scales linearly with customer count, making it impractical for self-serve SaaS products with many smaller customers.
Pattern 2: Schema per Tenant (Our B2B SaaS Default)
Each tenant gets their own PostgreSQL schema within a shared database. The application sets search_path to the tenant’s schema on each connection. Strong isolation — SQL injection in tenant A’s context cannot read tenant B’s tables without explicit cross-schema references. One database to back up, monitor, and scale. Manageable migrations with Flyway and Liquibase’s schema-per-tenant support.
Pattern 3: Shared Schema with Row-Level Security
All tenants share the same tables. Every table has a tenant_id column. PostgreSQL row-level security enforces that queries return only rows matching the current session’s tenant_id — at the database level, not the application level. Simplest to operate and most appropriate for consumer SaaS or B2B products with many small customers.
Our Recommendation
Schema-per-tenant for B2B SaaS with 10–10,000 business customers. Shared schema with RLS for consumer products. Database-per-tenant for enterprise contracts with strict compliance or data residency requirements.
