perf(rls): wrap auth.uid() in the account policies so it evaluates once per statement - #106
Open
dmitrymaranik wants to merge 1 commit into
Open
perf(rls): wrap auth.uid() in the account policies so it evaluates once per statement#106dmitrymaranik wants to merge 1 commit into
dmitrymaranik wants to merge 1 commit into
Conversation
…er statement (InitPlan)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two of the account RLS policies in
supabase/migrations/20240414161947_basejump-accounts.sqlcallauth.uid()directly in theirUSINGclause:"users can view their own account_users"→user_id = auth.uid()"Accounts are viewable by primary owner"→primary_owner_user_id = auth.uid()Left unwrapped, Postgres re-evaluates
auth.uid()once per row scanned — it can't tell the call is constant across the statement — on two of the hottest read paths (account_userandaccounts).Fix
Wrap the call in a scalar sub-select so the planner hoists it to a one-time InitPlan (once per statement instead of once per row):
auth.uid()is constant within a statement, so the value is identical — same rows, fewer calls. This is the pattern Supabase documents forauth.*()in policies, and since Basejump is a widely-forked template, every project that scaffolds from it inherits the fix.Scope / what's deliberately not changed
Only the two directly row-independent
auth.uid()calls are wrapped. The other policies gate onbasejump.has_role_on_account(account_id, …), which takes the row'saccount_idas an argument — that's genuinely row-dependent and must be evaluated per row, so wrapping it would be incorrect. Left untouched.Surfaced by pgrls (
PERF001), an open-source Postgres RLS linter / verifier. I couldn't run the Supabase test suite locally, but the change is value-preserving (a pure planner hint).