TL;DR
-- 遅い: auth.uid() が行ごとに呼ばれる
using ( auth.uid() = user_id )
-- 速い: 1 ステートメントに 1 回だけ評価される
using ( (select auth.uid()) = user_id )
これだけで、Supabase 公式
はじめに
Supabase の Row Level Security (RLS) は
マッチングprofiles テーブルがauth.uid() を
主要なto authenticated の
なぜ遅いのか
典型的な「自分の
create policy "users can read own profile"
on profiles for select
using ( auth.uid() = user_id );
auth.uid() は LANGUAGE sql の STABLE 関数で、current_setting('request.jwt.claim.sub') をsub を
- 1 万件の
profilesでselect * from profilesを投げると、 auth.uid() を 1 万回呼ぶ - 100 件取りたいだけでも、
ポリシーは 全行に 対して 評価される(フィルタ 前)
これが「RLS にしてから
修正 1: auth.uid() を SELECT で包む
修正版は
create policy "users can read own profile"
on profiles for select
using ( (select auth.uid()) = user_id );
(select auth.uid()) とinitPlan として
公式 docs (Supabase — RLS performance) の
| 書き方 | 実行時間 |
|---|---|
auth.uid() = user_id | 179 ms |
(select auth.uid()) = user_id | 9 ms |
約 95% 改善。security definer な
Wrapping the function causes an
initPlanto be run by the Postgres optimizer, which allows it to "cache" the results per-statement, rather than calling the function on each row.(訳: 関数を SELECT で
包むと Postgres オプティマイザが initPlanを生成し、 行ごとに 関数を 呼ぶ代わりに ステートメント 単位で 結果を キャッシュできる)
修正 2: TO authenticated でロールを絞る
ログインto authenticated を
create policy "users can read own profile"
on profiles for select
to authenticated -- これを追加
using ( (select auth.uid()) = user_id );
to authenticated を
逆に(select auth.uid()) = user_id が
既存ポリシーを書き換えるときの注意
運用中の
1. マイグレーションは drop + create
Postgres の create policy ... if not exists は
drop policy if exists "users can read own profile" on profiles;
create policy "users can read own profile"
on profiles for select
to authenticated
using ( (select auth.uid()) = user_id );
2. 全テーブル分のポリシーを棚卸す
pg_policies ビューで
select schemaname, tablename, policyname, qual
from pg_policies
where qual like '%auth.uid()%'
and qual not like '%(select auth.uid())%';
auth.uid() を
3. user_id カラムに index を貼る
これはuser_id)には B-tree index が
create index if not exists profiles_user_id_idx on profiles (user_id);
まとめ
- RLS で
auth.uid() = user_idと書いていたら、 (select auth.uid()) = user_idに書き換えるだけで、 条件次第で 1 桁速くなる - ログイン
必須なら to authenticatedも付ける(更に 1 桁) - 既存
ポリシーは pg_policiesで棚卸して 一気に 直す - 比較
カラムには index を 必ず貼る
公式の RLS パフォーマンス