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 1wTeLk-000fK0-0b for pgsql-hackers@arkaria.postgresql.org; Sun, 31 May 2026 11:29: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 1wTeLh-007ORX-0a for pgsql-hackers@arkaria.postgresql.org; Sun, 31 May 2026 11:28:57 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wTeLg-007ORO-1k for pgsql-hackers@lists.postgresql.org; Sun, 31 May 2026 11:28:57 +0000 Received: from mail-ed1-x531.google.com ([2a00:1450:4864:20::531]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wTeLa-00000000PQc-0XTI for pgsql-hackers@postgresql.org; Sun, 31 May 2026 11:28:55 +0000 Received: by mail-ed1-x531.google.com with SMTP id 4fb4d7f45d1cf-68c3421b009so2654827a12.1 for ; Sun, 31 May 2026 04:28:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1780226929; cv=none; d=google.com; s=arc-20240605; b=jTYob/uBiZV8GxQ+Jbld7MUOntSdhnPAIpt2V9/KJaH78KkN7oxqYSyD9OTqqlD5xJ alYmM9cbnxiz+tv3ojiv9SkYv1fq0QeNaZmfRUeW0Pe10pW2d2f1qOcNPEzyYPtDALW6 VNtkV3Fxo1KkkI265y3puQ2br+6KVJ2mzU70S//pRMhNU7klT4oGcEGMWTLtRNchXXZ6 C+9hzBr9IaSqZmRSmhI4rw9iFRfXRH+TGNXLsej6xGKIyj7iQ6Dr245qETZ6qoouq6gz y3OBdQNXoUBSRFHschb2LdKeEcIL0WfS+9Hrohcd5JIfv6OeUkE+EMkevcmZtiZU8cJ+ GVzA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=LKmZ3MmduZ/Bph6FDtrcHK5dQcmzAfRXhEbCetQvvXQ=; fh=p6zUls+H67Iawdcu+Sk5r2CVYopcVUjXwtdSuo2ShUI=; b=S94ybq3KZUa2gPFRspyWoeoAa8drZvdImhRo7tCJMePgBZsuTozZaK6hqwKpo4WcNQ 7rrpkKd/0RSNUAazc7ENS9o6K2azNGxGU1fMKy8mNcogbGCfxLwvgin9k7QuTNWBMvPw FNiN04A00Y1/mwEDWnzV5eiWx3xXlQSZAnpk45VURwkt3g7gd5Ho0RJ2OcMi4tyIa0w3 nzPpCsN14ufgOtWUNJnbbpToaCSihgQp1W6ZM0cCpwUkLZD5yyiPJQSpl08mTZ0rb5Tg eRln1S6Gfkoa0WabfBvo9t7hyer0N+Og83tdN9zJmSyPJLa82RTtQt8Q3pTj19SsB5XX 7eew==; darn=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=1780226929; x=1780831729; darn=postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=LKmZ3MmduZ/Bph6FDtrcHK5dQcmzAfRXhEbCetQvvXQ=; b=TeCaqu9KG6jUEY6q0h/uY51JIIJ6wMx044aXgxQ3joNyndc8mhuwJcyfItlSXrEEH9 1X85Bo8nki6ISZGSVCX79KYkPWXH0BkBpk1BMNw/axvC3fDpQV885Kqah4gTgzR4opgk LevWUjw2LPg7zFpsGXnrPASCj5h8gSelaoL2XB401+McFJLIIfZIhBD6HudgVtm+1xfa ZV9yUt/mg/DjVJ17oMfKYJl+6cmnb0GvYYCNN+N3/fepNElx/ry4oFNzBq5CPoJuCpt7 j3MjQL6zCGWNMJPI/9eo8fbS/QWT3aVibcoJFwAJhPfSTOjg8hUPJ+uub6WHWZPQybJV /65g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780226929; x=1780831729; h=content-transfer-encoding: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=LKmZ3MmduZ/Bph6FDtrcHK5dQcmzAfRXhEbCetQvvXQ=; b=JDMb5+TMXE8JztVDMEMKTq1QDfktr3MNGmcRFjAx12X5zumpt/e7lrDnFODwxuWwj8 A0Tpj5FZHMQFavsN8WNN4EwQUH1L+TN4xi5Xh4nr2ueXHxAqYvSVcR0IS7PnnOnUoAkJ KyDUGoBp2b5ycMGZBHnIz1ZAdIlI9CLnhcppXWg1UaLHK99cnSVB0vWzXzG2C/7EAz3o sYuQdlilV3sQZ1IbeSi4l95CPohWPTH7jqXltvye5YrxOaHV5hS1s05cYwlLVAhCdYVc JGGd+SAoOiMOImn0GgaNxDJMyZ0PFJR0IvwYlyWV3aUJOnQz/terVTtZkUG92iC8kYts sAAg== X-Forwarded-Encrypted: i=1; AFNElJ+RhT0ML0nZMweuGY1DMISZdS3H0KjTrRE68RfTUvtXqfOAqAg0FZ/SHaWX4wut+tQojRkL1cCFPDxBQtn5@postgresql.org X-Gm-Message-State: AOJu0YwV/qHMlVuiyGvujh8i0nAuz/au6DgYKGaKStoWTStQde/4L6ql VYA1AmFETktWUshcULIDo8s6K601uNY/vQImDUL3hPckAFPjFQ6k6Uh/PeaLNNIqeXhBwMlnWxP LYfd7Nc1W6x5/+HT1sf0Vrw5HoU2QJqLlRgZBN8E= X-Gm-Gg: Acq92OFoi98QKo2AUtZytgDFZip9o+B961BbubN29rDtzGnRg2PPZcgCYggeBD0GQ49 lc5WwFtKzE6vo0NwP2NTauH/+uOYCoZp6IH9HR/YvpZceuFpdqUl8rsOr62k1w71ZlitoU6Dbn3 /HGTwtQqB2HtcQEsE9OusNHrLLrMAar4LZhxp/gGoPm+1T78LJSiWWHa/lYj3LjScYMiiuBo0kO C2CSCBRmMsKWB5+DLF47CqVRffJawUeUCOJboEvw6dFBHQxxuxlbrbkuc2lZ4onJas0tbeTC8KK 5j0SWeDOO3F3+We5bMZWYZ1Q+JeEWv3Hj2SYdBfv0GRrK9ZXjP0XVhK5swQy4eyoIc5EpsFY X-Received: by 2002:a17:907:75d8:b0:bd5:eb9:6a53 with SMTP id a640c23a62f3a-beab06e370cmr225070166b.4.1780226928579; Sun, 31 May 2026 04:28:48 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Tender Wang Date: Sun, 31 May 2026 19:28:37 +0800 X-Gm-Features: AVHnY4JOJ2p17x1cq9NpiVOipEdn2D3EA1X1RJ4oA7VrVDq9s6_jCS52k42X9HE Message-ID: Subject: Re: Eager aggregation, take 3 To: Radim Marek Cc: Richard Guo , PostgreSQL-development Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Radim Marek =E4=BA=8E2026=E5=B9=B45=E6=9C=8829=E6=97= =A5=E5=91=A8=E4=BA=94 23:55=E5=86=99=E9=81=93=EF=BC=9A > > Hey Richard, > > I might be out of my depth here - but while testing RegreSQL as correctne= ss/performance harness on PostgreSQL it picked up a problem with the wrong-= results case during eager aggregation. > > It reproduces on current HEAD (commit 2670cc298f42cd7b1c426bf7ccfb0652d8e= 0b347 now) with enable_eager_aggregate enabled. > > My testing environment > - Linux aarch64, gcc 12 (Debian) > - macOS arm64, Apple clang 21 > (PostgreSQL 19devel on aarch64-apple-darwin25.5.0) > > =3D=3D How to reproduce > > CREATE TEMP TABLE c(id int, country text); > CREATE TEMP TABLE o(customer_id int); > INSERT INTO c VALUES (1,'US'),(2,'US'),(3,'DE'),(4,'DE'),(5,'DE'); > INSERT INTO o VALUES (1),(3); -- only customers 1 and 3 have a row in= o > > SELECT c.country, count(*) AS n > FROM c > WHERE NOT EXISTS (SELECT 1 FROM o WHERE o.customer_id =3D c.id) > GROUP BY c.country > ORDER BY c.country; > > Expected results (everywhere except master) > > country | n > ---------+--- > DE | 2 > US | 1 > (2 rows) > > The actual result with enable_eager_aggregate =3D on (default) > > country | n > ---------+--- > DE | 0 > US | 0 > (2 rows) > > With SET enable_eager_aggregate =3D off, the result is correct (DE=3D2, U= S=3D1), as it is on PG18. > > Query Plan > > QUERY PLAN > -------------------------------------------------------------------------= ---------------------------------------------------------- > Sort (cost=3D108.19..108.69 rows=3D200 width=3D40) (actual time=3D0.195= ..0.197 rows=3D2.00 loops=3D1) > Sort Key: c.country > Sort Method: quicksort Memory: 25kB > Buffers: local hit=3D2 > -> Finalize HashAggregate (cost=3D98.55..100.55 rows=3D200 width=3D4= 0) (actual time=3D0.183..0.186 rows=3D2.00 loops=3D1) > Group Key: c.country > Batches: 1 Memory Usage: 32kB > Buffers: local hit=3D2 > -> Hash Anti Join (cost=3D52.75..95.37 rows=3D635 width=3D40) = (actual time=3D0.177..0.179 rows=3D3.00 loops=3D1) > Hash Cond: (c.id =3D o.customer_id) > Buffers: local hit=3D2 > -> Seq Scan on c (cost=3D0.00..22.70 rows=3D1270 width= =3D36) (actual time=3D0.024..0.025 rows=3D5.00 loops=3D1) > Buffers: local hit=3D1 > -> Hash (cost=3D50.25..50.25 rows=3D200 width=3D12) (act= ual time=3D0.145..0.146 rows=3D2.00 loops=3D1) > Buckets: 1024 Batches: 1 Memory Usage: 9kB > Buffers: local hit=3D1 > -> Partial HashAggregate (cost=3D48.25..50.25 rows= =3D200 width=3D12) (actual time=3D0.122..0.123 rows=3D2.00 loops=3D1) > Group Key: o.customer_id > Batches: 1 Memory Usage: 32kB > Buffers: local hit=3D1 > -> Seq Scan on o (cost=3D0.00..35.50 rows=3D= 2550 width=3D4) (actual time=3D0.002..0.003 rows=3D2.00 loops=3D1) > Buffers: local hit=3D1 > Planning Time: 0.294 ms > Execution Time: 0.255 ms > (24 rows) > > If this is already known or in progress, apologies for the noise. Thanks for the report. This is a bug. When we use eager_agg, it can reduce many tuples before doing a join on the partial agg side. After partial agg, when we are doing a join, the matched rows will be significantly reduced. This is also the effect we want to achieve from eager_agg. But we should be careful about anti-join. Because we will ignore the matched row. The aggregate of unmatched rows seems wrong. And I can get the wrong results from the semi-join, too. For example: postgres=3D# CREATE TEMP TABLE c(id int, country text); CREATE TEMP TABLE o(customer_id int); INSERT INTO c VALUES (1,'US'),(2,'US'),(3,'DE'),(4,'DE'),(5,'DE'); INSERT INTO o VALUES (1),(3); CREATE TABLE CREATE TABLE INSERT 0 5 INSERT 0 2 postgres=3D# insert into o values (1); INSERT 0 1 -- correct result postgres=3D# SELECT c.country, count(*) AS n FROM c WHERE EXISTS (SELECT 1 FROM o WHERE o.customer_id =3D c.id) GROUP BY c.country ORDER BY c.country; country | n ---------+--- DE | 1 US | 1 (2 rows) I do some hacks that make the cost of the path created in make_grouped_join_rel() very small. So we can get a partial agg plan, as follow: postgres=3D# explain SELECT c.country, count(*) AS n FROM c WHERE EXISTS (SELECT 1 FROM o WHERE o.customer_id =3D c.id) GROUP BY c.country ORDER BY c.country; QUERY PLAN ---------------------------------------------------------------------------= -------------- Finalize GroupAggregate (cost=3D31.56..38.32 rows=3D200 width=3D40) Group Key: c.country -> Sort (cost=3D31.56..33.15 rows=3D635 width=3D40) Sort Key: c.country -> Hash Semi Join (cost=3D1.00..2.00 rows=3D635 width=3D40) Hash Cond: (c.id =3D o.customer_id) -> Seq Scan on c (cost=3D0.00..22.70 rows=3D1270 width=3D3= 6) -> Hash (cost=3D200.91..200.91 rows=3D200 width=3D12) -> Partial GroupAggregate (cost=3D179.78..200.91 rows=3D200 width=3D12) Group Key: o.customer_id -> Sort (cost=3D179.78..186.16 rows=3D2550 wid= th=3D4) Sort Key: o.customer_id -> Seq Scan on o (cost=3D0.00..35.50 rows=3D2550 width=3D4) (13 rows) postgres=3D# SELECT c.country, count(*) AS n FROM c WHERE EXISTS (SELECT 1 FROM o WHERE o.customer_id =3D c.id) GROUP BY c.country ORDER BY c.country; country | n ---------+--- DE | 1 US | 2 (2 rows) You can see that the count(us) has 2. Because partial agg pre-aggregates the results for country =3D1. However, for the semantics of semi-join, it returns once a match is found. I haven't thought about it too deeply yet. Maybe we can do something in the make_grouped_join_rel(). ... if (sjinfo->jointype =3D=3D JOIN_ANTI || sjinfo->jointype =3D=3D JOIN_SEMI) return; ... The fixes above can temporarily resolve these issues. But it seems too stri= ct. --=20 Thanks, Tender Wang