How would you speed up a Postgres query that's trying to filter on a date column between a start and end date?
I'm running a query like:
SELECT * FROM record WHERE tag_id IN (1,2,3) AND person_id = 1 AND created >= '2022-1-1' AND created < '2022-6-1'
ORDER BY priority DESC LIMIT 100;
on a table with millions of rows. However, only a few thousand rows should apply to my query, and I have a couple indexes that should cover the criteria exactly, like:
CREATE INDEX record_tag_priority_person_index
ON public.record USING btree
(tag_id ASC NULLS LAST, priority DESC NULLS LAST, person_id ASC NULLS LAST)
WHERE (tag_id = ANY (ARRAY[1, 2, 3])) AND person_id = 1;
CREATE INDEX record_created_index
ON public.record USING btree
(created ASC NULLS LAST);
Yet even with these indexes the query still takes ~18 minutes to run.
If I run an EXPLAIN
on my query, it shows:
"Limit (cost=155990.12..155990.37 rows=100 width=165) (actual time=1104683.783..1104683.799 rows=100 loops=1)"
" -> Sort (cost=155990.12..156078.05 rows=35170 width=165) (actual time=1104683.782..1104683.789 rows=100 loops=1)"
" Sort Key: priority DESC"
" Sort Method: top-N heapsort Memory: 58kB"
" -> Bitmap Heap Scan on record (cost=27359.52..154645.95 rows=35170 width=165) (actual time=556.641..1104569.771 rows=32804 loops=1)"
" Recheck Cond: ((created >= '2022-01-01 04:00:00+00'::timestamp with time zone) AND (created < '2022-6-1 04:00:00+00'::timestamp with time zone) AND (tag_id = ANY ('{1,2,3}'::integer[])) AND (person_id = 1))"
" Rows Removed by Index Recheck: 1103447"
" Heap Blocks: exact=35800 lossy=99400"
" -> BitmapAnd (cost=27359.47..27359.47 rows=35170 width=0) (actual time=547.819..547.821 rows=0 loops=1)"
" -> Bitmap Index Scan on record_created_index (cost=0.00..8666.93 rows=409449 width=0) (actual time=244.146..244.146 rows=309261 loops=1)"
" Index Cond: ((created >= '2022-01-01 04:00:00+00'::timestamp with time zone) AND (created < '2022-6-1 04:00:00+00'::timestamp with time zone))"
" -> Bitmap Index Scan on record_tag_priority_person_index (cost=0.00..18674.71 rows=2043655 width=0) (actual time=293.201..293.202 rows=2029783 loops=1)"
"Planning Time: 118.456 ms"
"Execution Time: 1104683.854 ms"
So it's using both of my indexes, but it's still taking forever to find the first 100 results.
How do I speed this up? Are my indexes inefficient?
I tried combining the two indexes into one partial index like:
CREATE INDEX record_tag_priority_person_created_index
ON public.record USING btree
(tag_id ASC NULLS LAST, priority DESC NULLS LAST, person_id ASC NULLS LAST, created DESC)
WHERE (tag_id = ANY (ARRAY[1, 2, 3])) AND person_id = 1;
but the planner doesn't pick it up and continues to use the two separate indexes.