Hi Baji, Etsuro,

Reproduced both repro scripts on two PG18.4 builds (same REL_18_4 tag, one
unpatched, one with just the costsize.c hunk). Output attached.

repro_plain.sql: plan shapes match your report exactly (Hash Join -> Nested
Loop+Memoize).

repro_e2e.sql: plan_cache_mode=auto, no config changes --
unpatched: generic_plans=0, custom_plans=10, ~65 ms/call
patched:   generic_plans=5, custom_plans=5, ~1.8 ms/call

Confirms the undercosted custom plan was defeating the plan cache's own
generic-vs-custom check.

Thanks,
Nazneen


On Thu, Oct 22, 2026 at 6:32 AM Baji Shaik <[email protected]> wrote:

> On Thu, Oct 22, 2026 at 6:47 AM Etsuro Fujita <[email protected]>
> wrote:
>
>> I agree that this is for master-only as well.  Could you add it to the
>> upcoming commitfest.  I'd like to review it next month.
>
>
> Thank you for looking into this, Etsuro.
> Added to the commitfest: https://commitfest.postgresql.org/patch/7502/
>
> Thanks,
> Baji Shaik.
>
psql:repro_e2e.sql:19: NOTICE:  function pc_f(pg_catalog.int4[],timestamptz) 
does not exist, skipping
psql:repro_e2e.sql:20: NOTICE:  table "pc_ev" does not exist, skipping
psql:repro_e2e.sql:20: NOTICE:  table "pc_lk" does not exist, skipping
PREPARE

=== 10 executions with plan_cache_mode = auto ===
 generic_plans | custom_plans 
---------------+--------------
             5 |            5
(1 row)

=== plan used under auto ===
                                                                         QUERY 
PLAN                                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=50100.95..50100.96 rows=1 width=8) (actual time=2.011..2.013 
rows=1.00 loops=1)
   ->  Nested Loop  (cost=4996.31..50100.50 rows=180 width=9) (actual 
time=0.036..1.976 rows=500.00 loops=1)
         ->  Nested Loop  (cost=4996.02..49964.29 rows=180 width=4) (actual 
time=0.030..0.914 rows=500.00 loops=1)
               ->  Function Scan on unnest g  (cost=0.00..0.10 rows=10 width=4) 
(actual time=0.009..0.016 rows=100.00 loops=1)
               ->  Limit  (cost=4996.01..4996.06 rows=18 width=12) (actual 
time=0.008..0.008 rows=5.00 loops=100)
                     ->  Sort  (cost=4996.01..4996.06 rows=18 width=12) (actual 
time=0.007..0.008 rows=5.00 loops=100)
                           Sort Key: e.ts DESC
                           Sort Method: quicksort  Memory: 25kB
                           ->  Bitmap Heap Scan on pc_ev e  
(cost=43.47..4995.64 rows=18 width=12) (actual time=0.003..0.005 rows=5.00 
loops=100)
                                 Recheck Cond: (gid = g.id)
                                 Filter: ((ts <= $2) AND (ts >= ($2 - '90 
days'::interval)))
                                 Heap Blocks: exact=372
                                 ->  Bitmap Index Scan on pc_ev_gid_idx  
(cost=0.00..43.47 rows=3606 width=0) (actual time=0.002..0.002 rows=5.00 
loops=100)
                                       Index Cond: (gid = g.id)
                                       Index Searches: 100
         ->  Memoize  (cost=0.30..6.98 rows=1 width=13) (actual 
time=0.002..0.002 rows=1.00 loops=500)
               Cache Key: e.lk_id
               Cache Mode: logical
               Hits: 0  Misses: 500  Evictions: 0  Overflows: 0  Memory Usage: 
57kB
               ->  Index Scan using pc_lk_pkey on pc_lk k  (cost=0.29..6.97 
rows=1 width=13) (actual time=0.001..0.001 rows=1.00 loops=500)
                     Index Cond: (id = e.lk_id)
                     Index Searches: 500
(22 rows)

=== generic plan ===
SET
                                                                         QUERY 
PLAN                                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=50100.95..50100.96 rows=1 width=8) (actual time=1.988..1.989 
rows=1.00 loops=1)
   ->  Nested Loop  (cost=4996.31..50100.50 rows=180 width=9) (actual 
time=0.033..1.952 rows=500.00 loops=1)
         ->  Nested Loop  (cost=4996.02..49964.29 rows=180 width=4) (actual 
time=0.028..0.917 rows=500.00 loops=1)
               ->  Function Scan on unnest g  (cost=0.00..0.10 rows=10 width=4) 
(actual time=0.009..0.016 rows=100.00 loops=1)
               ->  Limit  (cost=4996.01..4996.06 rows=18 width=12) (actual 
time=0.008..0.008 rows=5.00 loops=100)
                     ->  Sort  (cost=4996.01..4996.06 rows=18 width=12) (actual 
time=0.007..0.008 rows=5.00 loops=100)
                           Sort Key: e.ts DESC
                           Sort Method: quicksort  Memory: 25kB
                           ->  Bitmap Heap Scan on pc_ev e  
(cost=43.47..4995.64 rows=18 width=12) (actual time=0.003..0.005 rows=5.00 
loops=100)
                                 Recheck Cond: (gid = g.id)
                                 Filter: ((ts <= $2) AND (ts >= ($2 - '90 
days'::interval)))
                                 Heap Blocks: exact=372
                                 ->  Bitmap Index Scan on pc_ev_gid_idx  
(cost=0.00..43.47 rows=3606 width=0) (actual time=0.002..0.002 rows=5.00 
loops=100)
                                       Index Cond: (gid = g.id)
                                       Index Searches: 100
         ->  Memoize  (cost=0.30..6.98 rows=1 width=13) (actual 
time=0.002..0.002 rows=1.00 loops=500)
               Cache Key: e.lk_id
               Cache Mode: logical
               Hits: 0  Misses: 500  Evictions: 0  Overflows: 0  Memory Usage: 
57kB
               ->  Index Scan using pc_lk_pkey on pc_lk k  (cost=0.29..6.97 
rows=1 width=13) (actual time=0.001..0.001 rows=1.00 loops=500)
                     Index Cond: (id = e.lk_id)
                     Index Searches: 500
(22 rows)

RESET
CREATE FUNCTION

=== SQL function, auto: 10 calls, then timed 3 ===
Timing is on.
 pc_f 
------
  500
(1 row)

Time: 1.895 ms
 pc_f 
------
  500
(1 row)

Time: 1.827 ms
 pc_f 
------
  500
(1 row)

Time: 1.827 ms
Timing is off.
=== SQL function, force_generic_plan: timed 3 ===
ALTER FUNCTION
Timing is on.
 pc_f 
------
  500
(1 row)

Time: 2.126 ms
 pc_f 
------
  500
(1 row)

Time: 1.837 ms
 pc_f 
------
  500
(1 row)

Time: 1.822 ms
Timing is off.
psql:repro_plain.sql:4: NOTICE:  table "pc_ev" does not exist, skipping
psql:repro_plain.sql:4: NOTICE:  table "pc_lk" does not exist, skipping
                                                                                
         QUERY PLAN                                                             
                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=53215.44..53215.45 rows=1 width=8) (actual time=2.187..2.189 
rows=1.00 loops=1)
   InitPlan 1
     ->  Function Scan on generate_series g_1  (cost=0.00..1.25 rows=100 
width=4) (actual time=0.012..0.021 rows=100.00 loops=1)
   ->  Nested Loop  (cost=5247.50..53201.74 rows=4978 width=9) (actual 
time=0.104..2.150 rows=500.00 loops=1)
         ->  Nested Loop  (cost=5247.20..52584.61 rows=5000 width=4) (actual 
time=0.095..1.017 rows=500.00 loops=1)
               ->  Function Scan on unnest g  (cost=0.00..0.10 rows=10 width=4) 
(actual time=0.037..0.045 rows=100.00 loops=1)
               ->  Limit  (cost=5247.20..5248.45 rows=500 width=12) (actual 
time=0.008..0.009 rows=5.00 loops=100)
                     ->  Sort  (cost=5247.20..5256.60 rows=3758 width=12) 
(actual time=0.008..0.008 rows=5.00 loops=100)
                           Sort Key: e.ts DESC
                           Sort Method: quicksort  Memory: 25kB
                           ->  Bitmap Heap Scan on pc_ev e  
(cost=45.55..5059.94 rows=3758 width=12) (actual time=0.004..0.006 rows=5.00 
loops=100)
                                 Recheck Cond: (gid = g.id)
                                 Filter: ((ts <= '2026-03-31 
00:00:00+00'::timestamp with time zone) AND (ts >= ('2026-03-31 
00:00:00+00'::timestamp with time zone - '90 days'::interval)))
                                 Heap Blocks: exact=372
                                 ->  Bitmap Index Scan on pc_ev_gid_idx  
(cost=0.00..44.61 rows=3758 width=0) (actual time=0.002..0.002 rows=5.00 
loops=100)
                                       Index Cond: (gid = g.id)
                                       Index Searches: 100
         ->  Memoize  (cost=0.30..0.99 rows=1 width=13) (actual 
time=0.002..0.002 rows=1.00 loops=500)
               Cache Key: e.lk_id
               Cache Mode: logical
               Hits: 0  Misses: 500  Evictions: 0  Overflows: 0  Memory Usage: 
57kB
               ->  Index Scan using pc_lk_pkey on pc_lk k  (cost=0.29..0.98 
rows=1 width=13) (actual time=0.002..0.002 rows=1.00 loops=500)
                     Index Cond: (id = e.lk_id)
                     Index Searches: 500
 Planning Time: 0.386 ms
 Execution Time: 2.224 ms
(26 rows)

psql:repro_plain.sql:4: NOTICE:  table "pc_ev" does not exist, skipping
psql:repro_plain.sql:4: NOTICE:  table "pc_lk" does not exist, skipping
                                                                                
               QUERY PLAN                                                       
                                         
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=7447.03..7447.04 rows=1 width=8) (actual 
time=132.407..132.410 rows=1.00 loops=1)
   InitPlan 1
     ->  Function Scan on generate_series g_1  (cost=0.00..1.25 rows=100 
width=4) (actual time=0.012..0.021 rows=100.00 loops=1)
   ->  Nested Loop  (cost=5283.18..7433.28 rows=5000 width=9) (actual 
time=0.348..132.359 rows=500.00 loops=1)
         ->  Function Scan on unnest g  (cost=0.00..0.10 rows=10 width=4) 
(actual time=0.038..0.052 rows=100.00 loops=1)
         ->  Hash Join  (cost=5283.17..5493.17 rows=500 width=9) (actual 
time=0.164..1.321 rows=5.00 loops=100)
               Hash Cond: (k.id = e.lk_id)
               ->  Seq Scan on pc_lk k  (cost=0.00..155.00 rows=10000 width=13) 
(actual time=0.001..0.577 rows=10000.00 loops=100)
               ->  Hash  (cost=5276.92..5276.92 rows=500 width=4) (actual 
time=0.012..0.012 rows=5.00 loops=100)
                     Buckets: 1024  Batches: 1  Memory Usage: 9kB
                     ->  Subquery Scan on e  (cost=5270.67..5276.92 rows=500 
width=4) (actual time=0.009..0.011 rows=5.00 loops=100)
                           ->  Limit  (cost=5270.67..5271.92 rows=500 width=12) 
(actual time=0.009..0.010 rows=5.00 loops=100)
                                 ->  Sort  (cost=5270.67..5280.20 rows=3812 
width=12) (actual time=0.009..0.009 rows=5.00 loops=100)
                                       Sort Key: e_1.ts DESC
                                       Sort Method: quicksort  Memory: 25kB
                                       ->  Bitmap Heap Scan on pc_ev e_1  
(cost=45.97..5080.73 rows=3812 width=12) (actual time=0.005..0.007 rows=5.00 
loops=100)
                                             Recheck Cond: (gid = g.id)
                                             Filter: ((ts <= '2026-03-31 
00:00:00+00'::timestamp with time zone) AND (ts >= ('2026-03-31 
00:00:00+00'::timestamp with time zone - '90 days'::interval)))
                                             Heap Blocks: exact=372
                                             ->  Bitmap Index Scan on 
pc_ev_gid_idx  (cost=0.00..45.01 rows=3812 width=0) (actual time=0.003..0.003 
rows=5.00 loops=100)
                                                   Index Cond: (gid = g.id)
                                                   Index Searches: 100
 Planning Time: 0.432 ms
 Execution Time: 132.450 ms
(24 rows)

psql:repro_e2e.sql:19: NOTICE:  function pc_f(pg_catalog.int4[],timestamptz) 
does not exist, skipping
psql:repro_e2e.sql:20: NOTICE:  table "pc_ev" does not exist, skipping
psql:repro_e2e.sql:20: NOTICE:  table "pc_lk" does not exist, skipping
PREPARE

=== 10 executions with plan_cache_mode = auto ===
 generic_plans | custom_plans 
---------------+--------------
             0 |           10
(1 row)

=== plan used under auto ===
                                                                                
               QUERY PLAN                                                       
                                         
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=26814.72..26814.73 rows=1 width=8) (actual 
time=132.005..132.008 rows=1.00 loops=1)
   ->  Nested Loop  (cost=5188.72..26689.72 rows=50000 width=9) (actual 
time=0.258..131.959 rows=500.00 loops=1)
         ->  Function Scan on unnest g  (cost=0.00..1.00 rows=100 width=4) 
(actual time=0.012..0.040 rows=100.00 loops=1)
         ->  Hash Join  (cost=5188.72..5398.72 rows=500 width=9) (actual 
time=0.164..1.317 rows=5.00 loops=100)
               Hash Cond: (k.id = e.lk_id)
               ->  Seq Scan on pc_lk k  (cost=0.00..155.00 rows=10000 width=13) 
(actual time=0.001..0.571 rows=10000.00 loops=100)
               ->  Hash  (cost=5182.47..5182.47 rows=500 width=4) (actual 
time=0.011..0.011 rows=5.00 loops=100)
                     Buckets: 1024  Batches: 1  Memory Usage: 9kB
                     ->  Subquery Scan on e  (cost=5176.22..5182.47 rows=500 
width=4) (actual time=0.009..0.010 rows=5.00 loops=100)
                           ->  Limit  (cost=5176.22..5177.47 rows=500 width=12) 
(actual time=0.009..0.010 rows=5.00 loops=100)
                                 ->  Sort  (cost=5176.22..5185.23 rows=3606 
width=12) (actual time=0.009..0.009 rows=5.00 loops=100)
                                       Sort Key: e_1.ts DESC
                                       Sort Method: quicksort  Memory: 25kB
                                       ->  Bitmap Heap Scan on pc_ev e_1  
(cost=44.37..4996.54 rows=3606 width=12) (actual time=0.004..0.007 rows=5.00 
loops=100)
                                             Recheck Cond: (gid = g.id)
                                             Filter: ((ts <= '2026-03-31 
00:00:00+00'::timestamp with time zone) AND (ts >= ('2026-03-31 
00:00:00+00'::timestamp with time zone - '90 days'::interval)))
                                             Heap Blocks: exact=372
                                             ->  Bitmap Index Scan on 
pc_ev_gid_idx  (cost=0.00..43.47 rows=3606 width=0) (actual time=0.002..0.002 
rows=5.00 loops=100)
                                                   Index Cond: (gid = g.id)
                                                   Index Searches: 100
(20 rows)

=== generic plan ===
SET
                                                                         QUERY 
PLAN                                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=50100.95..50100.96 rows=1 width=8) (actual time=2.184..2.186 
rows=1.00 loops=1)
   ->  Nested Loop  (cost=4996.31..50100.50 rows=180 width=9) (actual 
time=0.043..2.148 rows=500.00 loops=1)
         ->  Nested Loop  (cost=4996.02..49964.29 rows=180 width=4) (actual 
time=0.033..0.952 rows=500.00 loops=1)
               ->  Function Scan on unnest g  (cost=0.00..0.10 rows=10 width=4) 
(actual time=0.011..0.019 rows=100.00 loops=1)
               ->  Limit  (cost=4996.01..4996.06 rows=18 width=12) (actual 
time=0.008..0.009 rows=5.00 loops=100)
                     ->  Sort  (cost=4996.01..4996.06 rows=18 width=12) (actual 
time=0.008..0.008 rows=5.00 loops=100)
                           Sort Key: e.ts DESC
                           Sort Method: quicksort  Memory: 25kB
                           ->  Bitmap Heap Scan on pc_ev e  
(cost=43.47..4995.64 rows=18 width=12) (actual time=0.003..0.006 rows=5.00 
loops=100)
                                 Recheck Cond: (gid = g.id)
                                 Filter: ((ts <= $2) AND (ts >= ($2 - '90 
days'::interval)))
                                 Heap Blocks: exact=372
                                 ->  Bitmap Index Scan on pc_ev_gid_idx  
(cost=0.00..43.47 rows=3606 width=0) (actual time=0.002..0.002 rows=5.00 
loops=100)
                                       Index Cond: (gid = g.id)
                                       Index Searches: 100
         ->  Memoize  (cost=0.30..6.98 rows=1 width=13) (actual 
time=0.002..0.002 rows=1.00 loops=500)
               Cache Key: e.lk_id
               Cache Mode: logical
               Hits: 0  Misses: 500  Evictions: 0  Overflows: 0  Memory Usage: 
57kB
               ->  Index Scan using pc_lk_pkey on pc_lk k  (cost=0.29..6.97 
rows=1 width=13) (actual time=0.002..0.002 rows=1.00 loops=500)
                     Index Cond: (id = e.lk_id)
                     Index Searches: 500
(22 rows)

RESET
CREATE FUNCTION

=== SQL function, auto: 10 calls, then timed 3 ===
Timing is on.
 pc_f 
------
  500
(1 row)

Time: 65.080 ms
 pc_f 
------
  500
(1 row)

Time: 65.069 ms
 pc_f 
------
  500
(1 row)

Time: 65.067 ms
Timing is off.
=== SQL function, force_generic_plan: timed 3 ===
ALTER FUNCTION
Timing is on.
 pc_f 
------
  500
(1 row)

Time: 2.402 ms
 pc_f 
------
  500
(1 row)

Time: 1.862 ms
 pc_f 
------
  500
(1 row)

Time: 1.802 ms
Timing is off.

Reply via email to