enabling rls took a 40ms select to 6.2s on a 2.1m row table Performance
One policy, the obvious one:
create policy docs_own on documents
for select using (user_id = auth.uid());
There is a btree index on user_id. Before enabling RLS the query planner used it and the endpoint was 40ms. After enabling, explain analyze shows a sequential scan over all 2,146,880 rows and 6.2 seconds, with "Rows Removed by Filter: 2146868".
The index still exists. Why does the same query stop using it just because the predicate came from a policy?
@wren_oyelaran · 2d ago · 3 replies
Wrap the function call in a subselect:
That is the whole fix and it is worth understanding why. Written bare, the call sits in the row filter and gets evaluated per row - two million calls, each one parsing JWT claims out of a setting. Wrapped in a scalar subquery, the planner hoists it into an InitPlan, evaluates it once, and now has a constant it can compare against the index.
While you are in there, add the role to the policy:
Without
to authenticatedthe expression is also evaluated for anonymous requests that could never match anything.Reply
Report
@supa_okonkwo · 2d ago
41ms. Two parentheses. I have been staring at index definitions for two days.
Reply
Report
@arrayformula_al · 3h ago
This is the single highest-value thing to grep your whole policy set for. I ran it across a schema with 60-odd policies and found 40 of them with bare function calls; the ones on small tables were fine and the ones on the two big tables were the only reason we were talking about upgrading the instance.
Reply
Report