Re: Distinct performance dropped by multiple times in v16

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 6/10/24 13:59, Vitaliy Litovskiy wrote:
) tbl2 on tbl1.token = tbl2.token Observations:
1. query runs for 4-5 seconds on v16 and less than a second on v15 2. in v16 it also goes downs to less than a second if 2.1 distinct is removed

2.2 unnest is removed. it is not really needed for this particular data but this query is autogenerated and unnest makes sense for other data

2.3 "order by token" is uncommented, this is my current way of fixing the problem I would really appreciate some feedback if that is expected behaviour and if there are better solutions
The reason for this behaviour is simple: commit 3c6fc58 allowed using incremental_sort with DISTINCT clauses.
So, in PostgreSQL 16 we have two concurrent strategies:
1. HashJoin + hashAgg at the end
2, NestLoop, which derives sort order from the index scan and planner can utilise IncrementalSort+Unique instead of full sort.

Disabling Incremental Sort (see explain 2) we get good plan with HashJoin at the top. Now we can see, that HashJoin definitely cheaper according to total cost, but has a big startup cost. Optimiser compares cost of incremental cost and, compare to hash agg it is much cheaper because of a reason.

Here we already have a couple of questions:
1. Why optimiser overestimates in such a simple situation.
2. Why in the case of big numbers of tuples incremental sort is better than hashAgg?

Before the next step just see how optimiser decides in the case of correct prediction. I usually use the AQO extension for that. Executing the query twice with the AQO in 'learn' mode we have correct plannedrows number in each node of the plan and, as you can see in EXPLAIN 3, optimiser chooses good strategy. So, having correct predictions, optimiser ends up with optimal plan.

Origins of overestimation lie in internals of the unnest, it is not obvious so far and may be discovered later. The reason, why optimiser likes NestLoop on big data looks enigmatic. Attempting to increase a cost of unnest routing from 1 to 1E5 we don't see any changes in decision. In my opinion, the key reason here may be triggered by unusual width of the JOIN result:
Hash Join  (cost=0.24..0.47 rows=10 width=0)
In my opinion, the cost model can provide too low cost of the join and it is a reason why upper NestLoop looks better than HashJoin.

I don't have any general recommendations to resolve this issue, but this case should be discovered by the core developers.

[1] https://github.com/postgrespro/aqo

--
regards,
Andrei Lepikhov
Postgres Professional
 EXPLAIN (1)
 ===========
 
 Unique  (cost=10170.87..94163004.90 rows=64000000 width=24) (actual time=1062.753..285758.085 rows=8000 loops=1)
   ->  Incremental Sort  (cost=10170.87..89363004.90 rows=640000000 width=24) (actual time=1062.751..285756.251 rows=8000 loops=1)
         Sort Key: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_Terretories"."BDN_Terretories_ID", "BDN_EmployeeTerritories"."Reference_Date"
         Presorted Key: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID"
         Full-sort Groups: 250  Sort Method: quicksort  Average Memory: 27kB  Peak Memory: 27kB
         ->  Nested Loop  (cost=0.52..29242165.28 rows=640000000 width=24) (actual time=0.103..285748.981 rows=8000 loops=1)
               ->  Index Scan using "PK_BDN_EmployeeTerritories" on "BDN_EmployeeTerritories"  (cost=0.28..285.28 rows=8000 width=20) (actual time=0.030..9.174 rows=8000 loops=1)
               ->  Nested Loop  (cost=0.24..2855.24 rows=80000 width=8) (actual time=18.264..35.716 rows=1 loops=8000)
                     ->  Seq Scan on "BDN_Terretories"  (cost=0.00..155.00 rows=8000 width=12) (actual time=0.002..0.754 rows=8000 loops=8000)
                     ->  Hash Join  (cost=0.24..0.47 rows=10 width=0) (actual time=0.003..0.003 rows=0 loops=64000000)
                           Hash Cond: (s.token = s_1.token)
                           ->  Function Scan on unnest s  (cost=0.01..0.11 rows=10 width=32) (actual time=0.000..0.000 rows=1 loops=64000000)
                           ->  Hash  (cost=0.11..0.11 rows=10 width=32) (actual time=0.002..0.002 rows=1 loops=64000000)
                                 Buckets: 1024  Batches: 1  Memory Usage: 9kB
                                 ->  Function Scan on unnest s_1  (cost=0.01..0.11 rows=10 width=32) (actual time=0.001..0.001 rows=1 loops=64000000)
 Planning Time: 1.023 ms
 Execution Time: 285758.551 ms
 
 EXPLAIN (2)
 ===========

SET enable_incremental_sort = off;
 HashAggregate  (cost=97605201.00..113245201.00 rows=64000000 width=24)
   Group Key: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_Terretories"."BDN_Terretories_ID", "BDN_EmployeeTerritories"."Reference_Date"
   Planned Partitions: 256
   ->  Hash Join  (cost=3246.00..7205201.00 rows=640000000 width=24)
         Hash Cond: (s_1.token = s.token)
         ->  Nested Loop  (cost=250.00..2005.00 rows=80000 width=40)
               ->  Seq Scan on "BDN_Terretories"  (cost=0.00..155.00 rows=8000 width=12)
               ->  Function Scan on unnest s_1  (cost=250.00..250.10 rows=10 width=32)
         ->  Hash  (cost=1996.00..1996.00 rows=80000 width=48)
               ->  Nested Loop  (cost=250.00..1996.00 rows=80000 width=48)
                     ->  Seq Scan on "BDN_EmployeeTerritories"  (cost=0.00..146.00 rows=8000 width=20)
                     ->  Function Scan on unnest s  (cost=250.00..250.10 rows=10 width=32)

EXPLAIN (3)
===========

 Unique  (cost=76139.64..76219.64 rows=8000 width=24) (actual time=38.034..39.457 rows=8000 loops=1)
   Output: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_Terretories"."BDN_Terretories_ID", "BDN_EmployeeTerritories"."Reference_Date"
   ->  Sort  (cost=76139.64..76159.64 rows=8000 width=24) (actual time=38.033..38.302 rows=8000 loops=1)
         Output: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_Terretories"."BDN_Terretories_ID", "BDN_EmployeeTerritories"."Reference_Date"
         Sort Key: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_Terretories"."BDN_Terretories_ID", "BDN_EmployeeTerritories"."Reference_Date"
         Sort Method: quicksort  Memory: 693kB
         ->  Hash Join  (cost=1846.01..75621.01 rows=8000 width=24) (actual time=18.806..36.903 rows=8000 loops=1)
               Output: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_Terretories"."BDN_Terretories_ID", "BDN_EmployeeTerritories"."Reference_Date"
               Hash Cond: (s_1.token = s.token)
               ->  Nested Loop  (cost=0.01..1755.01 rows=8000 width=40) (actual time=0.057..15.617 rows=8000 loops=1)
                     Output: "BDN_Terretories"."BDN_Terretories_ID", s_1.token
                     ->  Seq Scan on public."BDN_Terretories"  (cost=0.00..155.00 rows=8000 width=12) (actual time=0.026..0.789 rows=8000 loops=1)
                           Output: "BDN_Terretories"."BDN_Terretories_ID", "BDN_Terretories"."Title", "BDN_Terretories"."Description", "BDN_Terretories"."Reference_Date", "BDN_Terretories"."TerretoryID", "BDN_Terretories"."EMP_TerretoryID", "BDN_Terretor
ies"."RegionID_545", "BDN_Terretories"."xpl_isUpdated"
                     ->  Function Scan on pg_catalog.unnest s_1  (cost=0.01..0.11 rows=10 width=32) (actual time=0.001..0.001 rows=1 loops=8000)
                           Output: s_1.token
                           Function Call: unnest(string_to_array("BDN_Terretories"."EMP_TerretoryID", ';'::text))
               ->  Hash  (cost=1746.01..1746.01 rows=8000 width=48) (actual time=18.733..18.734 rows=8000 loops=1)
                     Output: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_EmployeeTerritories"."Reference_Date", s.token
                     Buckets: 8192  Batches: 1  Memory Usage: 477kB
                     ->  Nested Loop  (cost=0.01..1746.01 rows=8000 width=48) (actual time=0.020..16.994 rows=8000 loops=1)
                           Output: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_EmployeeTerritories"."Reference_Date", s.token
                           ->  Seq Scan on public."BDN_EmployeeTerritories"  (cost=0.00..146.00 rows=8000 width=20) (actual time=0.011..0.826 rows=8000 loops=1)
                                 Output: "BDN_EmployeeTerritories"."BDN_EmployeeTerritories_ID", "BDN_EmployeeTerritories"."Title", "BDN_EmployeeTerritories"."Description", "BDN_EmployeeTerritories"."Reference_Date", "BDN_EmployeeTerritories"."EMP_Terret
oryID", "BDN_EmployeeTerritories"."EmployeeID", "BDN_EmployeeTerritories"."xpl_isUpdated"
                           ->  Function Scan on pg_catalog.unnest s  (cost=0.01..0.11 rows=10 width=32) (actual time=0.002..0.002 rows=1 loops=8000)
                                 Output: s.token
                                 Function Call: unnest(string_to_array("BDN_EmployeeTerritories"."EMP_TerretoryID", ';'::text))
 Planning Time: 1.055 ms
 Execution Time: 39.810 ms

[Postgresql General]     [Postgresql PHP]     [PHP Users]     [PHP Home]     [PHP on Windows]     [Kernel Newbies]     [PHP Classes]     [PHP Books]     [PHP Databases]     [Yosemite]

  Powered by Linux