Slow plan choice with prepared query - Mailing list pgsql-performance

From Mark Saward
Subject Slow plan choice with prepared query
Date
Msg-id 6a1865a0-0379-d575-a7d7-87b01852d997@manse.cloud
Whole thread Raw
Responses Re: Slow plan choice with prepared query  (MichaelDBA <MichaelDBA@sqlexec.com>)
List pgsql-performance
Hi,


I've experienced a situation where the planner seems to make a very poor 
choice with a prepared query after the first five executions.  Looking 
at the documentation, I think this happens because it switches from a 
custom plan to a generic one, and doesn't make a good choice for the 
generic one.

Postgres version: running in docker, reports to be 'Debian 14.1-1.pgdg110+1'

If I force it to use a custom plan via 'set local plan_cache_mode = 
force_custom_plan', then I don't notice any slowdown.  Without it, the 
6th and onwards calls can take 1 second to 15 seconds each, as opposed 
to about 10ms.

Since I have a workaround, I don't necessarily need assistance, but 
posting this here in case it's of value as a test case. Here's a test 
case that reliably duplicates this issue for me:

----

create table test (
   test_id serial primary key,
   data text
);

insert into test (data) (select data from (select 
generate_series(1,10000) AS id, md5(random()::text) AS data) x);

prepare foo_test(text, text, int, text, bool) as SELECT * FROM (SELECT
   *,
   count(*) OVER () > $3 AS has_more,
   row_number() OVER ()
   FROM (
     WITH counted AS (
       SELECT count(*) AS total
       FROM   (select test_id::text, data
from test
where
   (cast($1 as text) is null or lower(data) like '%' || lower($1) || '%')
and
   (cast($2 as text) is null or lower(test_id::text) like '%' || 
lower($2) || '%')) base
     ), cursor_row AS (
       SELECT base.test_id
       FROM   (select test_id::text, data
from test
where
   (cast($1 as text) is null or lower(data) like '%' || lower($1) || '%')
and
   (cast($2 as text) is null or lower(test_id::text) like '%' || 
lower($2) || '%')) base
       WHERE  base.test_id = $4
     )
     SELECT counted.*, base.*
       FROM   (select test_id::text, data
from test
where
   (cast($1 as text) is null or lower(data) like '%' || lower($1) || '%')
and
   (cast($2 as text) is null or lower(test_id::text) like '%' || 
lower($2) || '%')) base
       LEFT JOIN   cursor_row ON true
       LEFT JOIN   counted ON true
       WHERE ((
             $4 IS NULL OR cast($5 as bool) IS NULL
           ) OR (
             (base.test_id)
               > (cursor_row.test_id)
           ))
       ORDER BY base.test_id ASC
       LIMIT $3 + 1
) xy LIMIT $3 ) z ORDER BY row_number ASC;

\timing

execute foo_test(null, null, 5, 500, true);
execute foo_test(null, null, 5, 500, true);
execute foo_test(null, null, 5, 500, true);
execute foo_test(null, null, 5, 500, true);
execute foo_test(null, null, 5, 500, true);

-- This one should be slower:
execute foo_test(null, null, 5, 500, true);




pgsql-performance by date:

Previous
From: Justin Pryzby
Date:
Subject: Re: An I/O error occurred while sending to the backend (PG 13.4)
Next
From: Ranier Vilela
Date:
Subject: Re: An I/O error occurred while sending to the backend (PG 13.4)