Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1w5RUV-003FsF-0w for pgsql-performance@arkaria.postgresql.org; Wed, 25 Mar 2026 16:53:59 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w5RUT-00FKRR-2H for pgsql-performance@arkaria.postgresql.org; Wed, 25 Mar 2026 16:53:58 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1w5RUT-00FKRI-1B for pgsql-performance@lists.postgresql.org; Wed, 25 Mar 2026 16:53:57 +0000 Received: from mail-qt1-x82b.google.com ([2607:f8b0:4864:20::82b]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1w5RUR-000000016Nb-05ZK for pgsql-performance@lists.postgresql.org; Wed, 25 Mar 2026 16:53:57 +0000 Received: by mail-qt1-x82b.google.com with SMTP id d75a77b69052e-5090c7e9081so1097911cf.0 for ; Wed, 25 Mar 2026 09:53:55 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1774457633; cv=none; d=google.com; s=arc-20240605; b=K3/V4LQ+4vd7/rwdaHvd62eUTF53z0QB/ogritKUqnTMzQXYE9rK0LOxIStzcVM67M 2HKly/+bHXBim6K6AwEZv7ByNUkVfRFyaVhftxb9we8KcjP22gsTXHIz4fgOlezH3g0L r51bYSHUF5q6OY2VYSwtX8Sl/OUneR3HgDlLs9XHduvAYw2KNyXqADp6jL2nN2LWAykQ oDsqhz9vTFXhm+5yKxcdp9cNhIWn0DCz8GipsiOWsMki6HnDmIhS4FrGAc9CmUJy7PaW e4fFo87h6OI28wfMqjMyzcOReUMUi0kDI+OuQi1gzuaRr6nXramtvwEyIwNZxriIYbgG oy/w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=zcgYWoU7KE1Mwvgt6QQHgTBD/mG5raw+Ts8C6mZ1PFk=; fh=qnnAIhMmubzLIaH6NlJvQaCr10bHxo2HNP6RGTLzzZY=; b=hWctDVl/mr3++xRoRyyTJo6xlBmJYrlxptDZflTv4IDDw6I5dwJ9RBz0o6Ebi4kXeP qjI9jnDmazcYf14ksJsIfKjQO9tIXFwS95U0t3I9hVmok71hPuWeVPnVL5G7VfmLWxIe fy/60QZY5tZ+hmN2qLtY02ebGXINr7uG6pvvQyvA5C/grDb8elr4mwR6MUjhzbPd1b1y efjde5zv4ChYAzri02MergNve4iHRZ/UEMqOTxeGoEXeFTwEVv99fWsOZxgBYKCzpIqW XHZaoAe+LvAropL81wJ2EJ3TFZxCFIU6jYG62at9p/s0ROm+PLGAzZcpQlwGUhpbJV5l YZtg==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774457633; x=1775062433; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=zcgYWoU7KE1Mwvgt6QQHgTBD/mG5raw+Ts8C6mZ1PFk=; b=RmC+kZ2WbH0t86JCqiQfeVJdpfw5RJjsR2QkQH3rIcjXbcfmNaLKEPqWzMmiN+/yIq DnrVzAoqE+PIRD431CsvcBQLHqMtohMjnOwJDItuMObIyah0GzNtZocJZojnsHmkdEYA YCIc7gm7teAvuFvmnK/NuscVUqFmGMT3QZ1I5SJEfXvdhgexRrj7FBkxW2o2kNXfVciF tGlriZ+/yJ8V5nYwnkaoJWq5yxIfhQTK2rYMbkQt4Ztwt7LmTg4CbW2RRRTLEdUvWQdB p7FnYe4obbg+MOk0UTSmp8s7nfsaKIYNr0s9fb3kkLLllunN3pNc3uWiho5dgR8I6eFy TfPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774457633; x=1775062433; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=zcgYWoU7KE1Mwvgt6QQHgTBD/mG5raw+Ts8C6mZ1PFk=; b=hAw75aAWmZr5TWN//aGTW/XM3Xfhu00A8yq8IC8vxI7N7czLzOGE+CaeCJXv5Y76o9 u7o01Ym/0TD5ddYCuyfGJ9pCC4aZ03RvWsYC5Ih/aA7z+nkiPJFh9TrW4EYW7M/UbFnf 5HDeYcmBsK93SKeglaMZz0yGBGl6qS6tsG9U9M/XTY0u2t1MJAtmYzZamB/W/6s9GvAq X176gCpR4yD31V5QCcK+Ux/f/NkQw/F9KuI0txcjHWGMlkt1FGBCVwuSX11IuGcqatLX jKFpf3wgzJ42arpeCU5efEXyHA5AUhOMBUU73ANWSV1pCeoarC3i80zocsdDMCahFCU4 Dfsg== X-Forwarded-Encrypted: i=1; AJvYcCUNTFVc/1CYpE+yjlW4iAumnkApsPBxbhJ/YmxpzC/O/iEnMY37sbeZU3ZorgX+kgDWuJLQSCoLdy3VPunbvedF4A==@lists.postgresql.org X-Gm-Message-State: AOJu0YzEpM0LOWhnXZgbE2YUNXJDm2Mn4BDx09cNfF6bX3rE37HmoaMX ajdVoaPE2GIlBNZYQoMI9Rw3moctmGCZcbE1OE+lIgROF4YFJJf+swpKZOLyB2SWplv5ib+jP46 xplEk1XezGZ1Yf4hzjKacSlX1gyA6DYk= X-Gm-Gg: ATEYQzyUb3voww4EcWOKTHfTyB8iwmjtM4YB4liWHOlLYh2hHWj5nULqo0eHidjWp1n 7tZN2VpJ9BEhxoBl+7wtKjTsF7uqaTUzar+fxIJMjt1/wdJnMla8FcvNnZOMOjIJTk+qROJ3m5N ypf5R8VOIsFj/FYtVKmgIe/23ALAy2Jb/K1kzaWf3s6pRxGEgWiNWU+q6PzHF7St5D6CgI1krWo RiVtAVBO0CzxgkX1JyRKho6xSnS1qvmeBKx75b0APNrp887hMXwxSmXR8qbZ9STkyuCtWg38hJJ JWnkScZEBa2/AqJY8ff28NxAqRuu9zHcnKM8BM+DOzwL4FmjE1MiG88cKlT7OQl3aH962++r8Q= = X-Received: by 2002:ac8:5991:0:b0:50b:22ad:6197 with SMTP id d75a77b69052e-50b80ccbc64mr64021571cf.11.1774457633181; Wed, 25 Mar 2026 09:53:53 -0700 (PDT) MIME-Version: 1.0 References: <574349.1773786268@sss.pgh.pa.us> <381e7d47-8021-458d-9a64-0cdb80b004f0@gmail.com> <2a32c870-3f69-4b09-854d-ffea03b1e537@gmail.com> <217AB71B-F368-4DA1-A3B5-ABED94E1BD0A@gmail.com> In-Reply-To: <217AB71B-F368-4DA1-A3B5-ABED94E1BD0A@gmail.com> From: Merlin Moncure Date: Wed, 25 Mar 2026 10:53:42 -0600 X-Gm-Features: AQROBzBlV8HcG7piLQqdoFExH9CN_7_ov0lbLP9ERn1HnLVaePzzsXy7XgAQ0kY Message-ID: Subject: Re: postgres chooses objectively wrong index To: Andrei Lepikhov Cc: Alexey Ermakov , Tom Lane , pgsql-performance@lists.postgresql.org Content-Type: multipart/alternative; boundary="000000000000e52deb064ddc1c43" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000e52deb064ddc1c43 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Tue, Mar 24, 2026 at 3:21=E2=80=AFAM Andrei Lepikhov = wrote: > > > > On 23 Mar 2026, at 22:58, Merlin Moncure wrote: > > On Thu, Mar 19, 2026 at 1:09=E2=80=AFAM Andrei Lepikhov > wrote: > > Poking around more, I see that the bad plans are related to bloat. A > simple REINDEX of one of the indexes made the problem disappear; however, > what's odd is that the estimates didn't really change although the net pl= an > cost certainly did. It's also worth noting ANALYZE doesn't help, only > REINDEX does. > > We already have plan-freezing and plan-hinting extensions. A couple of thoughts here. Re: plan-hinting, I'm a multi-decade disciple of the philosophy: "You are not smarter than the planner," "We need performance feedback on plans to improve planning," and "Do not exclude yourself from potential new types of plans," as offered many years ago by a significant poster on this thread. So, if I were to use it, it would be more for exploration and debugging. As things stand, I get by mostly by manipulating queries and the very occasional GUC (read: disable nestloop). Freezing the plan, however, seems to be essential functionality. My loose observation is that postgres plannner stability has generally declined over time, causing many production outages. Many reasons exist for this, including my personal tendency to design around optimal outcomes; this thread is a pretty good example of that. The various rules for preparing and managing plans are generally good but require more precise control in specific situations. Plan management ought to be in core, perhaps even at the syntax level. I say this because extensions like these are generally written in C and not offered by cloud providers, which highly limits their audience. This is why pgasync was written; it's a souped up pgbackground / pgmq written entirely at the SQL level, requiring only dblink which is now generally offered. > If I understand you correctly, it makes sense to invent a > statistics-freezing module right now. I think such a module will be quite > simple - is it a good crutch for you? > Also, we have some stuff already to work out your case someday: > Interesting question. I suppose a simple plan lock should be enough, but I'm not sure about that. We might be slightly off here though, my point is that the chosen plan seems indefensible even with the supplied statistics? Specifically, pre-REINDEX, postgres thinks that this plan: -> Bitmap Heap Scan on task (cost=3D9.10..21.89 rows=3D179 width= =3D563) (actual time=3D8.902..8.903 rows=3D0 loops=3D1) Recheck Cond: ((async.task_execution_state(task.*) =3D ANY ('{READY,RUNNING,YIELDED}'::async.task_execution_state_t[])) AND (concurrency_pool =3D 'xyz'::text) AND (async.task_execution_state(task.*) = =3D 'READY'::async.task_execution_state_t)) -> BitmapAnd (cost=3D9.10..9.10 rows=3D3 width=3D0) (actual time=3D8.883..8.883 rows=3D0 loops=3D1) -> Bitmap Index Scan on task_task_id_idx (cost=3D0.00..4.38 rows=3D575191 width=3D0) (actual time=3D8.828..8.828 ro= ws=3D16 loops=3D1) -> Bitmap Index Scan on task_concurrency_pool_priority_entered_idx (cost=3D0.00..4.38 rows=3D179 width=3D0) (actual time=3D0.053..0.053 rows=3D0 loops=3D1) Index Cond: (concurrency_pool =3D 'xyz'::text) is better than this plan: Limit (cost=3D0.38..39.74 rows=3D10 width=3D563) (actual time=3D0.054..0.= 054 rows=3D0 loops=3D1) -> Index Scan using task_concurrency_pool_priority_entered_idx on task (cost=3D0.38..705.08 rows=3D179 width=3D563) (actual time=3D0.053..0.053 r= ows=3D0 loops=3D1) Index Cond: (concurrency_pool =3D 'xyz'::text) which is something I just don't understand. After reindexing task_task_id_idx, things cleared up, and both plans ran well, which I also don't understand. ISTM a plan lock ought to keep things buttoned up though= . > 1. Postgres already scans indexes during planning to improve estimations > of inequality clauses (get_actual_variable_range). Here may be a way to > estimate the bloat effect. Not sure how to do it, but allowing index AM t= o > read the page number of the returned tuple, you might, in principle, dete= ct > anomalies in the index. > 2. We are quite close to vacuum statistics and detailed index statistics. > This is also a way to estimate issues of stale statistics/bloated indexes > and decide on the scan type. > > So, keep the community posted and provide more real-life examples to buil= d > up a proper solution. > very much appreciate your insight. merlin --000000000000e52deb064ddc1c43 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
On Tue, Mar 24, 2026 at 3:21=E2=80=AFAM A= ndrei Lepikhov <lepihov@gmail.com> wrote:


> On 23 Mar 2026, at 22:58, Merlin Moncure <
mmoncure@gmail.com> wrote:
> On Thu, Mar 19, 2026 at 1:09=E2=80=AFAM Andrei Lepikhov <lepihov@gmail.com> wrot= e:
> Poking around more, I see that the bad plans are related to bloat.=C2= =A0 =C2=A0A simple REINDEX of one of the indexes made the problem disappear= ; however, what's odd is that the estimates didn't really change al= though the net plan cost certainly did.=C2=A0 It's also worth noting AN= ALYZE doesn't help, only REINDEX does.

We already have plan-freezing and plan-hinting extensions.
A couple of thoughts here. Re: plan-hinting, I'm a mul= ti-decade disciple of the philosophy: "You are not smarter than the pl= anner," "We need performance feedback on plans to improve plannin= g," and "Do not exclude yourself from potential new types of plan= s," as offered many years ago by a significant poster on this thread.= =C2=A0 So, if I were to use it, it would be more for exploration and debugg= ing. As things stand, I get by mostly by manipulating queries and the very = occasional GUC (read: disable nestloop).

Freezing = the plan, however, seems to be essential functionality.=C2=A0 My loose obse= rvation is that postgres plannner stability has generally declined over tim= e, causing many production outages.=C2=A0 Many reasons exist for this, incl= uding my personal tendency to design around optimal outcomes; this thread i= s a pretty good example of that.=C2=A0=C2=A0

The v= arious rules for preparing and managing plans are generally good but requir= e more precise control in specific situations. Plan management ought to be = in core, perhaps even at the syntax level.=C2=A0 I say this because extensi= ons like these are generally written in C and not offered by cloud provider= s, which highly limits their audience.=C2=A0 This is why=C2=A0pgasync was written; it'= ;s a souped up pgbackground / pgmq written entirely at the SQL level, requi= ring only dblink which is now generally offered.
=C2=A0
If I understand you correctly= , it makes sense to invent a statistics-freezing module right now. I think = such a module will be quite simple - is it a good crutch for you?
Also, we have some stuff already to work out your case someday:

Interesting question.=C2=A0 I suppose a simple pla= n lock should be enough, but I'm not sure about that.=C2=A0 =C2=A0We mi= ght be slightly off here though, my point is that the chosen plan seems ind= efensible even with the supplied statistics?=C2=A0 Specifically, pre-REINDE= X, postgres thinks that this plan:

=C2=A0 =C2=A0 = =C2=A0 =C2=A0 -> =C2=A0Bitmap Heap Scan on task =C2=A0(cost=3D9.10..21.8= 9 rows=3D179 width=3D563) (actual time=3D8.902..8.903 rows=3D0 loops=3D1)=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Recheck Cond: ((async.ta= sk_execution_state(task.*) =3D ANY ('{READY,RUNNING,YIELDED}'::asyn= c.task_execution_state_t[])) AND (concurrency_pool =3D 'xyz'::text)= AND (async.task_execution_state(task.*) =3D 'READY'::async.task_ex= ecution_state_t))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ->= =C2=A0BitmapAnd =C2=A0(cost=3D9.10..9.10 rows=3D3 width=3D0) (actual time= =3D8.883..8.883 rows=3D0 loops=3D1)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 -> =C2=A0Bitmap Index Scan on task_ta= sk_id_idx =C2=A0(cost=3D0.00..4.38 rows=3D575191 width=3D0) (actual time=3D= 8.828..8.828 rows=3D16 loops=3D1)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 -> =C2=A0Bitmap Index Scan on task_concu= rrency_pool_priority_entered_idx =C2=A0(cost=3D0.00..4.38 rows=3D179 width= =3D0) (actual time=3D0.053..0.053 rows=3D0 loops=3D1)
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 I= ndex Cond: (concurrency_pool =3D 'xyz'::text)

<= div>is better than this plan:

=C2=A0Limit =C2=A0(c= ost=3D0.38..39.74 rows=3D10 width=3D563) (actual time=3D0.054..0.054 rows= =3D0 loops=3D1)
=C2=A0 =C2=A0-> =C2=A0Index Scan using task_concurren= cy_pool_priority_entered_idx on task =C2=A0(cost=3D0.38..705.08 rows=3D179 = width=3D563) (actual time=3D0.053..0.053 rows=3D0 loops=3D1)
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0Index Cond: (concurrency_pool =3D 'xyz'::te= xt)

which is something I just don't understand= .=C2=A0 After reindexing task_task_id_idx, things cleared up, and both plan= s ran well, which I also don't understand.=C2=A0 ISTM a plan lock ought= to keep things buttoned up though.
=C2=A0
1. Postgres already scans indexes during planning to improve estimations of= inequality clauses (get_actual_variable_range). Here may be a way to estim= ate the bloat effect. Not sure how to do it, but allowing index AM to read = the page number of the returned tuple, you might, in principle, detect anom= alies in the index.
2. We are quite close to vacuum statistics and detailed index statistics. T= his is also a way to estimate issues of stale statistics/bloated indexes an= d decide on the scan type.

So, keep the community posted and provide more real-life examples to build = up a proper solution.

very much appreci= ate your insight.

merlin=C2=A0
--000000000000e52deb064ddc1c43--