Using ORDER BY/LIMIT with a security barrier view can perform significantly slower than its non-barrier counterpart.
The following shows the issue on postgres 16.2.
-- setup
create table foo (id int, other int);
insert into foo (id, other) select s, s % 1e4 from generate_series(1, 1e6) s;
create index on foo using btree (id);
create index on foo using btree (other);
analyze foo;
create function slow()
returns int
language sql
stable leakproof
as $$
select pg_sleep(0.001);
select 1;
$$;
create view nobarrier as select id, other, slow() from foo;
create view barrier with (security_barrier=true) as select id, other, slow() from foo;
-- identical plans when limit is omitted.
explain analyze select * from nobarrier where other = 42 order by id;
explain analyze select * from barrier where other = 42 order by id;
-- QUERY PLAN
-- --------------------------------------------------------------------------------------------------------------------------------
-- Sort (cost=386.35..386.60 rows=100 width=12) (actual time=209.675..209.678 rows=100 loops=1)
-- Sort Key: foo.id
-- Sort Method: quicksort Memory: 28kB
-- -> Bitmap Heap Scan on foo (cost=5.20..383.03 rows=100 width=12) (actual time=2.246..209.607 rows=100 loops=1)
-- Recheck Cond: (other = 42)
-- Heap Blocks: exact=100
-- -> Bitmap Index Scan on foo_other_idx (cost=0.00..5.17 rows=100 width=0) (actual time=0.013..0.013 rows=100 loops=1)
-- Index Cond: (other = 42)
-- Planning Time: 0.073 ms
-- Execution Time: 209.697 ms
-- different plans when limit is specified.
explain analyze select * from nobarrier where other = 42 order by id limit 10; -- fast
-- QUERY PLAN
-- --------------------------------------------------------------------------------------------------------------------------------------------
-- Limit (cost=360.19..362.81 rows=10 width=12) (actual time=2.281..18.965 rows=10 loops=1)
-- -> Result (cost=360.19..386.44 rows=100 width=12) (actual time=2.279..18.959 rows=10 loops=1)
-- -> Sort (cost=360.19..360.44 rows=100 width=8) (actual time=0.143..0.148 rows=10 loops=1)
-- Sort Key: foo.id
-- Sort Method: top-N heapsort Memory: 25kB
-- -> Bitmap Heap Scan on foo (cost=5.20..358.03 rows=100 width=8) (actual time=0.033..0.134 rows=100 loops=1)
-- Recheck Cond: (other = 42)
-- Heap Blocks: exact=100
-- -> Bitmap Index Scan on foo_other_idx (cost=0.00..5.17 rows=100 width=0) (actual time=0.019..0.021 rows=100 loops=1)
-- Index Cond: (other = 42)
-- Planning Time: 0.120 ms
-- Execution Time: 18.991 ms
explain analyze select * from barrier where other = 42 order by id limit 10; -- slow
-- QUERY PLAN
-- --------------------------------------------------------------------------------------------------------------------------------------
-- Limit (cost=385.19..385.21 rows=10 width=12) (actual time=199.314..199.317 rows=10 loops=1)
-- -> Sort (cost=385.19..385.44 rows=100 width=12) (actual time=199.311..199.313 rows=10 loops=1)
-- Sort Key: foo.id
-- Sort Method: top-N heapsort Memory: 25kB
-- -> Bitmap Heap Scan on foo (cost=5.20..383.03 rows=100 width=12) (actual time=2.150..199.223 rows=100 loops=1)
-- Recheck Cond: (other = 42)
-- Heap Blocks: exact=100
-- -> Bitmap Index Scan on foo_other_idx (cost=0.00..5.17 rows=100 width=0) (actual time=0.014..0.014 rows=100 loops=1)
-- Index Cond: (other = 42)
-- Planning Time: 0.075 ms
-- Execution Time: 199.335 msIt seems as though the security barrier materializes the entire resultset before applying the limit, whereas the nonbarrier view can apply the limit earlier and only materialize the final 10 records.
This issue can be seen on telemedlive after login with the following query.
bennie.debtor is the debtor resource with barrier added back.
The query suddenly becomes fast when any of these changes are made:
- Remove the line
patients,. - Change
bennie.debtortoapi.debtor(remove the barrier). - Remove the
ORDER BY. When theLIMITis removed, the barrier/nobarrier versions of the query are equally slow.
EXPLAIN ANALYZE
SELECT
patients,
uid
FROM bennie.debtor
WHERE uid = ANY (array(SELECT * FROM generate_series(1, 50000, 10)))
ORDER BY id
LIMIT 10;