Bad query plan on different boolean values - postgresql
I have a table which is used as a job queue. New rows are continuously being inserted in the table and the fetched rows are updated. There are JOINs in the query and Postgres uses Nested Loop join for it. Around 400 rows are picked up from this table at a time. reminders table has a column named active. If value of active is false or the condition is not present, only close to 400 rows are fetched in the outer loop else Postgres fetches more than 100K rows.
Any help in solving this is appreciated.
Indexes on tables:
reminders: (id, created_at), (namespace)
reminder_items: (queued_at, process_status), (id, created_at), (next_run_at, created_at, active) WHERE (queued_at IS NULL)
namespace: (name), (id), (active)
Query when reminders.active = false
EXPLAIN (ANALYZE TRUE, VERBOSE TRUE, COSTS TRUE, BUFFERS TRUE, TIMING TRUE) SELECT ri.*
FROM reminder_items ri
JOIN reminders r ON ri.reminder_id = r.id
JOIN namespaces ns ON r.namespace = ns.Name
AND ns.Active = TRUE
AND r.active = FALSE
AND r.deleted_at IS NULL
AND ri.next_run_at > 1664471640
AND ri.next_run_at < 1664475260
AND ri.active = TRUE
AND ri.queued_at IS NULL
AND ri.created_at >= 1656633600
AND ri.created_at < 1664475260
AND r.created_at >= 1656633600
AND r.created_at <= 1664475260
LIMIT 400;
Query plan
QUERY PLAN
Limit (cost=1001.42..6144.95 rows=400 width=163) (actual time=4.076..9.737 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count"
Buffers: shared hit=6312 read=15
I/O Timings: read=3.038
-> Nested Loop (cost=1001.42..427914.42 rows=33200 width=163) (actual time=4.075..9.692 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count"
Inner Unique: true
Buffers: shared hit=6312 read=15
I/O Timings: read=3.038
-> Gather (cost=1001.14..426454.35 rows=33200 width=184) (actual time=4.056..9.348 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count, r.namespace"
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=6279 read=15
I/O Timings: read=3.038
-> Nested Loop (cost=1.14..422134.35 rows=13833 width=184) (actual time=1.201..4.366 rows=134 loops=3)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count, r.namespace"
Buffers: shared hit=6279 read=15
I/O Timings: read=3.038
Worker 0: actual time=0.065..3.181 rows=135 loops=1
Buffers: shared hit=2070
Worker 1: actual time=0.098..3.237 rows=135 loops=1
Buffers: shared hit=2101
-> Parallel Append (cost=0.57..65882.19 rows=13834 width=163) (actual time=0.153..0.629 rows=136 loops=3)
Buffers: shared hit=664 read=6
I/O Timings: read=1.001
Worker 0: actual time=0.008..0.257 rows=135 loops=1
Buffers: shared hit=216
Worker 1: actual time=0.041..0.296 rows=135 loops=1
Buffers: shared hit=240
-> Parallel Index Scan using reminder_items_y2022m09_next_run_at_created_at_active_idx on public.reminder_items_y2022m09 ri_3 (cost=0.57..65579.34 rows=13786 width=163) (actual time=0.022..0.226 rows=123 loops=3)
" Output: ri_3.id, ri_3.reminder_id, ri_3.queued_at, ri_3.config_id, ri_3.active, ri_3.process_status, ri_3.next_run_at, ri_3.total_count, ri_3.""interval"", ri_3.sent_count, ri_3.last_sent, ri_3.channels, ri_3.created_at, ri_3.updated_at, ri_3.deleted_at, ri_3.retry_count, ri_3.bucket_configuration_count"
Index Cond: ((ri_3.next_run_at > 1664471640) AND (ri_3.next_run_at < 1664475260) AND (ri_3.created_at >= 1656633600) AND (ri_3.created_at < 1664475260) AND (ri_3.active = true))
Buffers: shared hit=609
Worker 0: actual time=0.015..0.225 rows=130 loops=1
Buffers: shared hit=210
Worker 1: actual time=0.040..0.280 rows=135 loops=1
Buffers: shared hit=240
-> Parallel Index Scan using reminder_items_y2022m08_next_run_at_created_at_active_idx on public.reminder_items_y2022m08 ri_2 (cost=0.57..225.08 rows=66 width=163) (actual time=0.012..0.040 rows=16 loops=2)
" Output: ri_2.id, ri_2.reminder_id, ri_2.queued_at, ri_2.config_id, ri_2.active, ri_2.process_status, ri_2.next_run_at, ri_2.total_count, ri_2.""interval"", ri_2.sent_count, ri_2.last_sent, ri_2.channels, ri_2.created_at, ri_2.updated_at, ri_2.deleted_at, ri_2.retry_count, ri_2.bucket_configuration_count"
Index Cond: ((ri_2.next_run_at > 1664471640) AND (ri_2.next_run_at < 1664475260) AND (ri_2.created_at >= 1656633600) AND (ri_2.created_at < 1664475260) AND (ri_2.active = true))
Buffers: shared hit=38
Worker 0: actual time=0.007..0.016 rows=5 loops=1
Buffers: shared hit=6
-> Parallel Index Scan using reminder_items_y2022m07_next_run_at_created_at_active_idx on public.reminder_items_y2022m07 ri_1 (cost=0.57..8.59 rows=1 width=163) (actual time=0.409..1.083 rows=6 loops=1)
" Output: ri_1.id, ri_1.reminder_id, ri_1.queued_at, ri_1.config_id, ri_1.active, ri_1.process_status, ri_1.next_run_at, ri_1.total_count, ri_1.""interval"", ri_1.sent_count, ri_1.last_sent, ri_1.channels, ri_1.created_at, ri_1.updated_at, ri_1.deleted_at, ri_1.retry_count, ri_1.bucket_configuration_count"
Index Cond: ((ri_1.next_run_at > 1664471640) AND (ri_1.next_run_at < 1664475260) AND (ri_1.created_at >= 1656633600) AND (ri_1.created_at < 1664475260) AND (ri_1.active = true))
Buffers: shared hit=17 read=6
I/O Timings: read=1.001
-> Append (cost=0.57..25.72 rows=3 width=36) (actual time=0.026..0.027 rows=1 loops=408)
Buffers: shared hit=5615 read=9
I/O Timings: read=2.037
Worker 0: actual time=0.020..0.021 rows=1 loops=135
Buffers: shared hit=1854
Worker 1: actual time=0.021..0.021 rows=1 loops=135
Buffers: shared hit=1861
-> Index Scan using reminders_y2022m07_pkey on public.reminders_y2022m07 r_1 (cost=0.57..8.57 rows=1 width=36) (actual time=0.011..0.011 rows=0 loops=408)
" Output: r_1.id, r_1.namespace"
Index Cond: ((r_1.id = ri.reminder_id) AND (r_1.created_at >= 1656633600) AND (r_1.created_at <= 1664475260))
Filter: ((NOT r_1.active) AND (r_1.deleted_at IS NULL))
Rows Removed by Filter: 0
Buffers: shared hit=1634 read=9
I/O Timings: read=2.037
Worker 0: actual time=0.005..0.005 rows=0 loops=135
Buffers: shared hit=544
Worker 1: actual time=0.005..0.005 rows=0 loops=135
Buffers: shared hit=541
-> Index Scan using reminders_y2022m08_pkey on public.reminders_y2022m08 r_2 (cost=0.57..8.57 rows=1 width=36) (actual time=0.006..0.006 rows=0 loops=408)
" Output: r_2.id, r_2.namespace"
Index Cond: ((r_2.id = ri.reminder_id) AND (r_2.created_at >= 1656633600) AND (r_2.created_at <= 1664475260))
Filter: ((NOT r_2.active) AND (r_2.deleted_at IS NULL))
Buffers: shared hit=1666
Worker 0: actual time=0.005..0.005 rows=0 loops=135
Buffers: shared hit=546
Worker 1: actual time=0.005..0.005 rows=0 loops=135
Buffers: shared hit=541
-> Index Scan using reminders_y2022m09_pkey on public.reminders_y2022m09 r_3 (cost=0.57..8.57 rows=1 width=36) (actual time=0.009..0.010 rows=1 loops=408)
" Output: r_3.id, r_3.namespace"
Index Cond: ((r_3.id = ri.reminder_id) AND (r_3.created_at >= 1656633600) AND (r_3.created_at <= 1664475260))
Filter: ((NOT r_3.active) AND (r_3.deleted_at IS NULL))
Buffers: shared hit=2315
Worker 0: actual time=0.009..0.010 rows=1 loops=135
Buffers: shared hit=764
Worker 1: actual time=0.009..0.010 rows=1 loops=135
Buffers: shared hit=779
-> Memoize (cost=0.28..6.54 rows=1 width=24) (actual time=0.000..0.000 rows=1 loops=400)
Output: ns.name
Cache Key: r.namespace
Cache Mode: logical
Hits: 389 Misses: 11 Evictions: 0 Overflows: 0 Memory Usage: 2kB
Buffers: shared hit=33
-> Index Scan using namespaces_name_index on public.namespaces ns (cost=0.27..6.53 rows=1 width=24) (actual time=0.004..0.004 rows=1 loops=11)
Output: ns.name
Index Cond: ((ns.name)::text = (r.namespace)::text)
Filter: ns.active
Buffers: shared hit=33
Query Identifier: -6499700351606025442
Planning:
Buffers: shared hit=1251
Planning Time: 5.957 ms
Execution Time: 9.889 ms
Query when reminders.active = true
EXPLAIN (ANALYZE TRUE, VERBOSE TRUE, COSTS TRUE, BUFFERS TRUE, TIMING TRUE) SELECT ri.*
FROM reminder_items ri
JOIN reminders r ON ri.reminder_id = r.id
JOIN namespaces ns ON r.namespace = ns.Name
AND ns.Active = TRUE
AND r.active = TRUE
AND r.deleted_at IS NULL
AND ri.next_run_at > 1664471917
AND ri.next_run_at < 1664475537
AND ri.active = TRUE
AND ri.queued_at IS NULL
AND ri.created_at >= 1656633600
AND ri.created_at < 1664475537
AND r.created_at >= 1656633600
AND r.created_at <= 1664475537
LIMIT 400;
Query plan
QUERY PLAN
Limit (cost=1001.42..6144.87 rows=400 width=163) (actual time=1.432..958.027 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count"
Buffers: shared hit=1812210 read=59 dirtied=224
I/O Timings: read=20.351
-> Nested Loop (cost=1001.42..427946.40 rows=33203 width=163) (actual time=1.431..957.977 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count"
Inner Unique: true
Buffers: shared hit=1812210 read=59 dirtied=224
I/O Timings: read=20.351
-> Gather (cost=1001.14..426486.25 rows=33203 width=184) (actual time=1.405..957.356 rows=402 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count, r.namespace"
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=1812086 read=59 dirtied=223
I/O Timings: read=20.351
-> Nested Loop (cost=1.14..422165.95 rows=13835 width=184) (actual time=614.043..947.957 rows=135 loops=3)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count, r.namespace"
Buffers: shared hit=1812086 read=59 dirtied=223
I/O Timings: read=20.351
Worker 0: actual time=918.780..937.869 rows=90 loops=1
Buffers: shared hit=579156 read=33 dirtied=56
I/O Timings: read=11.298
Worker 1: actual time=922.574..951.608 rows=133 loops=1
Buffers: shared hit=587055 read=7 dirtied=104
I/O Timings: read=3.086
-> Parallel Append (cost=0.57..65887.94 rows=13835 width=163) (actual time=0.025..69.957 rows=41234 loops=3)
Buffers: shared hit=187091 read=5 dirtied=7
I/O Timings: read=1.373
Worker 0: actual time=0.017..57.049 rows=39562 loops=1
Buffers: shared hit=59425
Worker 1: actual time=0.014..80.984 rows=40034 loops=1
Buffers: shared hit=60806 read=2 dirtied=1
I/O Timings: read=0.915
-> Parallel Index Scan using reminder_items_y2022m09_next_run_at_created_at_active_idx on public.reminder_items_y2022m09 ri_3 (cost=0.57..65585.09 rows=13787 width=163) (actual time=0.014..65.623 rows=41223 loops=3)
" Output: ri_3.id, ri_3.reminder_id, ri_3.queued_at, ri_3.config_id, ri_3.active, ri_3.process_status, ri_3.next_run_at, ri_3.total_count, ri_3.""interval"", ri_3.sent_count, ri_3.last_sent, ri_3.channels, ri_3.created_at, ri_3.updated_at, ri_3.deleted_at, ri_3.retry_count, ri_3.bucket_configuration_count"
Index Cond: ((ri_3.next_run_at > 1664471917) AND (ri_3.next_run_at < 1664475537) AND (ri_3.created_at >= 1656633600) AND (ri_3.created_at < 1664475537) AND (ri_3.active = true))
Buffers: shared hit=187041 read=3 dirtied=7
I/O Timings: read=1.344
Worker 0: actual time=0.017..53.025 rows=39562 loops=1
Buffers: shared hit=59425
Worker 1: actual time=0.013..76.860 rows=40034 loops=1
Buffers: shared hit=60806 read=2 dirtied=1
I/O Timings: read=0.915
-> Parallel Index Scan using reminder_items_y2022m08_next_run_at_created_at_active_idx on public.reminder_items_y2022m08 ri_2 (cost=0.57..225.08 rows=66 width=163) (actual time=0.014..0.068 rows=30 loops=1)
" Output: ri_2.id, ri_2.reminder_id, ri_2.queued_at, ri_2.config_id, ri_2.active, ri_2.process_status, ri_2.next_run_at, ri_2.total_count, ri_2.""interval"", ri_2.sent_count, ri_2.last_sent, ri_2.channels, ri_2.created_at, ri_2.updated_at, ri_2.deleted_at, ri_2.retry_count, ri_2.bucket_configuration_count"
Index Cond: ((ri_2.next_run_at > 1664471917) AND (ri_2.next_run_at < 1664475537) AND (ri_2.created_at >= 1656633600) AND (ri_2.created_at < 1664475537) AND (ri_2.active = true))
Buffers: shared hit=36
-> Parallel Index Scan using reminder_items_y2022m07_next_run_at_created_at_active_idx on public.reminder_items_y2022m07 ri_1 (cost=0.57..8.59 rows=1 width=163) (actual time=0.045..0.083 rows=3 loops=1)
" Output: ri_1.id, ri_1.reminder_id, ri_1.queued_at, ri_1.config_id, ri_1.active, ri_1.process_status, ri_1.next_run_at, ri_1.total_count, ri_1.""interval"", ri_1.sent_count, ri_1.last_sent, ri_1.channels, ri_1.created_at, ri_1.updated_at, ri_1.deleted_at, ri_1.retry_count, ri_1.bucket_configuration_count"
Index Cond: ((ri_1.next_run_at > 1664471917) AND (ri_1.next_run_at < 1664475537) AND (ri_1.created_at >= 1656633600) AND (ri_1.created_at < 1664475537) AND (ri_1.active = true))
Buffers: shared hit=14 read=2
I/O Timings: read=0.029
-> Append (cost=0.57..25.72 rows=3 width=36) (actual time=0.021..0.021 rows=0 loops=123701)
Buffers: shared hit=1624995 read=54 dirtied=216
I/O Timings: read=18.978
Worker 0: actual time=0.022..0.022 rows=0 loops=39562
Buffers: shared hit=519731 read=33 dirtied=56
I/O Timings: read=11.298
Worker 1: actual time=0.021..0.021 rows=0 loops=40034
Buffers: shared hit=526249 read=5 dirtied=103
I/O Timings: read=2.171
-> Index Scan using reminders_y2022m07_pkey on public.reminders_y2022m07 r_1 (cost=0.57..8.57 rows=1 width=36) (actual time=0.005..0.005 rows=0 loops=123701)
" Output: r_1.id, r_1.namespace"
Index Cond: ((r_1.id = ri.reminder_id) AND (r_1.created_at >= 1656633600) AND (r_1.created_at <= 1664475537))
Filter: (r_1.active AND (r_1.deleted_at IS NULL))
Buffers: shared hit=494810 read=5
I/O Timings: read=1.160
Worker 0: actual time=0.005..0.005 rows=0 loops=39562
Buffers: shared hit=158252
Worker 1: actual time=0.006..0.006 rows=0 loops=40034
Buffers: shared hit=160140
-> Index Scan using reminders_y2022m08_pkey on public.reminders_y2022m08 r_2 (cost=0.57..8.57 rows=1 width=36) (actual time=0.005..0.005 rows=0 loops=123701)
" Output: r_2.id, r_2.namespace"
Index Cond: ((r_2.id = ri.reminder_id) AND (r_2.created_at >= 1656633600) AND (r_2.created_at <= 1664475537))
Filter: (r_2.active AND (r_2.deleted_at IS NULL))
Rows Removed by Filter: 0
Buffers: shared hit=494836
Worker 0: actual time=0.005..0.005 rows=0 loops=39562
Buffers: shared hit=158249
Worker 1: actual time=0.005..0.005 rows=0 loops=40034
Buffers: shared hit=160137
-> Index Scan using reminders_y2022m09_pkey on public.reminders_y2022m09 r_3 (cost=0.57..8.57 rows=1 width=36) (actual time=0.010..0.010 rows=0 loops=123701)
" Output: r_3.id, r_3.namespace"
Index Cond: ((r_3.id = ri.reminder_id) AND (r_3.created_at >= 1656633600) AND (r_3.created_at <= 1664475537))
Filter: (r_3.active AND (r_3.deleted_at IS NULL))
Rows Removed by Filter: 1
Buffers: shared hit=635349 read=49 dirtied=216
I/O Timings: read=17.817
Worker 0: actual time=0.010..0.010 rows=0 loops=39562
Buffers: shared hit=203230 read=33 dirtied=56
I/O Timings: read=11.298
Worker 1: actual time=0.010..0.010 rows=0 loops=40034
Buffers: shared hit=205972 read=5 dirtied=103
I/O Timings: read=2.171
-> Memoize (cost=0.28..6.54 rows=1 width=24) (actual time=0.001..0.001 rows=1 loops=402)
Output: ns.name
Cache Key: r.namespace
Cache Mode: logical
Hits: 361 Misses: 41 Evictions: 0 Overflows: 0 Memory Usage: 6kB
Buffers: shared hit=124 dirtied=1
-> Index Scan using namespaces_name_index on public.namespaces ns (cost=0.27..6.53 rows=1 width=24) (actual time=0.005..0.005 rows=1 loops=41)
Output: ns.name
Index Cond: ((ns.name)::text = (r.namespace)::text)
Filter: ns.active
Rows Removed by Filter: 0
Buffers: shared hit=124 dirtied=1
Query Identifier: -6499700351606025442
Planning:
Buffers: shared hit=1251
Planning Time: 6.272 ms
Execution Time: 958.219 ms
Edit: Query plan after setting max_parallel_workers_per_gather = 0
QUERY PLAN
Limit (cost=1.42..11118.93 rows=400 width=163) (actual time=0.640..3188.959 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count"
Buffers: shared hit=2442983 read=107 dirtied=39
I/O Timings: read=14.735
-> Nested Loop (cost=1.42..921448.55 rows=33153 width=163) (actual time=0.639..3188.902 rows=400 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count"
Inner Unique: true
Buffers: shared hit=2442983 read=107 dirtied=39
I/O Timings: read=14.735
-> Nested Loop (cost=1.14..919989.66 rows=33153 width=184) (actual time=0.616..3188.279 rows=402 loops=1)
" Output: ri.id, ri.reminder_id, ri.queued_at, ri.config_id, ri.active, ri.process_status, ri.next_run_at, ri.total_count, ri.""interval"", ri.sent_count, ri.last_sent, ri.channels, ri.created_at, ri.updated_at, ri.deleted_at, ri.retry_count, ri.bucket_configuration_count, r.namespace"
Buffers: shared hit=2442868 read=107 dirtied=39
I/O Timings: read=14.735
-> Append (cost=0.57..66223.67 rows=33153 width=163) (actual time=0.078..232.043 rows=167795 loops=1)
Buffers: shared hit=235522 read=45 dirtied=20
I/O Timings: read=1.104
-> Index Scan using reminder_items_y2022m07_next_run_at_created_at_active_idx on public.reminder_items_y2022m07 ri_1 (cost=0.57..8.60 rows=1 width=163) (actual time=0.078..0.221 rows=16 loops=1)
" Output: ri_1.id, ri_1.reminder_id, ri_1.queued_at, ri_1.config_id, ri_1.active, ri_1.process_status, ri_1.next_run_at, ri_1.total_count, ri_1.""interval"", ri_1.sent_count, ri_1.last_sent, ri_1.channels, ri_1.created_at, ri_1.updated_at, ri_1.deleted_at, ri_1.retry_count, ri_1.bucket_configuration_count"
Index Cond: ((ri_1.next_run_at > 1664507457) AND (ri_1.next_run_at < 1664511077) AND (ri_1.created_at >= 1656633600) AND (ri_1.created_at < 1664511077) AND (ri_1.active = true))
Buffers: shared hit=24 read=11
I/O Timings: read=0.106
-> Index Scan using reminder_items_y2022m08_next_run_at_created_at_active_idx on public.reminder_items_y2022m08 ri_2 (cost=0.57..225.55 rows=113 width=163) (actual time=0.019..0.446 rows=2 loops=1)
" Output: ri_2.id, ri_2.reminder_id, ri_2.queued_at, ri_2.config_id, ri_2.active, ri_2.process_status, ri_2.next_run_at, ri_2.total_count, ri_2.""interval"", ri_2.sent_count, ri_2.last_sent, ri_2.channels, ri_2.created_at, ri_2.updated_at, ri_2.deleted_at, ri_2.retry_count, ri_2.bucket_configuration_count"
Index Cond: ((ri_2.next_run_at > 1664507457) AND (ri_2.next_run_at < 1664511077) AND (ri_2.created_at >= 1656633600) AND (ri_2.created_at < 1664511077) AND (ri_2.active = true))
Buffers: shared hit=5 read=1
I/O Timings: read=0.424
-> Index Scan using reminder_items_y2022m09_next_run_at_created_at_active_idx on public.reminder_items_y2022m09 ri_3 (cost=0.57..65823.76 rows=33039 width=163) (actual time=0.032..213.454 rows=167777 loops=1)
" Output: ri_3.id, ri_3.reminder_id, ri_3.queued_at, ri_3.config_id, ri_3.active, ri_3.process_status, ri_3.next_run_at, ri_3.total_count, ri_3.""interval"", ri_3.sent_count, ri_3.last_sent, ri_3.channels, ri_3.created_at, ri_3.updated_at, ri_3.deleted_at, ri_3.retry_count, ri_3.bucket_configuration_count"
Index Cond: ((ri_3.next_run_at > 1664507457) AND (ri_3.next_run_at < 1664511077) AND (ri_3.created_at >= 1656633600) AND (ri_3.created_at < 1664511077) AND (ri_3.active = true))
Buffers: shared hit=235493 read=33 dirtied=20
I/O Timings: read=0.575
-> Append (cost=0.57..25.72 rows=3 width=36) (actual time=0.017..0.017 rows=0 loops=167795)
Buffers: shared hit=2207346 read=62 dirtied=19
I/O Timings: read=13.631
-> Index Scan using reminders_y2022m07_pkey on public.reminders_y2022m07 r_1 (cost=0.57..8.57 rows=1 width=36) (actual time=0.004..0.004 rows=0 loops=167795)
" Output: r_1.id, r_1.namespace"
Index Cond: ((r_1.id = ri.reminder_id) AND (r_1.created_at >= 1656633600) AND (r_1.created_at <= 1664511077))
Filter: (r_1.active AND (r_1.deleted_at IS NULL))
Buffers: shared hit=671170 read=26
I/O Timings: read=5.753
-> Index Scan using reminders_y2022m08_pkey on public.reminders_y2022m08 r_2 (cost=0.57..8.57 rows=1 width=36) (actual time=0.004..0.004 rows=0 loops=167795)
" Output: r_2.id, r_2.namespace"
Index Cond: ((r_2.id = ri.reminder_id) AND (r_2.created_at >= 1656633600) AND (r_2.created_at <= 1664511077))
Filter: (r_2.active AND (r_2.deleted_at IS NULL))
Rows Removed by Filter: 0
Buffers: shared hit=671180 read=2
I/O Timings: read=0.662
-> Index Scan using reminders_y2022m09_pkey on public.reminders_y2022m09 r_3 (cost=0.57..8.57 rows=1 width=36) (actual time=0.008..0.008 rows=0 loops=167795)
" Output: r_3.id, r_3.namespace"
Index Cond: ((r_3.id = ri.reminder_id) AND (r_3.created_at >= 1656633600) AND (r_3.created_at <= 1664511077))
Filter: (r_3.active AND (r_3.deleted_at IS NULL))
Rows Removed by Filter: 1
Buffers: shared hit=864996 read=34 dirtied=19
I/O Timings: read=7.216
-> Memoize (cost=0.28..6.54 rows=1 width=24) (actual time=0.001..0.001 rows=1 loops=402)
Output: ns.name
Cache Key: r.namespace
Cache Mode: logical
Hits: 364 Misses: 38 Evictions: 0 Overflows: 0 Memory Usage: 6kB
Buffers: shared hit=115
-> Index Scan using namespaces_name_index on public.namespaces ns (cost=0.27..6.53 rows=1 width=24) (actual time=0.005..0.005 rows=1 loops=38)
Output: ns.name
Index Cond: ((ns.name)::text = (r.namespace)::text)
Filter: ns.active
Rows Removed by Filter: 0
Buffers: shared hit=115
Query Identifier: -6499700351606025442
Planning:
Buffers: shared hit=1251
Planning Time: 6.039 ms
Execution Time: 3189.116 ms
Your query has these ON filters on reminder_items.
... AND ri.next_run_at > 1664471640
AND ri.next_run_at < 1664475260
AND ri.active = TRUE
AND ri.queued_at IS NULL
AND ri.created_at >= 1656633600
AND ri.created_at < 1664475260 ...
Due to the way BTREE indexes work, you should create a compound index on that table starting with the columns where you match for equality, then a single column where you match on a range. This might work.
CREATE INDEX active_queued_created ON reminder_items
(active, queued_at, next_run);
The database query engine can random-access that BTREE index to the first eligible row, then scan it sequentially to the last row. That helps performance.
Now, your ON filters do range matches on two columns, created_at and next_run. BTREE indexes can only exploit one range match, not two. Therefore you need to choose whether to put next_run or created_at in the index. You should choose the most selective of those two columns. I chose next_run because your range for that is narrower than it is for created_at.
You should try adding the index I suggest, then examine your EXPLAIN output again to figure out what indexes might help on other tables.
And, by the way, when you use LIMIT without ORDER BY, you're telling the query engine "give me whatever 400 rows you want" -- the choice of rows won't be deterministic.
The reason for the problem is pretty clear, but the solution is not.
It is scanning the reminder_items using the index, in that index's order. When doing it, the first rows it encounter all find join partners where reminder.active is false, so the LIMIT kicks and it gets to stop early. But few of those early rows find join partners meeting reminder.active, so when testing for that condition it has to keep going for a long time before it catches its LIMIT. This could be because reminder.active=true is very rare, or it could be that they are common but are all bunched up at one end of the next_run_at "value space".
One thing you can try is adding an ORDER BY next_run_at DESC to the query. This might cause it scan that index in the other direction, where it might encounter its LIMIT sooner. But you mentioned this being a queue. If what it does with the rows it finds is to toggle off the corresponding value of reminder.active, then this solution wouldn't last for long, as then it would soon become depleted of matching rows when starting from that direction, too.
Another thing to try would be create index on reminder (active, created_at). That might cause the planner to invert the nested loop, so it scans reminders as outer and reminder_items on the inner side. This could be particularly effective if reminder.active=true is rare, as this proposed index would provide an effective way to efficiently skip the inactive ones.
A more general solution might be to increase the LIMIT. Skipping over the filtered out values would still be a lot of work, but that work would be amortized over a larger number of fetched rows. What is driving the current choice of LIMIT?
Related
Postgresql random slow query
We are having a problem with a specific query that sometimes takes over 620.000 ms on execute, but this is random because if we execute the same query again, it takes aprox. 79ms This is the query: INSERT INTO product_store(id_store, cod_product, account_id, stock, product,active) SELECT s.id_store, t.cod_product, t.account_id, 0, i.product, i.active FROM tmp_import_product t, store s, product i WHERE t.id_process_import_product = 99988 AND s.account_id = t.account_id AND s.active = 'S' AND i.account_id = t.account_id AND i.cod_product = t.cod_product AND i.manage_stock = 'S' AND i.variant_stock = 'N' AND t.existent = 'S' AND t.error = 'N' AND NOT EXISTS(SELECT 1 FROM product_store i WHERE i.cod_product = t.cod_product AND i.account_id = t.account_id AND i.id_store = s.id_store) And this is the plan Insert on product_store (cost=1.57..296.01 rows=1 width=733) (actual rows=0 loops=1) Buffers: shared hit=532388600 read=21556 dirtied=186 -> Nested Loop (cost=1.57..296.01 rows=1 width=733) (actual rows=0 loops=1) Buffers: shared hit=532388600 read=21556 dirtied=186 -> Nested Loop (cost=1.14..291.09 rows=1 width=76) (actual rows=22200 loops=1) Join Filter: (t.account_id = s.account_id) Buffers: shared hit=74893 -> Nested Loop (cost=0.85..290.40 rows=2 width=76) (actual rows=7400 loops=1) Buffers: shared hit=37893 -> Index Scan using ix_tmp_import_product_2 on tmp_import_product t (cost=0.42..120.32 rows=64 width=21) (actual rows=7400 loops=1) Index Cond: (t.id_process_import_product = '96680'::bigint) Filter: ((t.existent = 'S'::bpchar) AND (t.error = 'N'::bpchar)) Rows Removed by Filter: 102 Buffers: shared hit=3515 -> Index Scan using pk_product on product i (cost=0.43..2.66 rows=1 width=64) (actual rows=1 loops=7400) Index Cond: (((i.cod_product)::text = t.cod_product) AND (i.account_id = t.account_id)) Filter: ((i.manage_stock = 'S'::bpchar) AND (i.variant_stock = 'N'::bpchar)) Rows Removed by Filter: 0 Buffers: shared hit=34291 -> Index Scan using store_account_id_active_index on store s (cost=0.28..0.32 rows=2 width=16) (actual rows=3 loops=7400) Index Cond: ((s.account_id = i.account_id) AND (s.active = 'S'::bpchar)) Buffers: shared hit=37000 -> Index Scan using idx_product_store_account_id_id_store on product_store i_1 (cost=0.43..4.90 rows=1 width=24) (actual rows=1 loops=22200) Index Cond: ((i_1.id_store = s.id_store) AND (i_1.account_id = t.account_id)) Filter: ((i_1.cod_product)::text = t.cod_product) Rows Removed by Filter: 141420 Buffers: shared hit=532306240 read=21556 dirtied=186 Trigger t_insert_product_stock: time=0.000 calls=1 I think that because tmp_import_product has a bad estimation, it affects the next joins. How can I check if the stats of the table are updated? (already check last autovacuum and it is running) How can I improve this query? Could it be something else?
Postgresql shows slow query but low execution time in analyzed output
Postgresql Version: 13.6 Server size: 2cx4g Logs of Postgresql shows duration 6215.926ms, but explain shows that the execution time and planning time are less than 200ms Is slow reading speed of the client a cause of the problem? 2022-04-18 16:28:20.491 CST [1159] LOG: duration: 6215.926 ms statement: EXPLAIN (ANALYZE ON, BUFFERS ON) select 1 from information_schema.tables where table_schema = 'pg_catalog' and table_name = 'pg_file_settings' Nested Loop (cost=0.68..17.88 rows=1 width=4) (actual time=0.126..0.128 rows=1 loops=1) Join Filter: (c.relnamespace = nc.oid) Buffers: shared hit=8 read=2 I/O Timings: read=0.027 -> Nested Loop Left Join (cost=0.68..16.81 rows=1 width=4) (actual time=0.085..0.087 rows=1 loops=1) Buffers: shared hit=3 read=2 I/O Timings: read=0.027 -> Index Scan using pg_class_relname_nsp_index on pg_class c (cost=0.27..8.33 rows=1 width=8) (actual time=0.065..0.066 rows=1 loops=1) Index Cond: (relname = 'pg_file_settings'::name) Filter: ((relkind = ANY ('{r,v,f,p}'::"char"[])) AND (pg_has_role(relowner, 'USAGE'::text) OR has_table_privilege(oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text))) Buffers: shared hit=1 read=2 I/O Timings: read=0.027 -> Nested Loop (cost=0.40..8.47 rows=1 width=4) (actual time=0.017..0.018 rows=0 loops=1) Buffers: shared hit=2 -> Index Scan using pg_type_oid_index on pg_type t (cost=0.27..8.29 rows=1 width=8) (actual time=0.016..0.016 rows=0 loops=1) Index Cond: (oid = c.reloftype) Buffers: shared hit=2 -> Index Only Scan using pg_namespace_oid_index on pg_namespace nt (cost=0.13..0.17 rows=1 width=4) (never executed) Index Cond: (oid = t.typnamespace) Heap Fetches: 0 -> Seq Scan on pg_namespace nc (cost=0.00..1.06 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=1) Filter: ((NOT pg_is_other_temp_schema(oid)) AND (nspname = 'pg_catalog'::name)) Rows Removed by Filter: 1 Buffers: shared hit=5 Planning: Buffers: shared hit=168 read=52 written=5 I/O Timings: read=103.513 write=43.230 Planning Time: 184.492 ms Execution Time: 0.438 ms Edit: After running analyze on the database, same results: 2022-04-18 17:53:04.563 CST [16398] LOG: duration: 5987.077 ms statement: EXPLAIN (ANALYZE ON, BUFFERS ON) select 1 from information_schema.tables where table_schema = 'pg_catalog' and table_name = 'pg_file_settings' Nested Loop (cost=0.68..17.88 rows=1 width=4) (actual time=0.125..0.127 rows=1 loops=1) Join Filter: (c.relnamespace = nc.oid) Buffers: shared hit=8 read=2 I/O Timings: read=0.025 -> Nested Loop Left Join (cost=0.68..16.81 rows=1 width=4) (actual time=0.088..0.090 rows=1 loops=1) Buffers: shared hit=3 read=2 I/O Timings: read=0.025 -> Index Scan using pg_class_relname_nsp_index on pg_class c (cost=0.27..8.33 rows=1 width=8) (actual time=0.069..0.070 rows=1 loops=1) Index Cond: (relname = 'pg_file_settings'::name) Filter: ((relkind = ANY ('{r,v,f,p}'::"char"[])) AND (pg_has_role(relowner, 'USAGE'::text) OR has_table_privilege(oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text))) Buffers: shared hit=1 read=2 I/O Timings: read=0.025 -> Nested Loop (cost=0.40..8.47 rows=1 width=4) (actual time=0.016..0.016 rows=0 loops=1) Buffers: shared hit=2 -> Index Scan using pg_type_oid_index on pg_type t (cost=0.27..8.29 rows=1 width=8) (actual time=0.014..0.014 rows=0 loops=1) Index Cond: (oid = c.reloftype) Buffers: shared hit=2 -> Index Only Scan using pg_namespace_oid_index on pg_namespace nt (cost=0.13..0.17 rows=1 width=4) (never executed) Index Cond: (oid = t.typnamespace) Heap Fetches: 0 -> Seq Scan on pg_namespace nc (cost=0.00..1.06 rows=1 width=4) (actual time=0.035..0.035 rows=1 loops=1) Filter: ((NOT pg_is_other_temp_schema(oid)) AND (nspname = 'pg_catalog'::name)) Rows Removed by Filter: 1 Buffers: shared hit=5 Planning: Buffers: shared hit=170 read=59 written=9 I/O Timings: read=105.688 write=79.773 Planning Time: 262.826 ms Execution Time: 0.366 ms Edit: Analyze output after increasing shared_buffers to 25% of memory Nested Loop (cost=0.68..17.88 rows=1 width=4) (actual time=0.096..0.098 rows=1 loops=1) Join Filter: (c.relnamespace = nc.oid) Buffers: shared hit=10 -> Nested Loop Left Join (cost=0.68..16.81 rows=1 width=4) (actual time=0.057..0.059 rows=1 loops=1) Buffers: shared hit=5 -> Index Scan using pg_class_relname_nsp_index on pg_class c (cost=0.27..8.33 rows=1 width=8) (actual time=0.040..0.042 rows=1 loops=1) Index Cond: (relname = 'pg_file_settings'::name) Filter: ((relkind = ANY ('{r,v,f,p}'::"char"[])) AND (pg_has_role(relowner, 'USAGE'::text) OR has_table_privilege(oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text))) Buffers: shared hit=3 -> Nested Loop (cost=0.40..8.47 rows=1 width=4) (actual time=0.014..0.014 rows=0 loops=1) Buffers: shared hit=2 -> Index Scan using pg_type_oid_index on pg_type t (cost=0.27..8.29 rows=1 width=8) (actual time=0.012..0.012 rows=0 loops=1) Index Cond: (oid = c.reloftype) Buffers: shared hit=2 -> Index Only Scan using pg_namespace_oid_index on pg_namespace nt (cost=0.13..0.17 rows=1 width=4) (never executed) Index Cond: (oid = t.typnamespace) Heap Fetches: 0 -> Seq Scan on pg_namespace nc (cost=0.00..1.06 rows=1 width=4) (actual time=0.036..0.036 rows=1 loops=1) Filter: ((NOT pg_is_other_temp_schema(oid)) AND (nspname = 'pg_catalog'::name)) Rows Removed by Filter: 1 Buffers: shared hit=5 Planning: Buffers: shared hit=229 Planning Time: 7.866 ms Execution Time: 0.306 ms
Postgresql Limit 1 causing large number of loops
We have a transactions table, approx. 10m rows and growing. Each customer we have specifies many rules which group certain transactions together based on locations, associated products, sale customer, etc. Off of these rules we produce reports each night which allows them to see the price the customer is paying for products vs their purchase prices from different price lists, these lists are changing daily and each date on the transaction we have to either find their set yearly price or the price that was effective at the date of the transaction. These price lists can change historically and do all the time as do new historic transactions which are added so within each financial year we have to continue to regenerate these reports. We are having a problem with the two types of price list/price joins we have to do. The first is on the set yearly price list. I have removed the queries which bring the transactions in and put into a table called transaction_data_6787. EXPLAIN analyze SELECT * FROM transaction_data_6787 t inner JOIN LATERAL ( SELECT p."Price" FROM "Prices" p INNER JOIN "PriceLists" pl on p."PriceListId" = pl."Id" WHERE (pl."CustomerId" = 20) AND (pl."Year" = 2020) AND (pl."PriceListTypeId" = 2) AND p."ProductId" = t.product_id limit 1 ) AS prices ON true Nested Loop (cost=0.70..133877.20 rows=5394 width=165) (actual time=0.521..193.638 rows=5394 loops=1) -> Seq Scan on transaction_data_6787 t (cost=0.00..159.94 rows=5394 width=145) (actual time=0.005..0.593 rows=5394 loops=1) -> Limit (cost=0.70..24.77 rows=1 width=20) (actual time=0.035..0.035 rows=1 loops=5394) -> Nested Loop (cost=0.70..24.77 rows=1 width=20) (actual time=0.035..0.035 rows=1 loops=5394) -> Index Scan using ix_prices_covering on "Prices" p (cost=0.42..8.44 rows=1 width=16) (actual time=0.006..0.015 rows=23 loops=5394) Index Cond: (("ProductId" = t.product_id)) -> Index Scan using ix_pricelists_covering on "PriceLists" pl (cost=0.28..8.30 rows=1 width=12) (actual time=0.001..0.001 rows=0 loops=122443) Index Cond: (("Id" = p."PriceListId") AND ("CustomerId" = 20) AND ("PriceListTypeId" = 2)) Filter: ("Year" = 2020) Rows Removed by Filter: 0 Planning Time: 0.307 ms Execution Time: 193.982 ms If I remove the LIMIT 1, the execution time drops to 3ms and the 122443 loops on ix_pricelists_covering don't happen. The reason we are doing a lateral join is the price query is dynamically built and sometimes when not joining on the annual price list we join on the effective price lists. This looks like the below: EXPLAIN analyze SELECT * FROM transaction_data_6787 t inner JOIN LATERAL ( SELECT p."Price" FROM "Prices" p INNER JOIN "PriceLists" pl on p."PriceListId" = pl."Id" WHERE (pl."CustomerId" = 20) AND (pl."PriceListTypeId" = 1) AND p."ProductId" = t.product_id and pl."ValidFromDate" <= t.transaction_date ORDER BY pl."ValidFromDate" desc limit 1 ) AS prices ON true This is killing our performance, some queries are taking 20 seconds plus when we don't order by date desc/limit 1 it completes in ms but we could get duplicate prices back. We are happy to rewrite if a better way of joining the most recent record. We have thousands of price lists and 100k or prices and there could be 100s if not 1000s of effective prices for each transaction and we need to ensure we get the one which was most recently effective for a product at the date of the transaction. I have found if I denormalise the price lists/prices into a single table and add an index with ValidFromDate DESC it seems to eliminate the loops but I am hesitant to denormalise and have to maintain that data, these reports can be run adhoc as well as batch jobs and we would have to maintain that data in real time. Updated Explain/Analyze: I've added below the query which joins on prices which need to get the most recently effective for the transaction date. I see now that when <= date clause and limit 1 is removed it's actually spinning up multiple workers which is why it seems faster. I am still seeing the slower query doing a large number of loops, 200k+ (when limit 1/<= date is included). Maybe the better question is what can we do instead of a lateral join which will allow us to join the effective prices for transactions in the most efficient/performant way possible. I am hoping to avoid denormalising and maintainint that data but if it is the only way we'll do it. If there is a way to rewrite this and not denormalise then I'd really appreciate any insight. Nested Loop (cost=14.21..76965.60 rows=5394 width=10) (actual time=408.948..408.950 rows=0 loops=1) Output: t.transaction_id, pr."Price" Buffers: shared hit=688022 -> Seq Scan on public.transaction_data_6787 t (cost=0.00..159.94 rows=5394 width=29) (actual time=0.018..0.682 rows=5394 loops=1) Output: t.transaction_id Buffers: shared hit=106 -> Limit (cost=14.21..14.22 rows=1 width=10) (actual time=0.075..0.075 rows=0 loops=5394) Output: pr."Price", pl."ValidFromDate" Buffers: shared hit=687916 -> Sort (cost=14.21..14.22 rows=1 width=10) (actual time=0.075..0.075 rows=0 loops=5394) Output: pr."Price", pl."ValidFromDate" Sort Key: pl."ValidFromDate" DESC Sort Method: quicksort Memory: 25kB Buffers: shared hit=687916 -> Nested Loop (cost=0.70..14.20 rows=1 width=10) (actual time=0.074..0.074 rows=0 loops=5394) Output: pr."Price", pl."ValidFromDate" Inner Unique: true Buffers: shared hit=687916 -> Index Only Scan using ix_prices_covering on public."Prices" pr (cost=0.42..4.44 rows=1 width=10) (actual time=0.007..0.019 rows=51 loops=5394) Output: pr."ProductId", pr."ValidFromDate", pr."Id", pr."Price", pr."PriceListId" Index Cond: (pr."ProductId" = t.product_id) Heap Fetches: 0 Buffers: shared hit=17291 -> Index Scan using ix_pricelists_covering on public."PriceLists" pl (cost=0.28..8.30 rows=1 width=8) (actual time=0.001..0.001 rows=0 loops=273678) Output: pl."Id", pl."Name", pl."CustomerId", pl."ValidFromDate", pl."PriceListTypeId" Index Cond: ((pl."Id" = pr."PriceListId") AND (pl."CustomerId" = 20) AND (pl."PriceListTypeId" = 1)) Filter: (pl."ValidFromDate" <= t.transaction_date) Rows Removed by Filter: 0 Buffers: shared hit=670625 Planning Time: 1.254 ms Execution Time: 409.088 ms Gather (cost=6395.67..7011.99 rows=68 width=10) (actual time=92.481..92.554 rows=0 loops=1) Output: t.transaction_id, pr."Price" Workers Planned: 2 Workers Launched: 2 Buffers: shared hit=1466 read=2 -> Hash Join (cost=5395.67..6005.19 rows=28 width=10) (actual time=75.126..75.129 rows=0 loops=3) Output: t.transaction_id, pr."Price" Inner Unique: true Hash Cond: (pr."PriceListId" = pl."Id") Join Filter: (pl."ValidFromDate" <= t.transaction_date) Rows Removed by Join Filter: 41090 Buffers: shared hit=1466 read=2 Worker 0: actual time=64.707..64.709 rows=0 loops=1 Buffers: shared hit=462 Worker 1: actual time=72.545..72.547 rows=0 loops=1 Buffers: shared hit=550 read=1 -> Merge Join (cost=5374.09..5973.85 rows=3712 width=18) (actual time=26.804..61.492 rows=91226 loops=3) Output: t.transaction_id, t.transaction_date, pr."Price", pr."PriceListId" Merge Cond: (pr."ProductId" = t.product_id) Buffers: shared hit=1325 read=2 Worker 0: actual time=17.677..51.590 rows=83365 loops=1 Buffers: shared hit=400 Worker 1: actual time=24.995..59.395 rows=103814 loops=1 Buffers: shared hit=488 read=1 -> Parallel Index Only Scan using ix_prices_covering on public."Prices" pr (cost=0.42..7678.38 rows=79544 width=29) (actual time=0.036..12.136 rows=42281 loops=3) Output: pr."ProductId", pr."ValidFromDate", pr."Id", pr."Price", pr."PriceListId" Heap Fetches: 0 Buffers: shared hit=989 read=2 Worker 0: actual time=0.037..9.660 rows=36873 loops=1 Buffers: shared hit=285 Worker 1: actual time=0.058..13.459 rows=47708 loops=1 Buffers: shared hit=373 read=1 -> Sort (cost=494.29..507.78 rows=5394 width=29) (actual time=9.037..14.700 rows=94555 loops=3) Output: t.transaction_id, t.product_id, t.transaction_date Sort Key: t.product_id Sort Method: quicksort Memory: 614kB Worker 0: Sort Method: quicksort Memory: 614kB Worker 1: Sort Method: quicksort Memory: 614kB Buffers: shared hit=336 Worker 0: actual time=6.608..12.034 rows=86577 loops=1 Buffers: shared hit=115 Worker 1: actual time=8.973..14.598 rows=107126 loops=1 Buffers: shared hit=115 -> Seq Scan on public.transaction_data_6787 t (cost=0.00..159.94 rows=5394 width=29) (actual time=0.020..2.948 rows=5394 loops=3) Output: t.transaction_id, t.product_id, t.transaction_date Buffers: shared hit=318 Worker 0: actual time=0.017..2.078 rows=5394 loops=1 Buffers: shared hit=106 Worker 1: actual time=0.027..2.976 rows=5394 loops=1 Buffers: shared hit=106 -> Hash (cost=21.21..21.21 rows=30 width=8) (actual time=0.145..0.145 rows=35 loops=3) Output: pl."Id", pl."ValidFromDate" Buckets: 1024 Batches: 1 Memory Usage: 10kB Buffers: shared hit=53 Worker 0: actual time=0.137..0.138 rows=35 loops=1 Buffers: shared hit=18 Worker 1: actual time=0.149..0.150 rows=35 loops=1 Buffers: shared hit=18 -> Bitmap Heap Scan on public."PriceLists" pl (cost=4.59..21.21 rows=30 width=8) (actual time=0.067..0.114 rows=35 loops=3) Output: pl."Id", pl."ValidFromDate" Recheck Cond: (pl."CustomerId" = 20) Filter: (pl."PriceListTypeId" = 1) Rows Removed by Filter: 6 Heap Blocks: exact=15 Buffers: shared hit=53 Worker 0: actual time=0.068..0.108 rows=35 loops=1 Buffers: shared hit=18 Worker 1: actual time=0.066..0.117 rows=35 loops=1 Buffers: shared hit=18 -> Bitmap Index Scan on "IX_PriceLists_CustomerId" (cost=0.00..4.58 rows=41 width=0) (actual time=0.049..0.049 rows=41 loops=3) Index Cond: (pl."CustomerId" = 20) Buffers: shared hit=8 Worker 0: actual time=0.053..0.054 rows=41 loops=1 Buffers: shared hit=3 Worker 1: actual time=0.048..0.048 rows=41 loops=1 Buffers: shared hit=3 Planning Time: 2.236 ms Execution Time: 92.814 ms
Append Cost very high on Partitioned table
I have a query joining two tables partitioned on timestamp column. Both tables are filtered on current date partition. But query is unusually slow with APPEND Cost of the driving table very high. Query and Plan : https://explain.dalibo.com/plan/wVA Nested Loop (cost=0.56..174042.82 rows=16 width=494) (actual time=0.482..20.133 rows=1713 loops=1) Output: tran.transaction_status, mgwor.apx_transaction_id, org.organisation_name, mgwor.order_status, mgwor.request_date, mgwor.response_date, (date_part('epoch'::text, mgwor.response_date) - date_part('epoch'::text, mgwor.request_date)) Buffers: shared hit=5787 dirtied=3 -> Nested Loop (cost=0.42..166837.32 rows=16 width=337) (actual time=0.459..7.803 rows=1713 loops=1) Output: mgwor.apx_transaction_id, mgwor.order_status, mgwor.request_date, mgwor.response_date, org.organisation_name Join Filter: ((account.account_id)::text = (mgwor.account_id)::text) Rows Removed by Join Filter: 3007 Buffers: shared hit=589 -> Nested Loop (cost=0.27..40.66 rows=4 width=54) (actual time=0.203..0.483 rows=2 loops=1) Output: account.account_id, org.organisation_name Join Filter: ((account.organisation_id)::text = (org.organisation_id)::text) Rows Removed by Join Filter: 289 Buffers: shared hit=27 -> Index Scan using account_pkey on mdm.account (cost=0.27..32.55 rows=285 width=65) (actual time=0.013..0.122 rows=291 loops=1) Output: account.account_id, account.account_created_at, account.account_name, account.account_status, account.account_valid_until, account.currency_id, account.organisation_id, account.organisation_psp_id, account."account_threeDS_required", account.account_use_webhook, account.account_webhook_url, account.account_webhook_max_attempt, account.reporting_account_id, account.card_type, account.country_id, account.product_id Buffers: shared hit=24 -> Materialize (cost=0.00..3.84 rows=1 width=55) (actual time=0.000..0.000 rows=1 loops=291) Output: org.organisation_name, org.organisation_id Buffers: shared hit=3 -> Seq Scan on mdm.organisation_smd org (cost=0.00..3.84 rows=1 width=55) (actual time=0.017..0.023 rows=1 loops=1) Output: org.organisation_name, org.organisation_id Filter: ((org.organisation_name)::text = 'ABC'::text) Rows Removed by Filter: 67 Buffers: shared hit=3 -> Materialize (cost=0.15..166576.15 rows=3835 width=473) (actual time=0.127..2.826 rows=2360 loops=2) Output: mgwor.apx_transaction_id, mgwor.order_status, mgwor.request_date, mgwor.response_date, mgwor.account_id Buffers: shared hit=562 -> Append (cost=0.15..166556.97 rows=3835 width=473) (actual time=0.252..3.661 rows=2360 loops=1) Buffers: shared hit=562 Subplans Removed: 1460 -> Bitmap Heap Scan on public.mgworderrequest_part_20200612 mgwor (cost=50.98..672.23 rows=2375 width=91) (actual time=0.251..2.726 rows=2360 loops=1) Output: mgwor.apx_transaction_id, mgwor.order_status, mgwor.request_date, mgwor.response_date, mgwor.account_id Recheck Cond: ((mgwor.request_type)::text = ANY ('{CARD,CARD_PAYMENT}'::text[])) Filter: ((mgwor.request_date >= date(now())) AND (mgwor.request_date < (date(now()) + 1))) Heap Blocks: exact=549 Buffers: shared hit=562 -> Bitmap Index Scan on mgworderrequest_part_20200612_request_type_idx (cost=0.00..50.38 rows=2375 width=0) (actual time=0.191..0.192 rows=2361 loops=1) Index Cond: ((mgwor.request_type)::text = ANY ('{CARD,CARD_PAYMENT}'::text[])) Buffers: shared hit=13 -> Append (cost=0.14..435.73 rows=1461 width=316) (actual time=0.005..0.006 rows=1 loops=1713) Buffers: shared hit=5198 dirtied=3 Subplans Removed: 1460 -> Index Scan using transaction_part_20200612_pkey on public.transaction_part_20200612 tran (cost=0.29..0.87 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=1713) Output: tran.transaction_status, tran.transaction_id Index Cond: (((tran.transaction_id)::text = (mgwor.apx_transaction_id)::text) AND (tran.transaction_created_at >= date(now())) AND (tran.transaction_created_at < (date(now()) + 1))) Filter: (tran.transaction_status IS NOT NULL) Buffers: shared hit=5198 dirtied=3 Planning Time: 19535.308 ms Execution Time: 21.006 ms Partition pruning is working on both the tables. Am I missing something obvious here? Thanks, VA
I don't know why the cost estimate for the append is so large, but presumably you are really worried about how long this takes, not how large the estimate is. As noted, the actual time is going to planning, not to execution. A likely explanation is that it was waiting on a lock. Time spent waiting on a table lock for a partition table (but not for the parent table) gets attributed to planning time.
Postgresql: Query is slow (9.5)
this is my query. I have more than 600000 rows of my table sale. I think the numbers of data is not too much, but it takes approximately 50s. Here's the query. I need faster. SELECT st.product_id, prd.price_in_stock, prd.product_name_eng, prd.currency_id, prd.product_name_kh, (SELECT quantity FROM daily_stock WHERE stock_id = st.stock_id AND DATE (stock_date) = DATE (now()) ) AS pre_stock_quantity, (SELECT SUM (trrc.quantity) FROM transfer trs INNER JOIN transfer_detail trd ON trs.transfer_id = trd.transfer_id INNER JOIN transfer_received trrc ON trrc.transfer_detail_id = trd.transfer_detail_id WHERE trs.transfer_from = st.branch_id AND trd.product_id = st.product_id AND DATE (trrc.received_date) = DATE (NOW()) ) AS trasfered_quantity, (SELECT SUM (trrc.quantity) FROM transfer trs INNER JOIN transfer_detail trd ON trs.transfer_id = trd.transfer_id INNER JOIN transfer_received trrc ON trrc.transfer_detail_id = trd.transfer_detail_id WHERE trs.transfer_to = st.branch_id AND trd.product_id = st.product_id AND DATE (trrc.received_date) = DATE (NOW()) ) AS received_quantity, (SELECT (SUM (smallest_devisor (sd.product_id) * sd.quantity / getDevisor (sd.product_id, sd.unit_id))) / smallest_devisor (sd.product_id) AS sold_quantity FROM sale_detail sd INNER JOIN sale sa ON sa.sale_id = sd.sale_id WHERE sa.branch_id = st.branch_id AND sd.product_id = st.product_id AND DATE (sa.sale_date) = DATE (NOW()) GROUP BY sd.product_id ), (SELECT (SUM(smallest_devisor (sd.product_id) * rp.quantity / getDevisor (sd.product_id, sd.unit_id)) ) / smallest_devisor (sd.product_id) AS returned_quantity FROM returned_product rp INNER JOIN sale_detail sd ON sd.sale_detail_id = rp.sale_detail_id WHERE rp.branch_id = st.branch_id AND sd.product_id = st.product_id AND DATE (rp.returned_date) = DATE (NOW()) GROUP BY sd.product_id ), ( SELECT SUM (quantity) AS imported_quantity FROM import WHERE branch_id = st.branch_id AND product_id = st.product_id AND DATE (import_date) = DATE (NOW()) ), st.quantity AS post_stock_quantity FROM stock st INNER JOIN product prd ON prd.product_id = st.product_id LEFT JOIN import imp ON imp.product_id = st.product_id WHERE st.branch_id = 'BR0000'; analyze is here, I've seen the nested loop has taken cost much, but I don't know what to do more. I don't understand well about them, pls explain me some. Hash Left Join (cost=2.45..210929.10 rows=4 width=99) (actual time=845.040..68556.076 rows=22 loops=1) Hash Cond: ((st.product_id)::text = (imp.product_id)::text) Buffers: shared hit=2362098 read=499796, temp read=63910 written=63781 -> Hash Join (cost=1.11..11.36 rows=4 width=99) (actual time=0.130..0.160 rows=5 loops=1) Hash Cond: ((st.product_id)::text = (prd.product_id)::text) Buffers: shared hit=11 -> Seq Scan on stock st (cost=0.00..10.19 rows=4 width=23) (actual time=0.025..0.041 rows=5 loops=1) Filter: ((branch_id)::text = 'BR0000'::text) Rows Removed by Filter: 10 Buffers: shared hit=10 -> Hash (cost=1.05..1.05 rows=5 width=114) (actual time=0.058..0.058 rows=5 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 9kB Buffers: shared hit=1 -> Seq Scan on product prd (cost=0.00..1.05 rows=5 width=114) (actual time=0.037..0.041 rows=5 loops=1) Buffers: shared hit=1 -> Hash (cost=1.15..1.15 rows=15 width=38) (actual time=0.041..0.041 rows=22 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 9kB Buffers: shared hit=1 -> Seq Scan on import imp (cost=0.00..1.15 rows=15 width=38) (actual time=0.009..0.019 rows=22 loops=1) Buffers: shared hit=1 SubPlan 1 -> Seq Scan on daily_stock (cost=0.00..48.25 rows=1 width=8) (actual time=0.010..0.011 rows=1 loops=22) Filter: ((stock_id = st.stock_id) AND (date(stock_date) = date(now()))) Rows Removed by Filter: 29 Buffers: shared hit=22 SubPlan 2 -> Aggregate (cost=52.41..52.42 rows=1 width=8) (actual time=0.010..0.010 rows=1 loops=22) -> Nested Loop (cost=0.15..52.41 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=22) -> Nested Loop (cost=0.00..45.56 rows=1 width=60) (actual time=0.001..0.001 rows=0 loops=22) Join Filter: (trd.transfer_detail_id = trrc.transfer_detail_id) -> Seq Scan on transfer_received trrc (cost=0.00..28.00 rows=4 width=12) (actual time=0.000..0.000 rows=0 loops=22) Filter: (date(received_date) = date(now())) -> Materialize (cost=0.00..17.39 rows=3 width=56) (never executed) -> Seq Scan on transfer_detail trd (cost=0.00..17.38 rows=3 width=56) (never executed) Filter: ((product_id)::text = (st.product_id)::text) -> Index Scan using transfer_pkey on transfer trs (cost=0.15..6.83 rows=1 width=52) (never executed) Index Cond: ((transfer_id)::text = (trd.transfer_id)::text) Filter: ((transfer_from)::text = (st.branch_id)::text) SubPlan 3 -> Aggregate (cost=52.41..52.42 rows=1 width=8) (actual time=0.007..0.007 rows=1 loops=22) -> Nested Loop (cost=0.15..52.41 rows=1 width=8) (actual time=0.005..0.005 rows=0 loops=22) -> Nested Loop (cost=0.00..45.56 rows=1 width=60) (actual time=0.000..0.000 rows=0 loops=22) Join Filter: (trd_1.transfer_detail_id = trrc_1.transfer_detail_id) -> Seq Scan on transfer_received trrc_1 (cost=0.00..28.00 rows=4 width=12) (actual time=0.000..0.000 rows=0 loops=22) Filter: (date(received_date) = date(now())) -> Materialize (cost=0.00..17.39 rows=3 width=56) (never executed) -> Seq Scan on transfer_detail trd_1 (cost=0.00..17.38 rows=3 width=56) (never executed) Filter: ((product_id)::text = (st.product_id)::text) -> Index Scan using transfer_pkey on transfer trs_1 (cost=0.15..6.83 rows=1 width=52) (never executed) Index Cond: ((transfer_id)::text = (trd_1.transfer_id)::text) Filter: ((transfer_to)::text = (st.branch_id)::text) SubPlan 4 -> GroupAggregate (cost=32059.02..52563.95 rows=1 width=18) (actual time=3116.048..3116.048 rows=0 loops=22) Group Key: sd.product_id Buffers: shared hit=2362020 read=499796, temp read=63910 written=63781 -> Hash Join (cost=32059.02..52045.08 rows=1007 width=18) (actual time=830.903..988.696 rows=53521 loops=22) Hash Cond: ((sd.sale_id)::text = (sa.sale_id)::text) Buffers: shared hit=7084 read=499796, temp read=63910 written=63781 -> Seq Scan on sale_detail sd (cost=0.00..19220.90 rows=201358 width=35) (actual time=0.079..193.761 rows=292880 loops=22) Filter: ((product_id)::text = (st.product_id)::text) Rows Removed by Filter: 512552 Buffers: shared hit=3520 read=197846 -> Hash (cost=32008.68..32008.68 rows=4027 width=17) (actual time=552.758..552.758 rows=147183 loops=22) Buckets: 131072 (originally 4096) Batches: 4 (originally 1) Memory Usage: 3137kB Buffers: shared hit=3564 read=301950, temp written=10934 -> Seq Scan on sale sa (cost=0.00..32008.68 rows=4027 width=17) (actual time=400.100..500.394 rows=147183 loops=22) Filter: (((branch_id)::text = (st.branch_id)::text) AND (date(sale_date) = date(now()))) Rows Removed by Filter: 658225 Buffers: shared hit=3564 read=301950 SubPlan 5 -> GroupAggregate (cost=0.42..10.66 rows=1 width=18) (actual time=0.041..0.041 rows=0 loops=22) Group Key: sd_1.product_id Buffers: shared hit=22 -> Nested Loop (cost=0.42..9.88 rows=1 width=18) (actual time=0.040..0.040 rows=0 loops=22) Buffers: shared hit=22 -> Seq Scan on returned_product rp (cost=0.00..1.43 rows=1 width=12) (actual time=0.026..0.026 rows=0 loops=22) Filter: (((branch_id)::text = (st.branch_id)::text) AND (date(returned_date) = date(now()))) Rows Removed by Filter: 19 Buffers: shared hit=22 -> Index Scan using sale_detail_pkey on sale_detail sd_1 (cost=0.42..8.45 rows=1 width=18) (never executed) Index Cond: (sale_detail_id = rp.sale_detail_id) Filter: ((product_id)::text = (st.product_id)::text) SubPlan 6 -> Aggregate (cost=1.38..1.39 rows=1 width=8) (actual time=0.027..0.027 rows=1 loops=22) Buffers: shared hit=22 -> Seq Scan on import (cost=0.00..1.38 rows=1 width=8) (actual time=0.018..0.018 rows=0 loops=22) Filter: (((branch_id)::text = (st.branch_id)::text) AND ((product_id)::text = (st.product_id)::text) AND (date(import_date) = date(now()))) Rows Removed by Filter: 22 Buffers: shared hit=22 Planning time: 4.046 ms Execution time: 68557.361 ms