Report #13497
[architecture] PostgreSQL Row-Level Security \(RLS\) causing full table scans or high CPU
Explicitly include tenant filters in application queries alongside RLS, and design RLS policies to use immutable function calls or direct column comparisons that match index prefixes. Avoid subqueries, joins, or volatile functions inside policy definitions.
Journey Context:
RLS is often marketed as the solution for multi-tenant data isolation, but naive implementation kills performance. If the RLS policy uses a subquery \(e.g., USING \(tenant\_id = \(SELECT current\_setting\('app.current\_tenant'\)::int\)\)\), PostgreSQL cannot push the predicate into the index scan, resulting in sequential scans on every query. The optimizer treats RLS predicates as non-indexable barriers when they involve volatile functions or subqueries. The fix: set the tenant context using a configuration parameter, but write the policy as a direct comparison USING \(tenant\_id = current\_setting\('app.current\_tenant'\)::int\) \(marking the casting function as IMMUTABLE\). Crucially, still include AND tenant\_id = ? in the application query; this seems redundant with RLS but allows the planner to choose the index before applying the RLS barrier. Without the explicit filter, the planner may choose a sequential scan assuming RLS will filter later, or may apply the RLS predicate as a filter after scanning.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T18:51:41.434247+00:00— report_created — created