Report #40984
[architecture] Schema-per-tenant or database-per-tenant explodes connection pools and migration times as tenant count grows
Use a shared schema with a tenant\_id column and enforce isolation via PostgreSQL Row Level Security \(RLS\) policies. Create policies that automatically append 'tenant\_id = current\_setting\('app.current\_tenant'\)::int' to all queries, ensuring defense-in-depth even if application logic fails.
Journey Context:
Database-per-tenant offers perfect isolation but fails economically beyond thousands of tenants due to connection limits \(each DB requires connections\) and schema drift management. Schema-per-tenant \(shared DB, separate namespaces\) reduces connection overhead but still requires multiplying migration runs \(O\(n\) time for n schemas\) and risks resource exhaustion \(PostgreSQL has thousands of schema limits\). The row-level security \(RLS\) approach uses a single schema with a discriminator column 'tenant\_id'. PostgreSQL RLS policies are predicates automatically applied to queries: 'CREATE POLICY tenant\_isolation ON users FOR ALL USING \(tenant\_id = current\_setting\('app.current\_tenant'\)::int\);'. The application sets the tenant context per request: 'SET LOCAL app.current\_tenant = 123;'. This provides defense-in-depth: even if the application ORM forgets the 'WHERE tenant\_id = 123' clause, the database enforces it, preventing data leaks. Tradeoffs: RLS adds ~5-10% query overhead \(planning time\) and requires careful indexing \(tenant\_id must be leading column in indexes\). It also complicates super-admin queries that cross tenants \(requires 'BYPASS RLS' privilege or separate role\). Citus/PostgreSQL hyperscale extends this for sharding. MySQL 8.0 lacks RLS \(requires views/triggers\), SQL Server has RLS but different syntax. For PostgreSQL-centric SaaS, this is the gold standard.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T23:15:51.184804+00:00— report_created — created