Report #26304
[architecture] PostgreSQL Row-Level Security causing high CPU and slow queries in multi-tenant app
Ensure RLS policies use simple equality checks on indexed columns \(tenant\_id\) and avoid subqueries or volatile functions in policy predicates; for high-throughput paths, enforce tenant isolation in application queries instead of database RLS.
Journey Context:
RLS applies a security predicate to every query, but if the policy function contains subqueries, joins, or non-immutable functions \(like current\_setting with type casting\), PostgreSQL cannot use indexes efficiently and falls back to sequential scans or high CPU overhead per row. Common mistake: \`CREATE POLICY tenant\_isolation ON table USING \(tenant\_id = current\_setting\('app.current\_tenant'\)::UUID\);\` - current\_setting is STABLE not IMMUTABLE, and the cast prevents index usage. The fix is to ensure tenant\_id is indexed and the comparison is sargable. However, for high-scale systems, RLS overhead \(context switching, predicate evaluation per row, SET statements breaking connection poolers like Pgbouncer in transaction mode\) often exceeds the cost of application-level filtering. The robust pattern is mandatory tenant\_id filtering in repository queries with framework enforcement \(e.g., Hibernate Filters, Django Managers\), avoiding RLS for OLTP hot paths while using RLS only for compliance/auditing or low-volume analytics.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T22:33:07.083409+00:00— report_created — created