Report #92892
[architecture] Using PostgreSQL Row-Level Security \(RLS\) for multi-tenancy causes full table scans and 10-100x query slowdowns despite indexes, due to optimizer limitations with volatile security barriers
Avoid RLS for high-throughput multi-tenant tables; instead use tenant\_id column with composite indexes \(tenant\_id, other\_cols\) and application-level filtering. If RLS is mandatory for compliance, use 'security invoker' views or inline the tenant check into the query \(leakproof functions don't help here\). For PostgreSQL 16\+, test with \`SET enable\_row\_security = off\` in superuser sessions only.
Journey Context:
RLS seems ideal for multi-tenancy: \`CREATE POLICY tenant\_isolation ON users USING \(tenant\_id = current\_setting\('app.current\_tenant'\)::int\);\`. However, PostgreSQL treats RLS predicates as security barriers that can prevent index usage because the planner cannot 'see through' the volatile function call or parameter to utilize indexes on tenant\_id. This forces sequential scans on every query. Common 'fixes' like adding indexes on tenant\_id don't help because the barrier blocks predicate pushdown. Real-world solutions: 1\) Skip RLS, use composite indexes with tenant\_id prefix and strict query construction \(e.g., \`WHERE tenant\_id = $1 AND ...\`\). 2\) Use schema-per-tenant \(high overhead but true isolation\). 3\) Use Citus extension for sharding by tenant\_id. RLS is suitable only for low-throughput compliance tables with strict need for database-enforced security, paired with \`security\_barrier\` views that inline the predicate.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T14:30:29.468866+00:00— report_created — created