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.98.2) (envelope-from ) id 1x7hW3-00000000but-4Ant for pgsql-bugs@arkaria.postgresql.org; Fri, 18 Sep 2026 22:57:12 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1x7hW3-00000003fgs-1Ajd for pgsql-bugs@arkaria.postgresql.org; Fri, 18 Sep 2026 22:57:11 +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.98.2) (envelope-from ) id 1x7hW2-00000003fgk-3CvQ for pgsql-bugs@lists.postgresql.org; Fri, 18 Sep 2026 22:57:11 +0000 Received: from mail-pj2-x10.google.com ([2607:f8b0:4864:39::10]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1x7hW0-0000000079M-0Zjl for pgsql-bugs@postgresql.org; Fri, 18 Sep 2026 22:57:10 +0000 Received: by mail-pj2-x10.google.com with SMTP id 98e67ed59e1d1-398cb5615deso1404886a91.3 for ; Fri, 18 Sep 2026 15:57:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=j-davis-com.20251104.gappssmtp.com; s=20251104; t=1789772225; x=1790377025; darn=postgresql.org; h=mime-version:user-agent:content-transfer-encoding:content-type:date :cc:to:from:subject:message-id:from:to:cc:subject:date:message-id :reply-to:content-type; bh=yiadupBw1q2yW8PRlkexPPcTCBx4K4kfrnPO7If3La4=; b=Sk0SvH8VubZjLL0cEyUCl+9C7vV+kALpUAoVai/Gn6vdkNmHHCGIpGh1Au+k8rCkvZ h82hP8pTWHN4Jc0NnqLgTZWCteyvl46JZx89oEYySq+i6YwilJJ7foMJ8E+aso8TyzAn nWKg717gB2+Gl7AeJbPscipcP6rr9t462NuWHTw3CMLIOiUx5DxZyRNpGThxdim1kmi8 WJ/ELJdepzuAtCOtxhHAZZ0Nk3819CT8tmdvClVOZVOpFqE+e/MUxBmvbrCozwrS8BUU vkd1YfJff7U5lxBD6vRTse8w8hqg46PxW2j+osCiCLMs2csow0AtgLqGZcCU/jKL9EFL HV7Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789772225; x=1790377025; h=mime-version:user-agent:content-transfer-encoding:content-type:date :cc:to:from:subject:message-id:x-gm-gg:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to:content-type; bh=yiadupBw1q2yW8PRlkexPPcTCBx4K4kfrnPO7If3La4=; b=MjzmxJpn3FMVrWAsscbnjvaBnKIeu0n8ugMTPGJn6m46WDlwrdEZGxeLIcG17kZ+T9 lQtR9s8E7QYE58RyLEMu0T2eponzxxbyyX+XVpN0TZq2rbqEd0SxStgT5Fgi7ETdOBn2 3nsRI1bLWz9cVmrYo6rKh7JtaX/VugxX4yxgwjSmzO/KRj5h4F2rDQHdwdrzlKnrEGiC apB7jrN3vO8cVata4+Eff0mCZPoj8f5+n6+PlnIJL5rQEOdTzELL7IRZSf8j8y1FQwMw UBY+HkG/KtTvwDKJNDww17oLlcVAIH4CIkG1Jsdydji1mmbgWZ8VyHYD9L7GC5UVIk3o iqDQ== X-Gm-Message-State: AFuF++l4sXOdLWYkgttFEa9DfM+5iryekXTYIiSiNRXDSRFhAP4nBUfr YTQnZlQZFk6E2cuRxUwvd95ju2DX6q7JYeH5IdIM48yMiN4r1dlFsISgfc29JL82iuSTKeaadyM SFYk= X-Gm-Gg: AYBFou3WEW10tjctV5wlUgaMEOsJFj2NsXN3JxhUeigmmAiMihZDLDmMh0f2i1tSFBk Qq2y1LKL1Kfjam3PexFL3ROw3fvE7dk9f/RtSZSt7WmN93wz9aWsfDs4H3RmBJZk3un4DcpQK1N 2dbcvY1gOwiuuHTYFdkP/4tfsEu6gJAvX+jQ7gZ4PkXgD9+lieHqm71N02x5vEbLE2Z2OirsZjO 7iCA94D3t9vk8IkoI0T2FClWbdwtCrauWuAfJu+8l7KBfXivt6bqv8UkW2b9KIhRCbOhblRBNvC x5FhnGlhJ6wuXHTdaSGfB0v3RZT15HA/hURL4E7of5QNCno4krKfufLbDRwemzb1E1uxjM3+XMJ 8prOYPzHJeDUQW5ZpNGe05djLjHRC5gQr1J113u+/l5+5+7cmq6RDCzdrnZ2PUNaRSDIs2u2h9K +O17uCF1Vd3g7qCFqzAg4Y+o3OQ+t8zOqO1KIuyPmZ61ySyZRddyfwHkCgm2AdZ6cWKBKhU6DtA W+0hlwrxYt4FXuyWGjEW6y3dKG6Y6n8adawiQ== X-Received: by 2002:a17:90b:3a81:b0:39e:6a82:afd9 with SMTP id 98e67ed59e1d1-39e6a82bbe2mr1882973a91.43.1789772225257; Fri, 18 Sep 2026 15:57:05 -0700 (PDT) Received: from jeff-ws-bridge.lan (c-24-4-32-250.hsd1.ca.comcast.net. [24.4.32.250]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-144d55bf991sm1970689c88.6.2026.09.18.15.57.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 15:57:04 -0700 (PDT) Message-ID: <2701c83e33f3fe66320291fb5d1b74ca2cb90bd7.camel@j-davis.com> Subject: bug: Gather rescan keeps the first scan's tuple bound in workers From: Jeff Davis To: pgsql-bugs@postgresql.org Cc: Robert Haas Date: Fri, 18 Sep 2026 15:57:03 -0700 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable User-Agent: Evolution 3.52.3-0ubuntu1.1 MIME-Version: 1.0 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk AI-discovered bug report appended to this email. I checked it out and it's a legitimate wrong-results bug. Regards, Jeff Davis Problem ------- =20 = =20 When a Gather or Gather Merge sits under a Limit whose count/offset depends on a parameter, and the node is rescanned (inner side of a nested loop, LATERAL, correlated subquery), the parallel workers keep using the tuple bound from the first scan. If a later scan needs more tuples than the first one did, workers stop early and the query silently returns wrong results: too few rows, or -- with Gather Merge over a Sort -- the right number of the wrong rows. Affected branches and relevant commits =20 -------------------------------------- =20 = =20 REL_14_STABLE through master, i.e. all supported branches; the code is identical in each. Present since PG11. 3452dc5240d "Push tuple limits through Gather and Gather Merge" (2017-08-29). Introduced the bug: added the tuple bound to the workers' shared state, but only on the initial-setup path.=20 3a1f8611f25 "Update parallel executor support to reuse the same DSM" (2015-10-30). Added ExecParallelReinitialize(), the rescan path that the commit above did not update. Repro =20 ----- =20 = =20 Self-contained, all settings at their defaults. Small values are =20 placed in the table's last blocks so that the workers, rather than the leader (which starts scanning first), tend to read them. =20 = =20 CREATE TABLE big (a int, pad text); INSERT INTO big SELECT 2000000 - g, repeat('x', 100) =20 FROM generate_series(1, 2000000) g; =20 CREATE TABLE outer_t (n int); =20 INSERT INTO outer_t VALUES (1), (10); =20 ANALYZE big, outer_t; SELECT o.n, s.arr FROM outer_t o, LATERAL (SELECT array_agg(a) arr FROM (SELECT a FROM big=20 ORDER BY a LIMIT o.n) x) s; The inner side is planned as Limit -> Gather Merge (2 workers) -> Sort -> Parallel Seq Scan on big. Expected: n | arr ----+----------------------- 1 | {0} 10 | {0,1,2,3,4,5,6,7,8,9} Actual (the wrong values vary from run to run, depending on which process scans which blocks): n | arr ----+-------------------------------- 1 | {0} 10 | {0,44,45,46,47,48,49,50,51,52} For a stable symptom, take the leader out of the scan. Each of the two workers then returns only one row, the bound left over from the first scan: SET parallel_leader_participation =3D off; -- same query Expected, n =3D 10: {0,1,2,3,4,5,6,7,8,9} Actual, n =3D 10: {0,44} A plain Gather (no ORDER BY) behaves the same way with the leader out: count(*) over "SELECT a FROM big LIMIT o.n" returns 2 instead of 10. That plan is not chosen by default here; it needs parallel_setup_cost =3D 0, parallel_tuple_cost =3D 0 and min_parallel_table_scan_size =3D 0. Diagnosis --------- The bound reaches workers through FixedParallelExecutorState.tuples_needed in the DSM segment. That field is assigned only in ExecInitParallelPlan(). On rescan, ExecReScanLimit() -> recompute_limits() -> ExecSetTupleBound() correctly updates GatherState/GatherMergeState->tuples_needed in the leader. But ExecGather()/ExecGatherMerge() then call ExecParallelReinitialize(), which reuses the DSM segment and never rewrites fpes->tuples_needed. The newly launched workers read the stale value in ParallelQueryMain() and apply it twice: ExecSetTupleBound() makes the Sort below Gather Merge a top-N sort with the old N, and ExecutorRun() is given the old value as its row count. The leader's own copy of the subplan has the fresh bound, which is why the Gather Merge case yields a full-sized but incorrect result rather than a short one. A bound that shrinks, or an unbounded first scan, is harmless: it only forfeits the early exit.=20