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 1vnE5o-003zM1-1w for pgsql-hackers@arkaria.postgresql.org; Tue, 03 Feb 2026 10:57:12 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vnE5m-004Osu-1j for pgsql-hackers@arkaria.postgresql.org; Tue, 03 Feb 2026 10:57:10 +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 1vnE5l-004Osl-3A for pgsql-hackers@lists.postgresql.org; Tue, 03 Feb 2026 10:57:09 +0000 Received: from relay1-d.mail.gandi.net ([2001:4b98:dc4:8::221]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1vnE5j-00000000L3n-1SrC for pgsql-hackers@postgresql.org; Tue, 03 Feb 2026 10:57:08 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 6511244461; Tue, 3 Feb 2026 10:57:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=vondra.me; s=gm1; t=1770116222; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=EScdtrFKK1BW8C1JdZgn+MT7pe4/5XTL7CrsfTMq0TE=; b=UidyoMlDG+wb4LTWjm8z2i0NklDvRJJO+PrRcM1mW6sTU4xD2BgocQf1u8b9zTACZtIn6R +woJz9HQ0N5SJYrMRghMvTTWqGtGgmthmatKobBCGNGpatcdyf7fDc9/YbDiX1MXsjQgqB qNDlHjaGwye35LsaRBVc4P8TYhEcHe59sQEOZqAsfVUBOQnwPQGCUYyFVUTmIxMdpPfLWv yGm/iDEVkyxVDPFY/dwg9NLBds5XJHxXYMjWNDenliOvKz22lZyD3AG9Z4Em1X6rz1qRCL FKQaz1Ih+ly7Pe6nUiENPUa2U0wvjiMnUMEuKw0/eXkwexazDuNTf2CXp2DJeA== Message-ID: Date: Tue, 3 Feb 2026 11:57:01 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: Non-deterministic buffer counts reported in execution with EXPLAIN ANALYZE BUFFERS To: Radim Marek , pgsql-hackers@postgresql.org References: Content-Language: en-US From: Tomas Vondra In-Reply-To: Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-GND-Sasl: tomas@vondra.me X-GND-State: clean X-GND-Score: -100 X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddujeelleeiucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuifetpfffkfdpucggtfgfnhhsuhgsshgtrhhisggvnecuuegrihhlohhuthemuceftddunecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjughrpefkffggfgfuvfhfhfgjtgfgsehtkeertddtvdejnecuhfhrohhmpefvohhmrghsucggohhnughrrgcuoehtohhmrghssehvohhnughrrgdrmhgvqeenucggtffrrghtthgvrhhnpeekffdvudegteefieelffetkeelffeggffhuefffefhleekleethfefieeggfffkeenucfkphepkeeirdegledrvdeftddrvddtieenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepihhnvghtpeekiedrgeelrddvfedtrddvtdeipdhhvghloheplgdutddrudefjedrtddrvdgnpdhmrghilhhfrhhomhepthhomhgrshesvhhonhgurhgrrdhmvgdpqhhiugepieehudduvdeggeegiedupdhmohguvgepshhmthhpohhuthdpnhgspghrtghpthhtohepvddprhgtphhtthhopehrrgguihhmsegsohhrihhnghhsqhhlrdgtohhmpdhrtghpthhtohepphhgshhqlhdqhhgrtghkvghrshesphhoshhtghhrvghsqhhlrdhorhhg List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On 2/2/26 23:54, Radim Marek wrote: > Hello, > > First let me say this is a first time bug report and trying to patch the > PostgreSQL, fresh after the hacking webinar. Please let me know if I > need to clarify something. > > TLDR; EXPLAIN ANALYZE BUFFERS reports different buffer counts for the > Sort node between first and subsequent executions per session. > >          Buffers: shared hit=4 >          ->  Seq Scan on sort_buffer_test  (cost=0.00..2.50 rows=1 > width=32) (actual time=0.042..0.126 rows=1.00 loops=1) >                Filter: (lower(email) = 'email_1@example.com > '::text) >                Rows Removed by Filter: 99 >                Buffers: shared hit=1 > > vs > >          Buffers: shared hit=1 >          ->  Seq Scan on sort_buffer_test  (cost=0.00..2.50 rows=1 > width=32) (actual time=0.021..0.057 rows=1.00 loops=1) >                Filter: (lower(email) = 'email_1@example.com > '::text) >                Rows Removed by Filter: 99 >                Buffers: shared hit=1 > > I found it while working on SQL regression tool (regresql) that uses > total reported buffers from EXPLAIN ANALYZE BUFFERS to detect deviations > for query performance. > > The issue came on smaller queries where test cases showed consistent > "improvement" on performance (fewer buffers) when comparing baseline and > actual test runs. Previously did not notice it on larger data sets due > to the difference in buffers being negligible given the larger operations. > > The example is the test case I managed to isolate. > > --- way how to reproduce > >   CREATE TABLE sort_buffer_test (id serial PRIMARY KEY, val int); >   INSERT INTO sort_buffer_test (val) SELECT generate_series(1, 100); >   ANALYZE sort_buffer_test; > >   -- run twice in new psql session and observe reported buffer change >   EXPLAIN (ANALYZE, BUFFERS, COSTS OFF, TIMING OFF) >   SELECT * FROM sort_buffer_test ORDER BY val DESC LIMIT 1; > > --- > > This behaviour is session specific, tested on 17.x, 18.x and current > development version (just fiy, all verified on both amd64 and aarch64). > > I traced the potential issue down in case of ORDER BY to pg_amop/ > pg_amproc lookups. The specific lookups vary by operation but the > pattern is the same: first execution incurs catalog reads that get > cached for subsequent runs. This applies to DISTINCT (sort, not hashed), > GROUP BY, window functions, etc - which seems to isolate it to sort node. > > All those I believe are actually metadata resolution (i.e. planner > already knows what to sort by). > Correct. AFAICS this happens because the execution may need additional metadata that was not needed for planning. In this particular case the planning checks that the operators are valid/compatible, but does not lookup all the associated pg_amproc entries. That only happens during execution, when actually constructing the "tuplesort" object. But there are probably many similar cases. > Having said that - I'm happy to try to fix it. Just wondering what is > the right direction to go: > > 1. Pre-warm syscache during planning (tried locally and it works - but > does not feel conceptually right and it's rather 'hacky') Yeah, not going to happen. We'd have to preload the whole syscache, because during planning we don't even know if that particular path/plan will win. So we only do the bare minimum, to minimize the cost of planning. And then we initialize the additional fields later, once we have the cheapest plan. > 2. Pre-compute and store metadata and modify each plan node struct for > each type (lots of work). I believe that's what MergeJoin already does. I suppose MergeJoin may simply need more metadata during planning. If it does more than that, it might count as a bug (and it should do less). > 3. Or this is 'works as expected' - which I don't think it's the case > and would force me to do pre-warming on regresql side > I think it mostly works as expected/designed. That however does not mean the issue you pointed out does not exist. IMHO what regresql is aiming to do would be quite handy - e.g. in the index prefetching patch we have regression tests checking that we're not accessing unexpected number of buffers (or more buffers than master). I'm planning to check if regresql would be a more elegant way to do that. For now we're relying on plain regression tests (SQL + expected output), and that happens to not have this issue because it executes the queries in a session with a "deterministic" state. regards -- Tomas Vondra