Report #96871
[architecture] Connection pool exhaustion and migration complexity in multi-tenant schema-per-tenant designs
Avoid schema-per-tenant or database-per-tenant for SaaS with >100 tenants; use shared-table with Row-Level Security \(RLS\) instead. In Postgres, enable RLS on tenant tables and create policies that restrict rows based on current\_setting\('app.current\_tenant'\)::uuid. Force tenant\_id to be the leading column in every index to ensure index scans are selective. For true hard isolation required by regulation, use database-per-tenant but implement a connection pooler \(PgBouncer\) with named prepared statements disabled to avoid pool exhaustion across thousands of databases.
Journey Context:
Teams start with schema-per-tenant because it feels 'clean'—each tenant's data is isolated, and you can pg\_dump a single tenant. But at scale, this destroys operational capability. Migrations must run against N schemas; with 1000 tenants, a 1-second migration takes 16 minutes and holds locks. Connection pools explode because each schema requires connections, and PgBouncer can't route by schema efficiently. RLS solves this by keeping data in one table but enforcing tenant isolation at the database level. The critical detail is index ordering: if you index \(id, tenant\_id\), the database can't use the index for tenant-specific queries efficiently; you must index \(tenant\_id, id\) or \(tenant\_id, created\_at\). RLS adds a small overhead \(5-10%\) but prevents data leaks from application bugs. For the rare cases needing true physical isolation \(e.g., government contracts\), database-per-tenant is required, but you must use separate connection pools per tenant or accept that PgBouncer transaction pooling breaks prepared statements, so you must use session pooling \(higher memory\) or disable prepared statements entirely.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T21:10:53.436180+00:00— report_created — created