postgres seq scan on two partitions - postgresql

When ch table is queried direcly on rp_fk column, it uses index for almost all of its partitions.
explain analyze select * from oc.ch ch where rp_fk = 'abc123';
Append (cost=0.14..9469.61 rows=2357 width=10008) (actual time=0.164..0.166 rows=0 loops=1)
-> Index Scan using ch_2016_rp_fk_idx1 on ch_2016 ch (cost=0.14..8.16 rows=1 width=11097) (actual time=0.015..0.016 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Index Scan using ch_2017_rp_fk_idx1 on ch_2017 ch_1 (cost=0.14..8.16 rows=1 width=10477) (actual time=0.022..0.022 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Index Scan using ch_2018_rp_fk_idx1 on ch_2018 ch_2 (cost=0.56..4.58 rows=1 width=12295) (actual time=0.031..0.031 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Index Scan using ch_2019_rp_fk_idx1 on ch_2019 ch_3 (cost=0.69..4.71 rows=1 width=12299) (actual time=0.025..0.025 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Index Scan using ch_2020_rp_fk_idx1 on ch_2020 ch_4 (cost=0.69..6030.67 rows=1510 width=10259) (actual time=0.029..0.029 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Bitmap Heap Scan on ch_2021 ch_5 (cost=55.08..3392.36 rows=841 width=9566) (actual time=0.018..0.019 rows=0 loops=1)
Recheck Cond: (rp_fk = 'abc123'::bpchar)
-> Bitmap Index Scan on ch_2021_rp_fk_idx1 (cost=0.00..54.87 rows=841 width=0) (actual time=0.016..0.016 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Index Scan using ch_2022_rp_fk_idx on ch_2022 ch_6 (cost=0.12..8.14 rows=1 width=12241) (actual time=0.013..0.013 rows=0 loops=1)
Index Cond: (rp_fk = 'abc123'::bpchar)
-> Seq Scan on ch_def ch_7 (cost=0.00..1.05 rows=1 width=12167) (actual time=0.009..0.009 rows=0 loops=1)
Filter: (rp_fk = 'abc123'::bpchar)
Rows Removed by Filter: 4
Planning Time: 24.637 ms
Execution Time: 0.538 ms
But when joining ch table with p table from where it gets the rp_fk, its doing a sequential scan of the ch_2018 and ch_2019 partitions. This is where its taking most of the time, whereas 2020 and 2021 paritions are scanned using index. Any insight as to why its using seq scan on these 2 partitions? All the partitions have same indexes and have upto date vacuum analyze. random_page_cost is set to 1.
explain analyze select * FROM oc.ch ch JOIN op.p p ON ch.rp_fk = p.p_pk WHERE ch.del_flg = '0'::bpchar and hf_p_num_cd ='112642817-002';
Nested Loop (cost=0.42..13134696.50 rows=96 width=12593) (actual time=193878.564..193878.569 rows=0 loops=1)
-> Index Scan using ix_p_hf_p_num_cd on p p (cost=0.42..8.44 rows=1 width=1420) (actual time=0.025..0.028 rows=1 loops=1)
Index Cond: ((hf_p_num_cd)::text = '112642817-002'::text)
-> Append (cost=0.00..12666318.27 rows=46836978 width=11167) (actual time=193878.532..193878.535 rows=0 loops=1)
-> Seq Scan on ch_2016 ch (cost=0.00..11.57 rows=13 width=11097) (actual time=0.072..0.072 rows=0 loops=1)
Filter: ((del_flg = '0'::bpchar) AND (p.p_pk = rp_fk))
Rows Removed by Filter: 38
-> Index Scan using ch_2017_rp_fk_idx1 on ch_2017 ch_1 (cost=0.14..20.94 rows=40 width=10477) (actual time=0.016..0.016 rows=0 loops=1)
Index Cond: (rp_fk = p.p_pk)
Filter: (del_flg = '0'::bpchar)
-> Seq Scan on ch_2018 ch_2 (cost=0.00..4005273.43 rows=15109962 width=12295) (actual time=25051.825..25051.825 rows=0 loops=1)
Filter: ((del_flg = '0'::bpchar) AND (p.p_pk = rp_fk))
Rows Removed by Filter: 15111645
-> Seq Scan on ch_2019 ch_3 (cost=0.00..8406638.77 rows=31721918 width=12299) (actual time=168826.481..168826.481 rows=0 loops=1)
Filter: ((del_flg = '0'::bpchar) AND (p.p_pk = rp_fk))
Rows Removed by Filter: 31722679
-> Index Scan using ch_2020_rp_fk_idx1 on ch_2020 ch_4 (cost=0.69..14893.73 rows=3729 width=10259) (actual time=0.057..0.057 rows=0 loops=1)
Index Cond: (rp_fk = p.p_pk)
Filter: (del_flg = '0'::bpchar)
-> Bitmap Heap Scan on ch_2021 ch_5 (cost=82.74..5285.74 rows=1314 width=9566) (actual time=0.033..0.033 rows=0 loops=1)
Recheck Cond: (rp_fk = p.p_pk)
Filter: (del_flg = '0'::bpchar)
-> Bitmap Index Scan on ch_2021_rp_fk_idx1 (cost=0.00..82.42 rows=1314 width=0) (actual time=0.022..0.022 rows=0 loops=1)
Index Cond: (rp_fk = p.p_pk)
-> Index Scan using ch_2022_rp_fk_idx on ch_2022 ch_6 (cost=0.12..8.14 rows=1 width=12241) (actual time=0.014..0.014 rows=0 loops=1)
Filter: ((del_flg = '0'::bpchar) AND (p.p_pk = rp_fk))
-> Seq Scan on ch_def ch_7 (cost=0.00..1.06 rows=1 width=12167) (actual time=0.017..0.017 rows=0 loops=1)
Filter: ((del_flg = '0'::bpchar) AND (p.p_pk = rp_fk))
Rows Removed by Filter: 4
Planning Time: 4.183 ms
Execution Time: 193878.918 ms

It seems like 'abc123' is a very rare value for rp_fk, so PostgreSQL plans an index scan.
But it seems there are values that are much more frequent. In the second query, the optimizer does not know what value the rp_fk for hf_p_num_cd = '112642817-002' is going to be, so it goes with some average estimate, which turns out to be much higher for many partitions. Hence the sequential scan.
I would split the query in two parts and query the partitioned table with the constants found in the first query. Then the optimizer knows more and will plan better.
If you are certain that an index scan will always win, you can force the planner's hand:
BEGIN;
SET LOCAL enable_seqscan = off;
SET LOCAL enable_bitmapscan = off;
SELECT ...
COMMIT;

Related

Postgres hash join batches explosion

We are having some struggle identifying why Postgres is using too much batches to resolve a join.
Here it is the output of explain analyze of a problematic execution:
https://explain.dalibo.com/plan/xNJ#plan
Limit (cost=20880.87..20882.91 rows=48 width=205) (actual time=10722.953..10723.358 rows=48 loops=1)
-> Unique (cost=20880.87..21718.12 rows=19700 width=205) (actual time=10722.951..10723.356 rows=48 loops=1)
-> Sort (cost=20880.87..20930.12 rows=19700 width=205) (actual time=10722.950..10722.990 rows=312 loops=1)
Sort Key: titlemetadata_titlemetadata.creation_date DESC, titlemetadata_titlemetadata.id, titlemetadata_titlemetadata.title_type, titlemetadata_titlemetadata.original_title, titlemetadata_titlemetadata.alternative_ids, titlemetadata_titlemetadata.metadata,
titlemetadata_titlemetadata.is_adult, titlemetadata_titlemetadata.is_kids, titlemetadata_titlemetadata.last_modified, titlemetadata_titlemetadata.year, titlemetadata_titlemetadata.runtime, titlemetadata_titlemetadata.rating, titlemetadata_titlemetadata.video_provider, tit
lemetadata_titlemetadata.series_id_id, titlemetadata_titlemetadata.season_number, titlemetadata_titlemetadata.episode_number
Sort Method: quicksort Memory: 872kB
-> Hash Right Join (cost=13378.20..19475.68 rows=19700 width=205) (actual time=1926.352..10709.970 rows=2909 loops=1)
Hash Cond: (t4.titlemetadata_id = t3.id)
Filter: ((hashed SubPlan 1) OR (hashed SubPlan 2))
Rows Removed by Filter: 63248
-> Seq Scan on video_provider_offer t4 (cost=0.00..5454.90 rows=66290 width=16) (actual time=0.024..57.893 rows=66390 loops=1)
-> Hash (cost=11314.39..11314.39 rows=22996 width=221) (actual time=489.530..489.530 rows=60096 loops=1)
Buckets: 65536 (originally 32768) Batches: 32768 (originally 1) Memory Usage: 11656kB
-> Hash Right Join (cost=5380.95..11314.39 rows=22996 width=221) (actual time=130.024..225.271 rows=60096 loops=1)
Hash Cond: (video_provider_offer.titlemetadata_id = titlemetadata_titlemetadata.id)
-> Seq Scan on video_provider_offer (cost=0.00..5454.90 rows=66290 width=16) (actual time=0.011..32.950 rows=66390 loops=1)
-> Hash (cost=5129.28..5129.28 rows=20133 width=213) (actual time=129.897..129.897 rows=55793 loops=1)
Buckets: 65536 (originally 32768) Batches: 2 (originally 1) Memory Usage: 7877kB
-> Merge Left Join (cost=1.72..5129.28 rows=20133 width=213) (actual time=0.041..93.057 rows=55793 loops=1)
Merge Cond: (titlemetadata_titlemetadata.id = t3.series_id_id)
-> Index Scan using titlemetadata_titlemetadata_pkey on titlemetadata_titlemetadata (cost=1.30..4130.22 rows=20133 width=205) (actual time=0.028..62.949 rows=43921 loops=1)
Filter: ((NOT is_adult) AND (NOT (hashed SubPlan 3)) AND (((title_type)::text = 'MOV'::text) OR ((title_type)::text = 'TVS'::text) OR ((title_type)::text = 'TVP'::text) OR ((title_type)::text = 'EVT'::text)))
Rows Removed by Filter: 14121
SubPlan 3
-> Seq Scan on cable_operator_cableoperatorexcludedtitle u0_2 (cost=0.00..1.01 rows=1 width=8) (actual time=0.006..0.006 rows=0 loops=1)
Filter: (cable_operator_id = 54)
-> Index Scan using titlemetadata_titlemetadata_series_id_id_73453db4_uniq on titlemetadata_titlemetadata t3 (cost=0.41..3901.36 rows=58037 width=16) (actual time=0.011..9.375 rows=12887 loops=1)
SubPlan 1
-> Hash Join (cost=44.62..885.73 rows=981 width=8) (actual time=0.486..36.806 rows=5757 loops=1)
Hash Cond: (w2.device_id = w3.id)
-> Nested Loop (cost=43.49..866.20 rows=2289 width=16) (actual time=0.441..33.096 rows=20180 loops=1)
-> Nested Loop (cost=43.06..414.98 rows=521 width=8) (actual time=0.426..9.952 rows=2909 loops=1)
Join Filter: (w1.id = w0.video_provider_id)
-> Nested Loop (cost=42.65..54.77 rows=13 width=24) (actual time=0.399..0.532 rows=15 loops=1)
-> HashAggregate (cost=42.50..42.95 rows=45 width=16) (actual time=0.390..0.403 rows=45 loops=1)
Group Key: v0.id
-> Nested Loop (cost=13.34..42.39 rows=45 width=16) (actual time=0.095..0.364 rows=45 loops=1)
-> Hash Semi Join (cost=13.19..32.72 rows=45 width=8) (actual time=0.084..0.229 rows=45 loops=1)
Hash Cond: (v1.id = u0.id)
-> Seq Scan on cable_operator_cableoperatorprovider v1 (cost=0.00..17.36 rows=636 width=16) (actual time=0.010..0.077 rows=636 loops=1)
-> Hash (cost=12.63..12.63 rows=45 width=8) (actual time=0.046..0.046 rows=45 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 10kB
-> Index Scan using cable_operator_cableoperatorprovider_4d6e54b3 on cable_operator_cableoperatorprovider u0 (cost=0.28..12.63 rows=45 width=8) (actual time=0.016..0.035 rows=45 loops=1)
Index Cond: (cable_operator_id = 54)
-> Index Only Scan using video_provider_videoprovider_pkey on video_provider_videoprovider v0 (cost=0.15..0.20 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=45)
Index Cond: (id = v1.provider_id)
Heap Fetches: 45
-> Index Scan using video_provider_videoprovider_pkey on video_provider_videoprovider w1 (cost=0.15..0.25 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=45)
Index Cond: (id = v0.id)
Filter: ((video_provider_type)::text = 'VOD'::text)
Rows Removed by Filter: 1
-> Index Scan using video_provider_offer_da942d2e on video_provider_offer w0 (cost=0.42..27.22 rows=39 width=16) (actual time=0.026..0.585 rows=194 loops=15)
Index Cond: (video_provider_id = v0.id)
Filter: (((end_date > '2021-09-02 19:23:00-03'::timestamp with time zone) OR (end_date IS NULL)) AND (access_criteria && '{vtv_mas,TBX_LOGIN,urn:spkg:tve:fox-premium,urn:tve:mcp,AMCHD,AMC_CONSORCIO,ANIMAL_PLANET,ASUNTOS_PUBLI
COS,ASUNTOS_PUBLICOS_CONSORCIO,CINECANALLIVE,CINECANAL_CONSORCIO,DISCOVERY,DISCOVERY_KIDS_CONSORCIO,DISCOVERY_KIDS_OD,DISNEY,DISNEY_CH_CONSORCIO,DISNEY_XD,DISNEY_XD_CONSORCIO,EL_CANAL_HD,EL_CANAL_HD_CONSORCIO,EL_GOURMET_CONSORCIO,ESPN,ESPN2_HD_CONSORCIO,ESPN3_HD_CONSORCIO
,ESPNMAS_HD_CONSORCIO,ESPN_BASIC,ESPN_HD_CONSORCIO,ESPN_PLAY,EUROPALIVE,EUROPA_EUROPA,EUROPA_EUROPA_CONSORCIO,FILMANDARTS_DISPOSITIVOS,FILMS_ARTS,FILM_AND_ARTS_CONSORCIO,FOXLIFE,FOX_LIFE_CONSORCIO,FOX_SPORTS_1_DISPOSITIVOS,FOX_SPORTS_2_DISPOSITIVOS,FOX_SPORTS_2_HD_CONSORC
IO,FOX_SPORTS_3_DISPOSITIVOS,FOX_SPORTS_3_HD_CONSORCIO,FOX_SPORTS_HD_CONSORCIO,FRANCE24_DISPOSITIVOS,FRANCE_24_CONSORCIO,GOURMET,GOURMET_DISPOSITIVOS,HOME_HEALTH,INVESTIGATION_DISCOVERY,MAS_CHIC,NATGEOKIDS_DISPOSITIVOS,NATGEO_CONSORCIO,NATGEO_DISPOSITIVOS,NATGEO_KIDS_CONS
ORCIO,PASIONES,PASIONES_CONSORCIO,SVOD_TYC_BASIC,TBX_LOGIN,TCC_2_CONSORCIO,TCC_2_HD,TLC,TVE,TVE_CONSORCIO,TYC_SPORTS_CONSORCIO,VTV_LIVE,clarosports,discoverykids,espnplay_south_alt,urn:spkg:tve:fox-basic,urn:tve:babytv,urn:tve:cinecanal,urn:tve:discoverykids,urn:tve:foxli
fe,urn:tve:fp,urn:tve:fx,urn:tve:natgeo,urn:tve:natgeokids,urn:tve:natgeowild,urn:tve:thefilmzone}'::character varying(50)[]) AND ((((content_type)::text = 'VOD'::text) AND ((start_date < '2021-09-02 19:23:00-03'::timestamp with time zone) OR (start_date IS NULL))) OR ((c
ontent_type)::text = 'LIV'::text)))
Rows Removed by Filter: 5
-> Index Only Scan using video_provider_offer_devices_offer_id_device_id_key on video_provider_offer_devices w2 (cost=0.42..0.81 rows=6 width=16) (actual time=0.004..0.007 rows=7 loops=2909)
Index Cond: (offer_id = w0.id)
Heap Fetches: 17828
-> Hash (cost=1.10..1.10 rows=3 width=8) (actual time=0.029..0.029 rows=2 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Seq Scan on platform_device_device w3 (cost=0.00..1.10 rows=3 width=8) (actual time=0.024..0.027 rows=2 loops=1)
Filter: ((device_code)::text = ANY ('{ANDROID,ott_dual_tcc,ott_k2_tcc}'::text[]))
Rows Removed by Filter: 5
SubPlan 2
-> Hash Join (cost=44.62..885.73 rows=981 width=8) (actual time=0.410..33.580 rows=5757 loops=1)
Hash Cond: (w2_1.device_id = w3_1.id)
-> Nested Loop (cost=43.49..866.20 rows=2289 width=16) (actual time=0.375..29.886 rows=20180 loops=1)
-> Nested Loop (cost=43.06..414.98 rows=521 width=8) (actual time=0.366..9.134 rows=2909 loops=1)
Join Filter: (w1_1.id = w0_1.video_provider_id)
-> Nested Loop (cost=42.65..54.77 rows=13 width=24) (actual time=0.343..0.476 rows=15 loops=1)
-> HashAggregate (cost=42.50..42.95 rows=45 width=16) (actual time=0.333..0.347 rows=45 loops=1)
Group Key: v0_1.id
-> Nested Loop (cost=13.34..42.39 rows=45 width=16) (actual time=0.083..0.311 rows=45 loops=1)
-> Hash Semi Join (cost=13.19..32.72 rows=45 width=8) (actual time=0.076..0.202 rows=45 loops=1)
Hash Cond: (v1_1.id = u0_1.id)
-> Seq Scan on cable_operator_cableoperatorprovider v1_1 (cost=0.00..17.36 rows=636 width=16) (actual time=0.005..0.057 rows=636 loops=1)
-> Hash (cost=12.63..12.63 rows=45 width=8) (actual time=0.038..0.038 rows=45 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 10kB
-> Index Scan using cable_operator_cableoperatorprovider_4d6e54b3 on cable_operator_cableoperatorprovider u0_1 (cost=0.28..12.63 rows=45 width=8) (actual time=0.007..0.020 rows=45 loops=1)
Index Cond: (cable_operator_id = 54)
-> Index Only Scan using video_provider_videoprovider_pkey on video_provider_videoprovider v0_1 (cost=0.15..0.20 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=45)
Index Cond: (id = v1_1.provider_id)
Heap Fetches: 45
-> Index Scan using video_provider_videoprovider_pkey on video_provider_videoprovider w1_1 (cost=0.15..0.25 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=45)
Index Cond: (id = v0_1.id)
Filter: ((video_provider_type)::text = 'VOD'::text)
Rows Removed by Filter: 1
-> Index Scan using video_provider_offer_da942d2e on video_provider_offer w0_1 (cost=0.42..27.22 rows=39 width=16) (actual time=0.022..0.536 rows=194 loops=15)
Index Cond: (video_provider_id = v0_1.id)
Filter: (((end_date > '2021-09-02 19:23:00-03'::timestamp with time zone) OR (end_date IS NULL)) AND (access_criteria && '{vtv_mas,TBX_LOGIN,urn:spkg:tve:fox-premium,urn:tve:mcp,AMCHD,AMC_CONSORCIO,ANIMAL_PLANET,ASUNTOS_PUBLI
COS,ASUNTOS_PUBLICOS_CONSORCIO,CINECANALLIVE,CINECANAL_CONSORCIO,DISCOVERY,DISCOVERY_KIDS_CONSORCIO,DISCOVERY_KIDS_OD,DISNEY,DISNEY_CH_CONSORCIO,DISNEY_XD,DISNEY_XD_CONSORCIO,EL_CANAL_HD,EL_CANAL_HD_CONSORCIO,EL_GOURMET_CONSORCIO,ESPN,ESPN2_HD_CONSORCIO,ESPN3_HD_CONSORCIO
,ESPNMAS_HD_CONSORCIO,ESPN_BASIC,ESPN_HD_CONSORCIO,ESPN_PLAY,EUROPALIVE,EUROPA_EUROPA,EUROPA_EUROPA_CONSORCIO,FILMANDARTS_DISPOSITIVOS,FILMS_ARTS,FILM_AND_ARTS_CONSORCIO,FOXLIFE,FOX_LIFE_CONSORCIO,FOX_SPORTS_1_DISPOSITIVOS,FOX_SPORTS_2_DISPOSITIVOS,FOX_SPORTS_2_HD_CONSORC
IO,FOX_SPORTS_3_DISPOSITIVOS,FOX_SPORTS_3_HD_CONSORCIO,FOX_SPORTS_HD_CONSORCIO,FRANCE24_DISPOSITIVOS,FRANCE_24_CONSORCIO,GOURMET,GOURMET_DISPOSITIVOS,HOME_HEALTH,INVESTIGATION_DISCOVERY,MAS_CHIC,NATGEOKIDS_DISPOSITIVOS,NATGEO_CONSORCIO,NATGEO_DISPOSITIVOS,NATGEO_KIDS_CONS
ORCIO,PASIONES,PASIONES_CONSORCIO,SVOD_TYC_BASIC,TBX_LOGIN,TCC_2_CONSORCIO,TCC_2_HD,TLC,TVE,TVE_CONSORCIO,TYC_SPORTS_CONSORCIO,VTV_LIVE,clarosports,discoverykids,espnplay_south_alt,urn:spkg:tve:fox-basic,urn:tve:babytv,urn:tve:cinecanal,urn:tve:discoverykids,urn:tve:foxli
fe,urn:tve:fp,urn:tve:fx,urn:tve:natgeo,urn:tve:natgeokids,urn:tve:natgeowild,urn:tve:thefilmzone}'::character varying(50)[]) AND ((((content_type)::text = 'VOD'::text) AND ((start_date < '2021-09-02 19:23:00-03'::timestamp with time zone) OR (start_date IS NULL))) OR ((c
ontent_type)::text = 'LIV'::text)))
Rows Removed by Filter: 5
-> Index Only Scan using video_provider_offer_devices_offer_id_device_id_key on video_provider_offer_devices w2_1 (cost=0.42..0.81 rows=6 width=16) (actual time=0.003..0.006 rows=7 loops=2909)
Index Cond: (offer_id = w0_1.id)
Heap Fetches: 17828
-> Hash (cost=1.10..1.10 rows=3 width=8) (actual time=0.015..0.015 rows=2 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Seq Scan on platform_device_device w3_1 (cost=0.00..1.10 rows=3 width=8) (actual time=0.010..0.011 rows=2 loops=1)
Filter: ((device_code)::text = ANY ('{ANDROID,ott_dual_tcc,ott_k2_tcc}'::text[]))
Rows Removed by Filter: 5
Planning time: 8.255 ms
Execution time: 10723.830 ms
(100 rows)
The weird part is that the same query, sometimes just uses a single batch. Here is an example: https://explain.dalibo.com/plan/zTv#plan
Here is the work_mem being used:
show work_mem;
work_mem
----------
8388kB
(1 row)
I'm not interested in changing the query to be more performant, but in understanding why is the different behavior.
I've found this thread apparently related with this, but I don't quite understand what are they talking about: https://www.postgresql.org/message-id/flat/CA%2BhUKGKWWmf%3DWELLG%3DaUGbcugRaSQbtm0tKYiBut-B2rVKX63g%40mail.gmail.com
Can anyone tell me why is this different behavior? The underlying data is the same in both cases.
If the hash is done in memory, there will only be a single batch.
A difference with the original hash batch numbers is due to Postgres choosing to increase the number of batches in order to reduce memory consumption.
You might find this EXPLAIN glossary useful (disclaimer: I'm one of the authors), here is the page on Hash Batches which also links to the PostgreSQL source code (it's very nicely documented in plain English).
While not a perfect heuristic, you can see that the memory required for the operations with multiple batches are around or above your work_mem setting. They can be lower than it, due to operations on disk generally requiring less memory overall.
I'm not 100% sure why in your exact case one was chosen over the other, but it does look like there are some very slight row estimate differences, which might be a good place to start.
As of PostgreSQL 13 there is also now a hash_mem_multiplier setting that can be used to give more memory to hashes without doing so for other operations (like sorts).
We where able to solve the problem just by doing VACUUM FULL ANALYZE;.
After that, everything started to work as expected (https://explain.depesz.com/s/eoqH#html)
Side note: we where not aware that we should do this on daily basis.

Query is slow with indexes - how to understand execution plans?

I need help to understand why my query is slower when I use index than without any index. I ran explain analyze command, and below are execution plans option 1 - with index, and option 2 - without index.
Can someone explain to me why index makes performances worse in those execution plans?
PS. When I add 10 million rows to table (original size 2M), situation is turning in favor of index, and in that case query with index is 3x faster).
OPTION 1 WITH INDEX FOR LEFT JOIN invoice_id+acct_level ON TABLE cost_invoice_facepage AND CONDITION (cdb.invoice_id = invoice_id) AND (acct_level = 1)
Append (cost=48.87..38583.97 rows=163773 width=371) (actual time=1.269..1516.564 rows=379129 loops=1)
-> Nested Loop (cost=48.87..10520.11 rows=36504 width=362) (actual time=1.268..5.986 rows=579 loops=1)
-> Hash Left Join (cost=44.66..9918.22 rows=507 width=322) (actual time=1.160..5.497 rows=579 loops=1)
Hash Cond: (cd.gl_string_id = gs.id)
-> Nested Loop Left Join (cost=0.85..9873.07 rows=507 width=262) (actual time=0.485..4.473 rows=579 loops=1)
Filter: ((c.gl_rule_type IS NULL) OR ((cd.charge_id IS NOT NULL) AND (c.gl_rule_type_id <> ALL ('{60,70}'::integer[]))))
-> Index Scan using cost_invoice_charge_invoice_id_idx on cost_invoice_charge c (cost=0.43..1204.53 rows=1188 width=243) (actual time=0.467..2.664 rows=579 loops=1)
Index Cond: (invoice_id = 14517)
Filter: ((chg_amt <> '0'::numeric) AND ((gl_rule_type IS NULL) OR (gl_rule_type_id <> ALL ('{60,70}'::integer[]))))
Rows Removed by Filter: 3364
-> Index Scan using "gl_charge_detail.charge_id->cost_invoice_info_only.id" on gl_charge_detail cd (cost=0.42..7.28 rows=1 width=27) (actual time=0.002..0.002 rows=1 loops=579)
Index Cond: (c.id = charge_id)
-> Hash (cost=31.69..31.69 rows=969 width=64) (actual time=0.657..0.657 rows=969 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 103kB
-> Seq Scan on gl_strings gs (cost=0.00..31.69 rows=969 width=64) (actual time=0.026..0.389 rows=969 loops=1)
-> Materialize (cost=4.22..145.78 rows=72 width=44) (actual time=0.000..0.000 rows=1 loops=579)
-> Hash Left Join (cost=4.22..145.42 rows=72 width=44) (actual time=0.100..0.102 rows=1 loops=1)
Hash Cond: (f.vendor_id = vn.id)
-> Nested Loop (cost=0.57..141.57 rows=72 width=31) (actual time=0.027..0.029 rows=1 loops=1)
-> Index Scan using cost_invoice_header_id_idx on cost_invoice_header ch (cost=0.29..8.31 rows=1 width=4) (actual time=0.012..0.013 rows=1 loops=1)
Index Cond: (id = 14517)
Filter: (status_code <> ALL ('{100,101,102,490}'::integer[]))
-> Index Scan using "invoice_id+acct_level" on cost_invoice_facepage f (cost=0.29..132.55 rows=72 width=31) (actual time=0.013..0.013 rows=1 loops=1)
Index Cond: ((invoice_id = 14517) AND (acct_level = 1))
-> Hash (cost=2.73..2.73 rows=73 width=17) (actual time=0.061..0.061 rows=73 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on appdata_vendor_common vn (cost=0.00..2.73 rows=73 width=17) (actual time=0.020..0.038 rows=73 loops=1)
-> Hash Left Join (cost=2276.48..25607.26 rows=127269 width=374) (actual time=204.117..1486.717 rows=378550 loops=1)
Hash Cond: (f_1.vendor_id = vn_1.id)
-> Nested Loop Left Join (cost=2272.84..25250.14 rows=127269 width=361) (actual time=204.072..1328.491 rows=378550 loops=1)
-> Gather (cost=2272.55..24385.32 rows=2663 width=338) (actual time=204.055..335.965 rows=378550 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Hash Left Join (cost=1272.55..23119.02 rows=1110 width=338) (actual time=127.365..321.126 rows=126183 loops=3)
Hash Cond: (cdb.gl_string_id = gs_1.id)
-> Hash Join (cost=1228.74..23072.30 rows=1110 width=278) (actual time=126.126..263.315 rows=126183 loops=3)
Hash Cond: (cdb.charge_id = c_1.id)
-> Parallel Seq Scan on gl_charge_detail_ban cdb (cost=0.00..20581.15 rows=480915 width=43) (actual time=0.270..109.543 rows=384732 loops=3)
-> Hash (cost=1194.13..1194.13 rows=2769 width=239) (actual time=7.232..7.232 rows=3929 loops=3)
Buckets: 4096 Batches: 1 Memory Usage: 635kB
-> Index Scan using cost_invoice_charge_invoice_id_idx on cost_invoice_charge c_1 (cost=0.43..1194.13 rows=2769 width=239) (actual time=0.070..4.686 rows=3929 loops=3)
Index Cond: (invoice_id = 14517)
Filter: (chg_amt <> '0'::numeric)
Rows Removed by Filter: 14
-> Hash (cost=31.69..31.69 rows=969 width=64) (actual time=1.127..1.127 rows=969 loops=3)
Buckets: 1024 Batches: 1 Memory Usage: 103kB
-> Seq Scan on gl_strings gs_1 (cost=0.00..31.69 rows=969 width=64) (actual time=0.165..0.714 rows=969 loops=3)
-> Index Scan using "invoice_id+acct_level" on cost_invoice_facepage f_1 (cost=0.29..0.31 rows=1 width=31) (actual time=0.001..0.002 rows=1 loops=378550)
Index Cond: ((cdb.invoice_id = invoice_id) AND (acct_level = 1))
-> Hash (cost=2.73..2.73 rows=73 width=17) (actual time=0.035..0.035 rows=73 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on appdata_vendor_common vn_1 (cost=0.00..2.73 rows=73 width=17) (actual time=0.014..0.021 rows=73 loops=1)
Planning Time: 3.636 ms
Execution Time: 1550.844 ms
and
OPTION 2 WITHOUT INDEXES
Append (cost=48.58..43257.20 rows=163773 width=371) (actual time=7.965..831.408 rows=379129 loops=1)
-> Nested Loop (cost=48.58..12251.68 rows=36504 width=362) (actual time=7.965..14.476 rows=579 loops=1)
-> Hash Left Join (cost=44.66..9918.22 rows=507 width=322) (actual time=0.588..6.245 rows=579 loops=1)
Hash Cond: (cd.gl_string_id = gs.id)
-> Nested Loop Left Join (cost=0.85..9873.07 rows=507 width=262) (actual time=0.245..5.442 rows=579 loops=1)
Filter: ((c.gl_rule_type IS NULL) OR ((cd.charge_id IS NOT NULL) AND (c.gl_rule_type_id <> ALL ('{60,70}'::integer[]))))
-> Index Scan using cost_invoice_charge_invoice_id_idx on cost_invoice_charge c (cost=0.43..1204.53 rows=1188 width=243) (actual time=0.231..3.003 rows=579 loops=1)
Index Cond: (invoice_id = 14517)
Filter: ((chg_amt <> '0'::numeric) AND ((gl_rule_type IS NULL) OR (gl_rule_type_id <> ALL ('{60,70}'::integer[]))))
Rows Removed by Filter: 3364
-> Index Scan using "gl_charge_detail.charge_id->cost_invoice_info_only.id" on gl_charge_detail cd (cost=0.42..7.28 rows=1 width=27) (actual time=0.003..0.003 rows=1 loops=579)
Index Cond: (c.id = charge_id)
-> Hash (cost=31.69..31.69 rows=969 width=64) (actual time=0.331..0.331 rows=969 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 103kB
-> Seq Scan on gl_strings gs (cost=0.00..31.69 rows=969 width=64) (actual time=0.017..0.183 rows=969 loops=1)
-> Materialize (cost=3.93..1877.35 rows=72 width=44) (actual time=0.013..0.013 rows=1 loops=579)
-> Hash Left Join (cost=3.93..1876.99 rows=72 width=44) (actual time=7.370..7.698 rows=1 loops=1)
Hash Cond: (f.vendor_id = vn.id)
-> Nested Loop (cost=0.29..1873.14 rows=72 width=31) (actual time=7.307..7.635 rows=1 loops=1)
-> Index Scan using cost_invoice_header_id_idx on cost_invoice_header ch (cost=0.29..8.31 rows=1 width=4) (actual time=0.011..0.013 rows=1 loops=1)
Index Cond: (id = 14517)
Filter: (status_code <> ALL ('{100,101,102,490}'::integer[]))
-> Seq Scan on cost_invoice_facepage f (cost=0.00..1864.12 rows=72 width=31) (actual time=7.293..7.619 rows=1 loops=1)
Filter: ((invoice_id = 14517) AND (acct_level = 1))
Rows Removed by Filter: 40340
-> Hash (cost=2.73..2.73 rows=73 width=17) (actual time=0.045..0.045 rows=73 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on appdata_vendor_common vn (cost=0.00..2.73 rows=73 width=17) (actual time=0.022..0.028 rows=73 loops=1)
-> Hash Left Join (cost=4248.29..28548.92 rows=127269 width=374) (actual time=234.692..789.334 rows=378550 loops=1)
Hash Cond: (cdb.invoice_id = f_1.invoice_id)
-> Gather (cost=2272.55..24385.32 rows=2663 width=338) (actual time=216.507..376.349 rows=378550 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Hash Left Join (cost=1272.55..23119.02 rows=1110 width=338) (actual time=128.932..389.669 rows=126183 loops=3)
Hash Cond: (cdb.gl_string_id = gs_1.id)
-> Hash Join (cost=1228.74..23072.30 rows=1110 width=278) (actual time=127.984..308.092 rows=126183 loops=3)
Hash Cond: (cdb.charge_id = c_1.id)
-> Parallel Seq Scan on gl_charge_detail_ban cdb (cost=0.00..20581.15 rows=480915 width=43) (actual time=0.163..117.001 rows=384732 loops=3)
-> Hash (cost=1194.13..1194.13 rows=2769 width=239) (actual time=8.779..8.779 rows=3929 loops=3)
Buckets: 4096 Batches: 1 Memory Usage: 635kB
-> Index Scan using cost_invoice_charge_invoice_id_idx on cost_invoice_charge c_1 (cost=0.43..1194.13 rows=2769 width=239) (actual time=0.050..5.563 rows=3929 loops=3)
Index Cond: (invoice_id = 14517)
Filter: (chg_amt <> '0'::numeric)
Rows Removed by Filter: 14
-> Hash (cost=31.69..31.69 rows=969 width=64) (actual time=0.829..0.829 rows=969 loops=3)
Buckets: 1024 Batches: 1 Memory Usage: 103kB
-> Seq Scan on gl_strings gs_1 (cost=0.00..31.69 rows=969 width=64) (actual time=0.184..0.534 rows=969 loops=3)
-> Hash (cost=1804.87..1804.87 rows=13670 width=44) (actual time=18.101..18.101 rows=13705 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 1198kB
-> Hash Left Join (cost=3.64..1804.87 rows=13670 width=44) (actual time=0.075..14.009 rows=13705 loops=1)
Hash Cond: (f_1.vendor_id = vn_1.id)
-> Seq Scan on cost_invoice_facepage f_1 (cost=0.00..1763.26 rows=13670 width=31) (actual time=0.017..6.216 rows=13705 loops=1)
Filter: (acct_level = 1)
Rows Removed by Filter: 26636
-> Hash (cost=2.73..2.73 rows=73 width=17) (actual time=0.052..0.052 rows=73 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on appdata_vendor_common vn_1 (cost=0.00..2.73 rows=73 width=17) (actual time=0.013..0.027 rows=73 loops=1)
Planning Time: 3.365 ms
Execution Time: 863.941 ms
Look at the line that is driving the iteration over the index scan:
Gather (cost=2272.55..24385.32 rows=2663 width=338) (actual time=204.055..335.965 rows=378550 loops=1)
It thinks the index scan will get iterated 2663 times (with a different value of invoice_id for each one) but it really gets iterated 378550 times, (this latter number is where the 'loops' field on the index scan comes from), a difference of 140 fold. Every time you hit the index, you need to re-descend from the root to the leaf, locking and unlocking pages as you go. While this is not terribly expensive, it does add up if you do it 378550 times. It gets to be faster to process the table in bulk into a private hash table. But since the estimated row count is so wrong, PostgreSQL doesn't realize that in this case.

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

Very slow SELECT from partitioned table in PostgreSQL

I use Postgresql 9.5. The settings are default.
I have split a table into ~7000 partitions. Then I've inserted one row.
When I query SELECT * FROM "Offer";, it is running 1,5 seconds.
When I query SELECT * FROM "Offer" WHERE bid=4793;, where bid -- partition's constraint (one table per bid), it is running 1 second.
Here is an EXPLAIN ANALYZE for second query:
Append (cost=0.00..12.14 rows=2 width=596) (actual time=0.014..0.014 rows=1 loops=1)
-> Seq Scan on "Offer" (cost=0.00..1.01 rows=1 width=344) (actual time=0.011..0.011 rows=0 loops=1)
Filter: (bid = 4793)
Rows Removed by Filter: 1
-> Seq Scan on "Offer-4793" (cost=0.00..11.12 rows=1 width=848) (actual time=0.002..0.002 rows=1 loops=1)
Filter: (bid = 4793)
Planning time: 996.243 ms
Execution time: 0.261 ms
Why so slow? What can I use to profile it?
I have only one guess -- postgresql does not keep partitioning constrains in RAM and reads them from HDD every time.
Expecting some help!
UPDATE:
I've tried to create cascading partitioning (as #jmelesky has written). Results are worse:
Append (cost=0.00..12.24 rows=5 width=848) (actual time=0.013..0.013 rows=1 loops=1)
-> Seq Scan on "Offer" (cost=0.00..1.11 rows=1 width=848) (actual time=0.006..0.006 rows=0 loops=1)
Filter: (bid = 4793)
-> Seq Scan on "Offer-ddd-3" (cost=0.00..0.00 rows=1 width=848) (actual time=0.001..0.001 rows=0 loops=1)
Filter: (bid = 4793)
-> Seq Scan on "Offer-dd-33" (cost=0.00..0.00 rows=1 width=848) (actual time=0.000..0.000 rows=0 loops=1)
Filter: (bid = 4793)
-> Seq Scan on "Offer-d-336" (cost=0.00..0.00 rows=1 width=848) (actual time=0.000..0.000 rows=0 loops=1)
Filter: (bid = 4793)
-> Seq Scan on "Offer-4793" (cost=0.00..11.12 rows=1 width=848) (actual time=0.006..0.006 rows=1 loops=1)
Filter: (bid = 4793)
Planning time: 1449.872 ms
Execution time: 0.354 ms

postgresql optimize query analyze explain

Hi all postgresql experts. I am maintaining a web application I did neither conceive or develop, and there is a query that takes 5 minutes (yes minutes) to complete. As I am not familiar at all with PostgreSQL I have ran the "explain analyze" and here is the query and the output of the explain analyze. I would be very grateful if someone could explain ( :-) ) to me how to improve the query or just give me some pointers to understand the output :)
here is the query :
explain analyse SELECT a.salon as salonid, a.salon_ratachement,
(CASE WHEN a.salon IS NOT NULL THEN (select debut FROM salon WHERE id = a.salon) ELSE
a.hors_salon_date_debut END) as debut,
(CASE WHEN a.salon IS NOT NULL THEN (select fin FROM salon WHERE id = a.salon) ELSE a
.hors_salon_date_fin END) as fin,
(CASE WHEN a.salon IS NOT NULL THEN (select nom_fra FROM salon WHERE id = a.salon) WHEN a.salon_ratachement IS NOT NULL THEN (select nom_fra FROM salon WHERE id = a.salon_ratachement)||' (hors salon)' ELSE 'Hors salon' END ) as nom_salon,
a.id, a.reference, a.numero, ad.numero as numero_additif, a.jour_installation_souhaite,
ad.id as additifid, ad.statut_logistique, ad.etat, e.id as espaceid, e.code, e.nom,
c.id as clientid, c.nom as nom_client, c.lang
FROM client c, espace e, additif ad , affaire a, salon s
WHERE true and a.salon = '1237' and s.id = 1237
AND (ad.etat='NON CONFIRME' OR ad.etat='CONFIRME') and a.est_archive = 'false' AND (
ad.statut_logistique='a_preparer' OR ad.statut_logistique='en_preparation' OR ad.statut_logistique='a_installer' OR ad.statut_logistique='installe_partiellement' OR ad.statut_logistique='installe' OR ad.statut_logistique='desinstalle_partiellement' OR ad.statut_logistique='desinstalle') AND e.affaire=a.id AND a.client=c.id AND ad.affaire=a.id AND ad.espace=e.id
AND ((ad.id in (select lc.additif from ligne_commande lc, prestation p where lc.additif=ad .id and lc.prestation=p.id )) OR (ad.statut_logistique_force='t')) ORDER BY (case when a.salon is not null then a.salon::varchar||'b' when a.salon_ratachement is not null then a.salon_ratachement::varchar||'a' else '0' end) desc, a.id desc, ad.id;
and here is the explain output :
QUERY PLAN
Sort (cost=16051791.56..16051791.57 rows=1 width=143) (actual time=281189.406..281189.406 rows=5 loops=1)
Sort Key: (CASE WHEN (a.salon IS NOT NULL) THEN (((a.salon)::character varying)::text || 'b'::text) WHEN (a.salon_ratachement IS NOT NULL) THEN (((a.salon_ratachement)::character varying)::text || 'a'::text) ELSE '0'::text END), a.id, ad.id
Sort Method: quicksort Memory: 26kB
-> Nested Loop (cost=889.24..16051791.55 rows=1 width=143) (actual time=56077.822..281189.319 rows=5 loops=1)
Join Filter: (a.id = e.affaire)
-> Nested Loop (cost=889.24..16051757.76 rows=2 width=112) (actual time=56077.716..281188.945 rows=5 loops=1)
Join Filter: (a.id = ad.affaire)
-> Nested Loop (cost=0.00..961.46 rows=1 width=77) (actual time=0.096..8.279 rows=5 loops=1)
-> Nested Loop (cost=0.00..953.19 rows=1 width=77) (actual time=0.084..8.225 rows=5 loops=1)
-> Index Scan Backward using affaire_pkey on affaire a (cost=0.00..944.91 rows=1 width=56) (actual time=0.065..8.121 rows=5 loops=1)
Filter: ((NOT est_archive) AND (salon = 1237))
-> Index Scan using client_pkey on client c (cost=0.00..8.27 rows=1 width=25) (actual time=0.015..0.016 rows=1 loops=5)
Index Cond: (c.id = a.client)
-> Index Scan using salon_pkey on salon s (cost=0.00..8.27 rows=1 width=0) (actual time=0.007..0.009 rows=1 loops=5)
Index Cond: (s.id = 1237)
-> Bitmap Heap Scan on additif ad (cost=889.24..16050626.95 rows=13548 width=35) (actual time=4.506..56229.985 rows=17173 loops=5)
Recheck Cond: ((((ad.statut_logistique)::text = 'a_preparer'::text) OR ((ad.statut_logistique)::text = 'en_preparation'::text) OR ((ad.statut_logistique)::text = 'a_installer'::text) OR ((ad.statut_logistique)::text = 'installe_partiellement'::text) OR ((ad.statut_logistique)::text = 'installe'::text) OR ((ad.statut_logistique)::text = 'desinstalle_partiellement'::text) OR ((ad.statut_logistique)::text = 'desinstalle'::text)) AND (((ad.etat)::text = 'NON CONFIRME'::text) OR ((ad.etat)::text = 'CONFIRME'::text)))
Filter: ((SubPlan 5) OR ad.statut_logistique_force)
-> BitmapAnd (cost=889.24..889.24 rows=17129 width=0) (actual time=4.173..4.173 rows=0 loops=5)
-> BitmapOr (cost=443.27..443.27 rows=17304 width=0) (actual time=2.013..2.013 rows=0 loops=5)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..4.46 rows=27 width=0) (actual time=0.032..0.032 rows=51 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'a_preparer'::text)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..4.46 rows=27 width=0) (actual time=0.006..0.006 rows=4 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'en_preparation'::text)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..46.17 rows=1856 width=0) (actual time=0.245..0.245 rows=1783 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'a_installer'::text)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..4.46 rows=27 width=0) (actual time=0.004..0.004 rows=1 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'installe_partiellement'::text)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..23.84 rows=1012 width=0) (actual time=0.118..0.118 rows=985 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'installe'::text)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..4.46 rows=27 width=0) (actual time=0.012..0.012 rows=49 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'desinstalle_partiellement'::text)
-> Bitmap Index Scan on statut_logistique_key (cost=0.00..331.73 rows=14330 width=0) (actual time=1.594..1.594 rows=14329 loops=5)
Index Cond: ((ad.statut_logistique)::text = 'desinstalle'::text)
-> BitmapOr (cost=445.71..445.71 rows=18458 width=0) (actual time=2.116..2.116 rows=0 loops=5)
-> Bitmap Index Scan on etat_key (cost=0.00..4.27 rows=2 width=0) (actual time=0.011..0.011 rows=2 loops=5)
Index Cond: ((ad.etat)::text = 'NON CONFIRME'::text)
-> Bitmap Index Scan on etat_key (cost=0.00..434.67 rows=18456 width=0) (actual time=2.105..2.105 rows=18409 loops=5)
Index Cond: ((ad.etat)::text = 'CONFIRME'::text)
SubPlan 5
-> Nested Loop (cost=0.00..1873.85 rows=3 width=4) (actual time=3.271..3.271 rows=1 loops=85885)
-> Seq Scan on ligne_commande lc (cost=0.00..1849.01 rows=3 width=8) (actual time=3.264..3.264 rows=1 loops=85885)
Filter: (additif = $4)
-> Index Scan using prestation_pkey on prestation p (cost=0.00..8.27 rows=1 width=4) (actual time=0.005..0.005 rows=1 loops=85790)
Index Cond: (p.id = lc.prestation)
-> Index Scan using espace_pkey on espace e (cost=0.00..0.34 rows=1 width=43) (actual time=0.025..0.026 rows=1 loops=5)
Index Cond: (e.id = ad.espace)
SubPlan 1
-> Index Scan using salon_pkey on salon (cost=0.00..8.27 rows=1 width=4) (actual time=0.007..0.009 rows=1 loops=5)
Index Cond: (id = $0)
SubPlan 2
-> Index Scan using salon_pkey on salon (cost=0.00..8.27 rows=1 width=4) (actual time=0.002..0.003 rows=1 loops=5)
Index Cond: (id = $1)
SubPlan 3
-> Index Scan using salon_pkey on salon (cost=0.00..8.27 rows=1 width=14) (actual time=0.002..0.003 rows=1 loops=5)
Index Cond: (id = $2)
SubPlan 4
-> Index Scan using salon_pkey on salon (cost=0.00..8.27 rows=1 width=14) (never executed)
Index Cond: (id = $3)
Total runtime: 281189.828 ms
Thank you very much for your help !