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 1w5HfW-0035nV-1b for pgsql-hackers@arkaria.postgresql.org; Wed, 25 Mar 2026 06:24:42 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w5HfU-00CGv5-2l for pgsql-hackers@arkaria.postgresql.org; Wed, 25 Mar 2026 06:24:41 +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 1w5HfU-00CGuw-1Y for pgsql-hackers@lists.postgresql.org; Wed, 25 Mar 2026 06:24:40 +0000 Received: from mail-wr1-x432.google.com ([2a00:1450:4864:20::432]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1w5HfS-00000001179-19Xj for pgsql-hackers@lists.postgresql.org; Wed, 25 Mar 2026 06:24:40 +0000 Received: by mail-wr1-x432.google.com with SMTP id ffacd0b85a97d-439af7d77f0so4606794f8f.0 for ; Tue, 24 Mar 2026 23:24:38 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1774419877; cv=none; d=google.com; s=arc-20240605; b=T7ngZ/eYnL9zMYVOKuzHk5X/LBrLsq39er+tyJMgwENhNcxYb5W4aPFyHXa4PQQnlz ly9sulaldJWDtPcLhL+xRfgxDtmWUfLO62fnvNom2O4WcKXOeCRoGc0QJpIivXuN3/yQ EbBTAO0Nf/5Hu+LU/dMlqJ0ttYByMj3p4vCeJaex6rGFGZBjj7E4eiCQu/Ih9yRco42h s6ESPqoDOG70xnQaidhKLxlG0PC4xAtUOf5G17OMjNCLbpynCmxMfi6Hft+PQlCChFP0 hVZiiwEeVaNwMzVNznSazE+IAPmXmY7khp+D03iwFseRE9mY3QvKT6C1RHAkpDR38XQH 6mpA== 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=yZ6Xfp2Z/XmlijneyVNm+s07cmyp4+RckDV9tLFMdVE=; fh=DJ9UCP3zqHsTpYAUVVVIhV4ehf9kfk8PfS56peHjLyY=; b=G5+ypJQXmuWqIzVEGii5O07m4u7aQQh/DeTJk35gV5SIJgvNPERbhXqI1xPrRRAOpX I/C5Hha6Ctw8gEHG04mTeuRmQnfceCIV+6jGw4NSdiVkAKyMfmWVl66gVV8i4mrBFiz+ AD0IE/WeIdtXS+e3+3mRaGeF2CF4KrY4dGNboHGSokARSyRIHmoFUDcydavoR5VbzCb0 J+TwV34fPsv1o1BrGCBEbgH3NpILB4//FhkFVe7BBbRFxBvkKPAkHCyH/i5mz7LT9w9F 3v3Ic/ljljmfBGuagmeYeF9mEVzHnZLHMGHvSMszbwriq1f4kfylQMF8m1RUpJ1UylAy E0LQ==; 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=1774419877; x=1775024677; darn=lists.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=yZ6Xfp2Z/XmlijneyVNm+s07cmyp4+RckDV9tLFMdVE=; b=Vos7BcIfOKMbU5SIyf/vgYhyLFgn+UFAarBfwiQnhupoykoGQxn9pX6kUBrpvsqEn8 Nnup+t82MJnmTCs24oUJP/6sZTW8eDWtfUyEW7LWSaqtvmRtZ0y42GYM29q9u9Ar4MeV IgP0jkOcrhko/QajaTjtCNmZ31yEGq+fjQv3rf6t7tzJkUJwF12NQY/7JFZHv1sYtOqL W/PzQDQDbo0ab45GiMtYt9fTJR8r4nZmdJYXArFoz2qHrS8KYklrmjtYU7abWaWqPwfN laGCwTLmFqvJOtlcNbXO8wAOslAyhKv6S3yLrB36M/eWZNGTmNsrnHuKeG6w875JWaIE 6Srg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774419877; x=1775024677; 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=yZ6Xfp2Z/XmlijneyVNm+s07cmyp4+RckDV9tLFMdVE=; b=amK11oiBMXPW1M7o1atQF8Ue98He3CZbk5HugjPyAC5VTvu323vd2TeIs7uVOleqZf XPuNvJdnAq/lORlm93ArHRL/oNl0T6ycwd+h4XD3dLBGSEflVcLSiNYQg4iusnc5pbtY Lp3CeFUmtju8ydJZ5uEkiCml2Lg8Xjx+oic6UUg8qZmVVLDLlzhrNKX379PIyIisbxn0 MSAzBQKInG5WfpqdDRQP6eVe9AGHvBGoS475PnFtVoeBQ5cWBTXtE0k6g0ATf6rvd90U dIne2LC2gC+26zb0WpbPVkebQO0Owyr6gNfsiuVNnnJd3NCOEaWYi51QKZq5OMD/EGYI R/qA== X-Forwarded-Encrypted: i=1; AJvYcCXBNTQ5CP3HzmyQkB0DwK275/zSiqOuj9/stuOnGZn3mVS6Eh1IDGPwo40HveklW0edkXAHgGgwiTngiEpB@lists.postgresql.org X-Gm-Message-State: AOJu0YwRzVBqJ9/vBzCxhyNzDaxap41B4qbRDMjoGU1xwhAh3zYxfd0/ jAePuAOvIVuMT7yE3huY76AD3OqdU/qDM+nSwhz9DaqjGs83kr5s9N7fPqf4n5UmRFkDaJYM4oa x6pLchJWZS2BjwT9krdHGlw5T4QmAHi4= X-Gm-Gg: ATEYQzzaJTk5sfyVY117QcuQp7ndWzYILArm8sXi03/vzU1yZ/Ux0iQjkGi65v8LVOb EvDUmtF1WA43MdGM9cPxohf7oxNL6uQNxNkVBQdXk4E9h4yv7DiwMPLaip9QRxxhFcKA9rKq7uh NHUKGkmh/gs/guxjHTF9jqYCi4s1eTI7TlN3MrwQg6n9jAndCs2lt9XiZ3yF0PWc8UuLQZPx31W SASJw0yX5k/KHYAEX65VPEV467ZeJMVGaGso9s6G4R3ldTu5Vhoh+gS6Pmf6CJVUZIqYz432LNx D7f6GcpyVZ3fH9IupWv61GHm8A+WURD+UNfa89SXCkdRprZc1SIh X-Received: by 2002:a5d:64e5:0:b0:43b:883b:9548 with SMTP id ffacd0b85a97d-43b889e3553mr2906995f8f.33.1774419877171; Tue, 24 Mar 2026 23:24:37 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Ashutosh Bapat Date: Wed, 25 Mar 2026 11:54:24 +0530 X-Gm-Features: AQROBzAYvN8fcpxaaKcLTkdqqIIVA9RZzyOPX55W8HdlloZVVxat4iuPvG1Oeqs Message-ID: Subject: Re: pg_buffercache: Add per-relation summary stats To: Masahiko Sawada Cc: Lukas Fittl , PostgreSQL Hackers , Paul A Jungwirth , Khoa Nguyen 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 On Wed, Mar 25, 2026 at 12:40=E2=80=AFAM Masahiko Sawada wrote: > > Hi Lukas, > > On Sat, Feb 28, 2026 at 3:59=E2=80=AFPM Lukas Fittl wro= te: > > > > Hi, > > > > See attached a patch that implements a new function, > > pg_buffercache_relation_stats(), which returns per-relfilenode > > statistics on the number of buffers, how many are dirtied/pinned, and > > their avg usage count. > > Thank you for the proposal! > > Paul A Jungwirth, Khoa Nguyen, and I reviewed this patch through the > Patch Review Workshop, and I'd like to share our comments. > > > > > This can be used in monitoring scripts to know which relations are > > kept in shared buffers, to understand performance issues better that > > occur due to relations getting evicted from the cache. In our own > > monitoring tool (pganalyze) we've offered a functionality like this > > based on the existing pg_buffercache() function for a bit over a year > > now [0], and people have found this very valuable - but it doesn't > > work for larger database servers. > > > > Specifically, performing a query that gets this information can be > > prohibitively expensive when using large shared_buffers, and even on > > the default 128MB shared buffers there is a measurable difference: > > > > postgres=3D# WITH pg_buffercache_relation_stats AS ( > > SELECT relfilenode, reltablespace, reldatabase, relforknumber, > > COUNT(*) AS buffers, > > COUNT(*) FILTER (WHERE isdirty) AS buffers_dirty, > > COUNT(*) FILTER (WHERE pinning_backends > 0) AS buffers_pinned, > > AVG(usagecount) AS usagecount_avg > > FROM pg_buffercache > > WHERE reldatabase IS NOT NULL > > GROUP BY 1, 2, 3, 4 > > > > ) > > SELECT * FROM pg_buffercache_relation_stats WHERE relfilenode =3D 2659; > > > > relfilenode | reltablespace | reldatabase | relforknumber | buffers | > > buffers_dirty | buffers_pinned | usagecount_avg > > -------------+---------------+-------------+---------------+---------+-= --------------+----------------+-------------------- > > 2659 | 1663 | 5 | 0 | 8 | > > 0 | 0 | 5.0000000000000000 > > 2659 | 1663 | 1 | 0 | 7 | > > 0 | 0 | 5.0000000000000000 > > 2659 | 1663 | 229553 | 0 | 7 | > > 0 | 0 | 5.0000000000000000 > > (3 rows) > > > > Time: 20.991 ms > > > > postgres=3D# SELECT * FROM pg_buffercache_relation_stats() WHERE > > relfilenode =3D 2659; > > relfilenode | reltablespace | reldatabase | relforknumber | buffers | > > buffers_dirty | buffers_pinned | usagecount_avg > > -------------+---------------+-------------+---------------+---------+-= --------------+----------------+---------------- > > 2659 | 1663 | 1 | 0 | 7 | > > 0 | 0 | 5 > > 2659 | 1663 | 229553 | 0 | 7 | > > 0 | 0 | 5 > > 2659 | 1663 | 5 | 0 | 8 | > > 0 | 0 | 5 > > (3 rows) > > > > Time: 2.912 ms > > > > With the new function this gets done before putting the data in the > > tuplestore used for the set-returning function. > > Overall, we find that the proposed feature is useful. The proposed way > is much cheaper, especially when the number of per-relation stats is > not large. > > Here are review comments on the v1 patch: > > --- > - pg_buffercache--1.5--1.6.sql pg_buffercache--1.6--1.7.sql > + pg_buffercache--1.5--1.6.sql pg_buffercache--1.6--1.7.sql \ > + pg_buffercache--1.7--1.8.sql > > Since commit 4b203d499c6 bumped the version from 1.6 to 1.7 last > November, we think we don't need to bump the version again for this new > feature. > > --- > +/* > + * Hash key for pg_buffercache_relation_stats =E2=80=94 groups by relati= on identity. > + */ > +typedef struct > +{ > + RelFileNumber relfilenumber; > + Oid reltablespace; > + Oid reldatabase; > + ForkNumber forknum; > +} BufferRelStatsKey; > + > +/* > + * Hash entry for pg_buffercache_relation_stats =E2=80=94 accumulates pe= r-relation > + * buffer statistics. > + */ > +typedef struct > +{ > + BufferRelStatsKey key; /* must be first */ > + int32 buffers; > + int32 buffers_dirty; > + int32 buffers_pinned; > + int64 usagecount_total; > +} BufferRelStatsEntry; > > Can we move these typedefs above function prototypes as other typedefs > are defined there? > > --- > + relstats_hash =3D hash_create("pg_buffercache relation stats", > + 128, > + &hash_ctl, > + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); > > It might be worth considering simplehash.h for even better performance. > > --- > + while ((entry =3D (BufferRelStatsEntry *) hash_seq_search(&hash_seq))= !=3D NULL) > + { > + if (entry->buffers =3D=3D 0) > + continue; > + > > We might want to put CHECK_FOR_INTERRUPTS() here too as the number of > entries can be as many as NBuffers in principle. > > --- > We've discussed there might be room for improvement in the function > name. For example, pg_buffercache_relations instead of > pg_buffercache_relation_stats might be a good name, since everything > in this module > is stats. if we drop "_stats" then "relation" should be plural, to > match other functions in the module ("pages", "os_pages", > "numa_pages", "usage_counts"). I know we already have a couple of hand-aggregation functions but I am hesitant to add more of these. Question is where do we stop? For example, the current function is useless if someone wants to find the parts of a relation which are hot since it doesn't include page numbers. Do we write another function for the same? Or we add page numbers to this function and then there's hardly any aggregation happening. What if somebody wanted to perform an aggregation more complex than just count() like average number of buffers per relation or distribution of relation buffers in the cache, do they write separate functions? Another problem is the maintenance cost these functions bring. For example, with the resizable shared buffer project we have another function to stress test. Looking at the function, I see it uses a hash table to aggregate the data. To some extent it's duplicating the functionality we already have - aggregates using hashing. Are we going to duplicate functionality everywhere we require aggregation on top of a system function? These functions will then be missing any optimizations we do to hash aggregation in future. Can we instead investigate the reason the aggregation on top of pg_buffercache output requires so much more time than doing it in the function and fix that as much as we can? I know some slowness will come from tuplestore APIs, tuple formation and deformation but I won't expect it to be 10 times slower. --=20 Best Wishes, Ashutosh Bapat