From: | Charles Huang <charles(dot)huang(at)cim(dot)io> |
---|---|
To: | pgsql-performance(at)lists(dot)postgresql(dot)org |
Subject: | RLS not using index scan but seq scan when condition gets a bit complicated |
Date: | 2022-02-28 23:33:54 |
Message-ID: | E017F97F-F4A9-4231-828B-0BED73F9381B@cim.io |
Views: | Whole Thread | Raw Message | Download mbox | Resend email |
Thread: | |
Lists: | pgsql-performance |
Hi Postgres community,
We are experiencing some performance issues when RLS is enabled for large tables. With simplified example:
We have a table:
CREATE TABLE emp.employees (
employee_id INTEGER PRIMARY KEY,
-- companies table are defined in a different schema, not accessible to emp service
company_id INTEGER NOT NULL,
employee_name TEXT NOT NULL
);
Index for employees table:
CREATE INDEX employees_company_id_idx ON emp.employees (company_id);
And for the table we have RLS select policy:
CREATE POLICY employee_select_policy ON emp.employees FOR SELECT
USING (
company_id = ANY(coalesce(string_to_array(current_setting('emp.authorized_read_company_ids', TRUE), ',')::INTEGER[], ARRAY []::INTEGER[]))
);
When a very simple query is executed, for instance:
SET emp.authorized_read_company_ids = '1, 2, 3, ..., 200';
SELECT count(*) FROM emp.employees WHERE TRUE; -- 68091 rows
The query plan for this query reads:
Aggregate (cost=1096.02..1096.03 rows=1 width=8) (actual time=8.740..8.740 rows=1 loops=1)
Output: count(*)
Buffers: shared hit=778
-> Index Only Scan using employees_company_id_idx on emp.employees (cost=0.35..970.78 rows=50099 width=0) (actual time=0.124..4.976 rows=49953 loops=1)
Output: company_id
Index Cond: (employees.company_id = ANY (COALESCE((string_to_array(current_setting('emp.authorized_read_company_ids'::text, true), ','::text))::integer[], '{}'::integer[])))
Heap Fetches: 297
Buffers: shared hit=778
Planning:
Buffers: shared hit=12
Planning Time: 0.824 ms
Execution Time: 8.768 ms
The problem rises when we make the RLS select policy condition a bit more complicated by adding admin checks inside RLS select policy:
CREATE POLICY employee_select_policy ON emp.employees FOR SELECT
USING (
coalesce(nullif(current_setting('emp.is_admin', TRUE), ''), 'false')::BOOLEAN
OR company_id = ANY(coalesce(string_to_array(current_setting('emp.authorized_read_company_ids', TRUE), ',')::INTEGER[], ARRAY []::INTEGER[]))
);
When the same simple query is executed:
SET emp.is_admin = TRUE;
SET emp.authorized_read_company_ids = '1, 2, 3, ..., 200';
SELECT count(*) FROM emp.employees WHERE TRUE; -- 68091 rows
The query plan now reads:
Aggregate (cost=6238.51..6238.52 rows=1 width=8) (actual time=2156.271..2156.272 rows=1 loops=1)
Output: count(*)
Buffers: shared hit=367
-> Index Only Scan using employees_company_id_idx on emp.employees (cost=0.29..6099.16 rows=55740 width=0) (actual time=0.065..2151.939 rows=49953 loops=1)
Output: company_id
Filter: ((COALESCE(NULLIF(current_setting('emp.is_admin'::text, true), ''::text), 'false'::text))::boolean OR (employees.company_id = ANY (COALESCE((string_to_array(current_setting('emp.authorized_read_company_ids'::text, true), ','::text))::integer[], '{}'::integer[]))))
Rows Removed by Filter: 11430
Heap Fetches: 392
Buffers: shared hit=367
Planning Time: 0.744 ms
Execution Time: 2156.302 ms
We can see the performance has deteriorated horribly because the RLS is not using index any more for the company ids, the RLS scan happens for every single row in the result set against every single company id in the db context.
With the size of table and the number of company ids inside the db context growing, the execution time becomes longer and longer.
To summarise: We would like to have admin users run without any RLS restrictions, and normal users to have RLS enforced using an index based on company_ids. Unfortunately, we cannot have queries executed by admin users connect to the database as a different database user.
Is there anything you could suggest?
Thanks,
Charles
From | Date | Subject | |
---|---|---|---|
Next Message | Mladen Gogala | 2022-03-01 03:44:09 | Re: Never Ending query in PostgreSQL |
Previous Message | Justin Pryzby | 2022-02-28 22:05:03 | Re: An I/O error occurred while sending to the backend (PG 13.4) |