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 1w2dHY-000SdB-0k for pgsql-performance@arkaria.postgresql.org; Tue, 17 Mar 2026 22:53:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w2dHW-005tQU-0l for pgsql-performance@arkaria.postgresql.org; Tue, 17 Mar 2026 22:52: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 1w2dHV-005tQM-2u for pgsql-performance@lists.postgresql.org; Tue, 17 Mar 2026 22:52:57 +0000 Received: from mail-qk1-x72d.google.com ([2607:f8b0:4864:20::72d]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1w2dHT-00000000ivK-0rnU for pgsql-performance@lists.postgresql.org; Tue, 17 Mar 2026 22:52:57 +0000 Received: by mail-qk1-x72d.google.com with SMTP id af79cd13be357-8cd80bea5f3so406224185a.3 for ; Tue, 17 Mar 2026 15:52:55 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1773787973; cv=none; d=google.com; s=arc-20240605; b=MYwjJ26u6oPsj4XWgzTVagdCkjjVeOKgqIaOvgblVm871zxHJ1Jbv2+NrG9LQxSgu5 3LjlxYfxjj5weqhFcl4QUUjU3FEZIe8MOFFSIkQgBDldVv9CmWEj4BT1ccwmJrMNiFoc T6T36z1cetCPBfh1mRJT1FfQgtkbk4I/kXVGgKT9KyD9wmzsnrI195OpZIi8J1VQPA/i bHjYsNaS9Ba0R5fsREjQs4qoLmAiPJQp4aZrYVxjczDMNCvMcQnX7Ccn2pK27eJQyFyw 8ik+8fXlZlIgajmQ0KM21KWQU5F223zKEHh7mvOpqOOwziwK2ul7FXUeRAwzuOd4l2LH dbsg== 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=rQTechJvZ3CcO1NnUzvuncO4UaC/mT/V4uLdqdEbEik=; fh=bBO5IEP2K3ipP2nZ+PKu7NMf5WTitnTnFlfiACKzh/w=; b=ZjRQPg122OiCaYyENh47bvv9fvW3vg+PJr/KCog8DbWZqzin0NDADxBHt/BKr+Aah5 uOCGFndA1vcKM6TNAiFhdiyljHoKesbDTyNPTr9Zctv27zZONdRKPyoADYEVcCIJ2ene EVAOBl7W5SfmEpRHZhM9H/ZwiF8nsJTWZeQriHlKUROCjf8ISSyNgiKTAoRQ0EeWoV7L PAGtycTaAgEKJUI/D/0VipVZuTpmY+pkpdzBVIIbAe+Ws/MFuMk5OaJnZtOr0s0kpsS3 LQlAvZD+MbHmqJ2clJwPvOwXE1xzOGoElHH9flUjlrEL0xjqO0kF1hhJaZ6cWHCfrJ83 7SHg==; 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=20230601; t=1773787973; x=1774392773; 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=rQTechJvZ3CcO1NnUzvuncO4UaC/mT/V4uLdqdEbEik=; b=PBGdDpgKpl6wRGjB/Ur+b/mI0maH3Jln2Ijt0AEGDUGQmrTpgCt1DUWBA9BGodYIC6 VDuZbpLHnjf6J6xcJfikz1b18NaAiyzlcasIr05y7wrvoLbobGTA1RzhFfoXnyuBn6Ez Gi1F9IrcjOGh5OX6YGXdpOP+geH0eHlzfQbppH7cr1XPJEs9bmsM90maDjS/8yyisqq0 Uo9guHP2gWCGxGoadPAUgxbXSC5ACZKRjRwzrJQd0H4vreturl0ZwRV6WvK17NPifToj VFvwz2xF3xzGj/+LPmS8Cj8vg9OFmOaqDjSl7LL08C1GM1jgJISj5j6JQzB8dqIAI+DC tEBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773787973; x=1774392773; 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=rQTechJvZ3CcO1NnUzvuncO4UaC/mT/V4uLdqdEbEik=; b=gxmJ1jNFHe71cIcSKpAUClYSo9Q4N3gomCmO5G9qmt8G7zKT8oA+MBAlD3y68QPnoF 7ooNpUQ6QumwJUTay1rmG5OQuVWb0NDCWW0jqYVUlOGzyUhpMJJrzN+nRR7ZsOoFF4+O j2YFlyRVJPiQDDKSyDRT/OH3odC2ic9+8pH7CPIXZK1j5iI8BLQjMkPa4UDa7mI15eeK warE4YF7DaKnyfwn+ko7LbT/AM7PvJJFJ2gi8/yytVBLbHz9k8n4ty8cl/DbDiUY6Wss JqVsUIiswd/AR5JcjjchPPPUU+fUfqcyuKOYKzTraHZjtSePNXUiFSN5ckqovOt2d6Bv Rc0A== X-Gm-Message-State: AOJu0YymWCFUFVTPhNUtGlwzkjWVTZF2C9wuS4T8snU8LV0ZXaLnjegk U9VFrVISt2lYPdsPc3kPOw6G5Vkb2THxuaC1jqWjeh9Ii8eL+bLgoygRuyE9NaNS7To/waak/hR Jv5OXw6zWkFGN72N+MXXpX5HJmfFYV+s= X-Gm-Gg: ATEYQzyBS/aHt3qxsGK/XSSlay/ic64d0yz5m8+t8NL6B1qk/TrujPU7HPMxgI2eyaM Eml0LM2W5OioRSxNCgNC7zYK/ZxBmvHKSFiY1vdiF3vraExodiEJBHNYPhQzk29VJe4Veiha26n b1JYQgcV0bHe0w0ZceT+op80BITJ2BFYOulpaUtbpQxToi/hhp3ZPyOTKmXqlIAArPzvtHxsuId c/lZAT2LWJjbCh+aTbzRttoq3R8GsfxK6cW/A0QzGaSZkZJTvxp/rY2Zci+yWFSwNAUTSYlu499 AxRD7xtNyDEv4voCH9ZqVMc0oJhBu/AiSsRTUa8tNWKpO1OLyNo8YHr+qp72RnzHS+A9cuQS X-Received: by 2002:a05:620a:254c:b0:8cd:c078:1afe with SMTP id af79cd13be357-8cfad26f4c4mr201970485a.29.1773787973411; Tue, 17 Mar 2026 15:52:53 -0700 (PDT) MIME-Version: 1.0 References: <574349.1773786268@sss.pgh.pa.us> In-Reply-To: <574349.1773786268@sss.pgh.pa.us> From: Merlin Moncure Date: Tue, 17 Mar 2026 16:52:42 -0600 X-Gm-Features: AaiRm51K8zFlZjNYm20ECI7ifjfmLIE64Ymb-VjvEL7gkgLcBo1LxGxXt-XFehY Message-ID: Subject: Re: postgres chooses objectively wrong index To: Tom Lane , Alexey Ermakov Cc: pgsql-performance@lists.postgresql.org Content-Type: multipart/alternative; boundary="0000000000000fe717064d4032bc" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000000fe717064d4032bc Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Tue, Mar 17, 2026 at 4:16=E2=80=AFPM Alexey Ermakov wrote: > On 2026-03-18 03:01, Merlin Moncure wrote: > > I've been maintaining an airflow style orchestrator in pl/pgsql, and it's > revealed a performance issue I just can't solve. There is a table, task, > which may normally contain billions of rows, but only a tiny portion is > interesting for specific reasons=E2=80=94a common pattern in task-type sy= stems. > > ... > > I'm wondering if there are other tricks that might apply here, for > example, multi column index statistics...curious if anyone has thoughts o= n > that. > > Any suggestions? > > merlin > > Hello. I think planner doesn't have information about distribution of > *async.task_execution_state(task)* unless it's part of any full index. I > would try to give that with extended statistics (postgresql 14+): > > create statistics (mcv) task_task_execution_state_stat on ((async.task_ex= ecution_state(task))) from async.task; > analyze async.task; > > If that won't help - please show distribution from pg_stats_ext view for > extended statistic above. > This unfortunately fails, probably because the table type includes system columns (despite not using them). orchestrator_service_user@orchestrator=3D> create statistics task_stats (mcv) on (async.task_execution_state(task)) from async.task; ERROR: statistics creation on system columns is not supported This would require some refactoring to fix. On Tue, Mar 17, 2026 at 4:24=E2=80=AFPM Tom Lane wrote: > Merlin Moncure writes: > > I've been maintaining an airflow style orchestrator in pl/pgsql, and it= 's > > revealed a performance issue I just can't solve. There is a table, tas= k, > > which may normally contain billions of rows, but only a tiny portion is > > interesting for specific reasons=E2=80=94a common pattern in task-type = systems. > > ... > > > Usually, we get a plan that looks like this: > > > 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 > rows=3D0 loops=3D1) > > > Sometimes, based on a certain data distribution, we get results like > this: > > > Limit (cost=3D25.75..25.78 rows=3D10 width=3D563) (actual time=3D8.909= ..8.911 > rows=3D0 loops=3D1) > > -> Sort (cost=3D25.75..26.20 rows=3D179 width=3D563) (actual > time=3D8.908..8.909 rows=3D0 loops=3D1) > > I think the fundamental problem here is that the planner is estimating > 179 matching rows when the true count is 0. Getting that estimate > down by, say, an order of magnitude would probably fix your issue. > However, if the selectivity is already epsilon (are there really > billions of rows?) it may be hard to get it down to a smaller epsilon. > What statistics target are you using? > Potentially yes. Maybe 40m in this particular database. It's set to default, so it isn't very precise. Is my earlier point correct, though? No distribution of data should prefer that plan (barring some low row count seqscan stuff)? Let's say the row count was 179 rows, it would make no difference in the disparity (in fact, it'd probably be worse). Simplified, the query is: SELECT * FROM foo WHERE a=3D? AND b=3DK ORDER BY c, d LIMIT N; CREATE INDEX ON foo(a,b,c) WHERE b=3DK; why choose any other index? I was guessing mcv stats problem, but this can be proved out without stats IMO. > How often do tasks change state? > This is typical FIFO task processing system, pgmq, etc, with a huge number of processed rows. and a small number of "processing" rows that get staged and then complete. Loads are highly transient; unprocessed rows may surge up to millions before trending to zero. This naturally puts a lot of stress on statistics. Tasks often resolve in seconds or minutes, depending on depth of queue. Could it be reasonable to partition the task table on state, rather than > rely on an index? I've thought about this; the basic issue is that the flow module extends async.task with a BEFORE trigger. This can be worked around but not easily. This is my drop back and punt option, but I'm curious if there is an underlying solve here. merlin --0000000000000fe717064d4032bc Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
On = Tue, Mar 17, 2026 at 4:16=E2=80=AFPM Alexey Ermakov <alexius.work@gmail.com> wrote:
On 2026-03-18= 03:01, Merlin Moncure wrote:
I've been maintaining an airflow style o= rchestrator in pl/pgsql, and it's revealed a performance issue I just c= an't solve.=C2=A0 There is a table, task, which may normally=C2=A0conta= in billions of rows, but only a tiny portion is interesting for specific re= asons=E2=80=94a common pattern in task-type systems.

..= .

I'm wondering if there are other tricks that m= ight apply here, for example, multi column index statistics...curious if an= yone has thoughts on that.

Any suggestions?
<= div>
merlin

H= ello. I think planner doesn't have information about distribution of=C2= =A0async.task_execution_state(task)=C2=A0unless it's part of any= full index. I would try to give that with extended statistics (postgresql = 14+):

create statistics (mcv) task_task_execution_state_stat on=
 ((async.task_execution_state(task))) from async.task;
analyze async.task;

If that won't help - please show distributi= on from pg_stats_ext view for extended statistic above.


This unfortunately fails, probably because the t= able type includes system columns (despite not using them).
=C2= =A0
orchestrator_service_user@orchestrator=3D> create statisti= cs =C2=A0task_stats (mcv) on (async.task_execution_state(task)) from async.= task;
ERROR: =C2=A0statistics creation on system columns is not supporte= d

This would require some refactoring to fix.=C2= =A0

On Tue, Mar 17, 2026 at 4:24=E2=80=AFPM Tom = Lane <tgl@sss.pgh.pa.us> wro= te:
Merlin Moncu= re <mmoncure@gma= il.com> writes:
> I've been maintaining an airflow style orchestrator in pl/pgsql, a= nd it's
> revealed a performance issue I just can't solve.=C2=A0 There is a = table, task,
> which may normally contain billions of rows, but only a tiny portion i= s
> interesting for specific reasons=E2=80=94a common pattern in task-type= systems.
> ...

> Usually, we get a plan that looks like this:

>=C2=A0 Limit=C2=A0 (cost=3D0.38..39.74 rows=3D10 width=3D563) (actual t= ime=3D0.054..0.054 rows=3D0 loops=3D1)
>=C2=A0 =C2=A0 ->=C2=A0 Index Scan using task_concurrency_pool_priori= ty_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)

> Sometimes, based on a certain data distribution, we get results like t= his:

> Limit=C2=A0 (cost=3D25.75..25.78 rows=3D10 width=3D563) (actual time= =3D8.909..8.911 rows=3D0 loops=3D1)
>=C2=A0 =C2=A0->=C2=A0 Sort=C2=A0 (cost=3D25.75..26.20 rows=3D179 wid= th=3D563) (actual time=3D8.908..8.909 rows=3D0 loops=3D1)

I think the fundamental problem here is that the planner is estimating
179 matching rows when the true count is 0.=C2=A0 Getting that estimate
down by, say, an order of magnitude would probably fix your issue.
However, if the selectivity is already epsilon (are there really
billions of rows?) it may be hard to get it down to a smaller epsilon.
What statistics target are you using?

P= otentially yes.=C2=A0 Maybe 40m in this particular database.

=
It's set to default, so it isn't very precise.=C2=A0 Is = my earlier point correct, though? No distribution of data should prefer tha= t plan (barring some low row count seqscan stuff)?=C2=A0 Let's say the = row count was 179 rows, it would make no difference in the disparity (in fa= ct, it'd probably be worse).

Simplified, the q= uery is:
SELECT * FROM foo WHERE a=3D? AND b=3DK ORDER BY c, d LI= MIT N;
CREATE INDEX ON foo(a,b,c) WHERE b=3DK;

why choose any other index? I was guessing mcv stats problem, but th= is can be proved out without stats IMO.
=C2=A0
How often do tasks change state?=C2=A0=C2=A0

This is typical FIFO task processing system, pgmq, etc, with=C2=A0a h= uge number of processed rows. and a small number of "processing" = rows that get staged and then complete.=C2=A0 Loads are highly transient; u= nprocessed rows may surge up to millions before trending to zero.=C2=A0 =C2= =A0This naturally puts a lot of stress on statistics.=C2=A0 =C2=A0Tasks oft= en resolve in seconds or minutes, depending on depth of queue.=C2=A0
<= div>
Could = it be reasonable to partition the task table on state, rather than rely on = an index?

I've thought about this= ; the basic issue is that the flow module extends async.task with a BEFORE = trigger.=C2=A0 This can be worked around but not easily.=C2=A0 =C2=A0This i= s my drop back and punt option, but I'm curious if there is an underlyi= ng solve here.

merlin
=C2=A0
=
--0000000000000fe717064d4032bc--