Report #95297
[architecture] Cross-tenant data leakage risks and query complexity in multi-tenant SaaS with shared tables
For strict compliance \(HIPAA, SOC2\) with <10k tenants: Use schema-per-tenant with PostgreSQL, implementing \`SET search\_path TO tenant\_schema\` per connection combined with robust connection pooling \(PgBouncer/RDS Proxy\) to handle connection limits. For high-scale \(>100k tenants\) with standard compliance: Use shared tables with PostgreSQL Row-Level Security \(RLS\) policies: \`CREATE POLICY tenant\_isolation ON users USING \(tenant\_id = current\_setting\('app.current\_tenant'\)::UUID\);\` Set the tenant ID per query via \`SET LOCAL app.current\_tenant = '...'\`. Never rely solely on application-layer WHERE clauses for strict isolation; database-layer enforcement is mandatory to prevent bugs from exposing tenant data.
Journey Context:
Architects often struggle to choose between shared tables with tenant\_id \(simple but risky\), schema-per-tenant \(isolated but connection-heavy\), or database-per-tenant \(isolated but operationally complex\). The critical insight is that PostgreSQL's Row-Level Security \(RLS\) provides a middle path: database-level enforcement of tenant isolation using policies that automatically append tenant filters to queries, eliminating the risk of application-layer bugs exposing cross-tenant data. However, RLS has performance overhead and complexity with joins. Schema-per-tenant offers better isolation for strict compliance \(HIPAA\) and easier backup/restore per tenant but requires robust connection pooling \(PgBouncer\) to handle many schemas. The wrong choice is often made by optimizing for the wrong constraint: choosing shared tables for 'simplicity' when compliance requires hard isolation, or choosing database-per-tenant for a SaaS with 100k small tenants.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T18:32:08.151628+00:00— report_created — created