次のようなクエリがあります。
SELECT post.id, post.author_id, post.published_at, post.content
FROM post
WHERE post.group_id = 1
ORDER BY post.published_at DESC, post.id
LIMIT 5;
このクエリには(group_id, published_at DESC, id)
のインデックスがあり、行レベルセキュリティ(RLS)ポリシーが使用されていない場合にこのクエリプランが提供されます。
Limit (cost=0.14..1.12 rows=5 width=143)
-> Index Scan using post_published_at on post (cost=0.14..15.86 rows=80 width=143)
Index Cond: (group_id = 1)
次に、このポリシーを追加します。
CREATE POLICY select_member_of ON post FOR SELECT USING
(EXISTS (SELECT 1
FROM group_member
WHERE group_member.account_id = current_setting('current_account_id', false)::INT AND
group_member.group_id = post.group_id));
group_member.account_id
テーブルのgroup_member.group_id
とgroup_member
に複合主キーがあります。
group_member
とgroup_member.account_id
の両方が定数値に設定されるため、Postgresがこのクエリをgroup_member.group_id
のインデックスのみのスキャンとして計画することを期待しています。上記のSELECT
クエリのgroup_member.group_id
条件のため、WHERE post.group_id = 1
は定数でなければなりません。
実際、RLSポリシーを次のようなクエリにインライン化すると、これが発生しているように見えます。
SELECT id, author_id, published_at, content
FROM post
WHERE group_id = 1 AND
(EXISTS (SELECT 1
FROM group_member
WHERE group_member.account_id = current_setting('current_account_id', false)::INT AND
group_member.group_id = post.group_id))
ORDER BY published_at DESC, id
LIMIT 5;
クエリプランを取得します。
Limit (cost=0.30..1.85 rows=5 width=143)
-> Nested Loop Semi Join (cost=0.30..25.04 rows=80 width=143)
-> Index Scan using post_published_at on post (cost=0.14..15.86 rows=80 width=147)
Index Cond: (group_id = 1)
-> Materialize (cost=0.16..8.19 rows=1 width=4)
-> Index Only Scan using group_member_pkey on group_member (cost=0.16..8.18 rows=1 width=4)
Index Cond: ((account_id = (current_setting('current_account_id'::text, false))::integer) AND (group_id = 1))
それは私が探していたものです。ただし、実際のRLSポリシーを使用してクエリを実行すると、クエリプランは次のようになります。
Limit (cost=23.08..23.10 rows=5 width=143)
-> Sort (cost=23.08..23.28 rows=80 width=143)
Sort Key: post.published_at DESC, post.id
-> Subquery Scan on post (cost=8.92..21.75 rows=80 width=143)
-> Nested Loop Semi Join (cost=8.92..20.95 rows=80 width=147)
-> Bitmap Heap Scan on post post_1 (cost=8.76..11.76 rows=80 width=147)
Recheck Cond: (group_id = 1)
-> Bitmap Index Scan on post_published_at (cost=0.00..8.74 rows=80 width=0)
Index Cond: (group_id = 1)
-> Materialize (cost=0.16..8.20 rows=1 width=4)
-> Subquery Scan on group_member (cost=0.16..8.19 rows=1 width=4)
-> Index Only Scan using group_member_pkey on group_member group_member_1 (cost=0.16..8.18 rows=1 width=8)
Index Cond: ((account_id = (current_setting('current_account_id'::text, false))::integer) AND (group_id = 1))
それはかなり悪いです。
これは予想される動作ですか? RLSポリシーをインライン化したバージョンと同じクエリプランを取得する方法はありますか?
サンプルデータがないと、正確なシナリオを再現することは困難です。
政策の表現はできるだけシンプルにすべきだと思います。あなたの場合、それは:
CREATE POLICY select_member_of ON post FOR SELECT USING (group_id IN (SELECT group_id
FROM group_member
WHERE account_id = current_setting('current_account_id', false)::INT));