GrowthSpare IT Solutions

GrowthSpare

Empowering Digital Innovation
CRM & SaaS Solutions • Aug 29, 2026 • 1 min read

Architecting Multi-Tenant SaaS Databases in PostgreSQL

A
Admin User Systems Engineer
Architecting Multi-Tenant SaaS Databases in PostgreSQL

Quick answer: most B2B SaaS products should start with shared tables plus a tenant_id column guarded by tests and (ideally) PostgreSQL Row-Level Security, move to schema-per-tenant when compliance or per-tenant operations demand it, and reserve database-per-tenant for enterprise isolation contracts. The wrong choice early is survivable; the wrong choice after 200 tenants is a migration project. Decide on isolation needs, not fashion.

Model 1: shared database, shared schema (tenant_id)

Every row carries its tenant; every query filters by it. Cheapest to build, operate, and back up; onboarding a tenant is one INSERT. The risk is cross-tenant leakage from a single missed filter — mitigate with a mandatory tenant scope in the ORM layer, RLS policies as a second lock, and tests that specifically attempt cross-tenant reads. Right for: most startups, internal tools, CRMs, school/clinic systems.

Model 2: shared database, schema per tenant

Each tenant gets its own PostgreSQL schema with identical tables. Stronger isolation, per-tenant migrations and restores, but schema-count scaling pain (migrations across 500 schemas are slow), harder cross-tenant analytics, and connection-pool pressure. Right for: regulated clients, tenants demanding data separation, white-label products with divergent schemas.

Model 3: database per tenant

Maximum isolation — separate backups, credentials, even versions per tenant — at maximum operational cost: provisioning automation, per-DB migrations, monitoring sprawl. Right for: enterprise contracts that require it, or tenants big enough to fund their own infrastructure. Wrong for: a 30-customer startup that just likes the sound of it.

Django specifics

Resolve the tenant in middleware (subdomain, header, or authenticated org), store it on the request/thread-local, and enforce it in a custom manager so bare Model.objects.all() can never leak. For schema-per-tenant, use search_path switching with disciplined migrations. For RLS, set the tenant via session variables in the same transaction. Whichever model: tenant-aware fixtures, tenant-scoped admin, and backup/restore drills per isolation unit.

The mistakes that hurt later

  • Unscoped queries in background tasks (Celery has no request — pass tenant explicitly).
  • Noisy neighbours: one tenant's report query starving others — statement timeouts and read replicas.
  • Analytics afterthought: cross-tenant reporting on schema-per-tenant requires ETL you didn't budget.
  • Restore granularity: "restore tenant X to Tuesday" is trivial per-schema/DB and painful in shared-schema without point-in-time tooling.

Questions to ask any vendor

Which model and why for our tenant count? Show me the tenant-isolation tests. How do you restore one tenant? How do migrations run across tenants, and how long do they take at 10× our size? Vague answers here predict outages later. If you're scoping a product now, our engineering team answers these in writing before we build — start with a scoping session, especially for CRM-style products like our custom CRM builds.

Topic Tags:
#SaaS #PostgreSQL #Django #Architecture
Liked this article? Share it

Comments Thread (0)

No comments posted yet. Be the first to share your insights!

Post a Response

Note: Comments are moderated before display in order to filter spam.

About the Author
A

Admin User

Systems Engineer

Technical author and developer at GrowthSpare IT Solutions. Deeply focused on custom Django logic databases, automated API workflows, and cloud-native solutions.

Get a Free Website Audit