Thread

Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Modernize and optimize pg_buffercache_pages()

  2. pg_buffercache: Add pg_buffercache_os_pages

  3. Don't bother to lock bufmgr partitions in pg_buffercache.

  1. pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-02-28T23:58:34Z

    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.
    
    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=# 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 = 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=# SELECT * FROM pg_buffercache_relation_stats() WHERE
    relfilenode = 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.
    
    Thanks,
    Lukas
    
    [0]: https://pganalyze.com/blog/tracking-postgres-buffer-cache-statistics
    
    -- 
    Lukas Fittl
    
  2. Re: pg_buffercache: Add per-relation summary stats

    Jakub Wartak <jakub.wartak@enterprisedb.com> — 2026-03-02T10:16:40Z

    On Sun, Mar 1, 2026 at 12:59 AM Lukas Fittl <lukas@fittl.com> wrote:
    >
    > 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.
    >
    > 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.
    [..]
    > (3 rows)
    >
    > Time: 20.991 ms
    [..vs]
    >
    > Time: 2.912 ms
    
    Hi Lukas, I have glanced at the patch briefly and couldn't find any
    issues - patch looks solid, however I'm not sure if e.g. launching whole
    NBuffers scan let's say every 5mins doesn't cause latency spikes on the
    system? I mean introducing such function seems to invite users to use
    pg_buffercache and I'm wondering if such regular pattern doesn't cause
    issues? (this is not FUD :), just more like a question based on Your's
    obervation)
    
    Also have you quantified what was the breaking point of previous query?
    (You wrote "larger database servers", but was that like 128GB+ shared_buffers?
    and if so what would be the difference in terms of runtime there -- also
    like ~7x?)
    
    -J.
    
    
    
    
  3. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-03T02:39:30Z

    On Mon, Mar 2, 2026 at 2:16 AM Jakub Wartak
    <jakub.wartak@enterprisedb.com> wrote:
    > Hi Lukas, I have glanced at the patch briefly and couldn't find any
    > issues - patch looks solid, however I'm not sure if e.g. launching whole
    > NBuffers scan let's say every 5mins doesn't cause latency spikes on the
    > system? I mean introducing such function seems to invite users to use
    > pg_buffercache and I'm wondering if such regular pattern doesn't cause
    > issues? (this is not FUD :), just more like a question based on Your's
    > obervation)
    
    Thanks for taking a look!
    
    I think its the definitely the kind of thing you'd want to have people
    opt-in to (we currently only do it when someone enables pg_buffercache
    on their own, and allow turning it off completely), but on systems
    that experience performance issues due to what's in cache (and have
    some CPU capacity to spare), it seems to be helpful more than harmful,
    I think. To be clear on the sample size, this is a subset of our user base
    where we have experience with this, probably across 50-100 installs,
    roughly speaking, of small to medium sized production systems (size as
    in shared_buffers).
    
    Also, FWIW, this isn't going not helpful if your cache contents change
    completely once a minute, but in practice I think its more the
    unexpected effects of e.g. large data loads or background processes
    that mess with the cache, where tracking this over time can help you
    find the root cause of slowness - we currently run this on a 10 minute
    schedule when enabled, and that seems to work in terms of
    understanding large swings in cache contents. I think even if you ran
    this once an hour it could be helpful, and with the patch would give
    you the data you're interested in ("whats in the cache") without
    causing a large temporary file to be created.
    
    >
    > Also have you quantified what was the breaking point of previous query?
    > (You wrote "larger database servers", but was that like 128GB+ shared_buffers?
    > and if so what would be the difference in terms of runtime there -- also
    > like ~7x?)
    
    We've currently set the default limit of where we measure this with
    our tool at 200GB, but that's mainly because the temporary file that
    gets written out with pg_buffercache today to do the grouping just
    becomes noticeably large.
    
    I'll work on sharing more numbers in the following days for larger
    servers, to show the benefit of the patch.
    
    Thanks,
    Lukas
    
    
    --
    Lukas Fittl
    
    
    
    
  4. Re: pg_buffercache: Add per-relation summary stats

    Bertrand Drouvot <bertranddrouvot.pg@gmail.com> — 2026-03-09T09:15:03Z

    Hi,
    
    On Sat, Feb 28, 2026 at 03:58:34PM -0800, Lukas Fittl wrote:
    > 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.
    > 
    > 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:
    
    Thanks for the patch! 
    
    A few comments:
    
    === 1
    
    +typedef struct
    +{
    +   RelFileNumber relfilenumber;
    +   Oid         reltablespace;
    +   Oid         reldatabase;
    +   ForkNumber  forknum;
    +} BufferRelStatsKey;
    
    What about making use of RelFileLocator (instead of 3 members relfilenumber,
    reltablespace and reldatabase)?
    
    === 2
    
    +  <para>
    +   The <function>pg_buffercache_relation_stats()</function> function returns a
    +   set of rows summarizing the state of all shared buffers, aggregated by
    +   relation and fork number.  Similar and more detailed information is
    +   provided by the <structname>pg_buffercache</structname> view, but
    +   <function>pg_buffercache_relation_stats()</function> is significantly
    +   cheaper.
    +  </para>
    
    I'm not 100% sure about the name of the function since the stats are "reset"
    after a rewrite. What about pg_buffercache_relfilenode or 
    pg_buffercache_aggregated?
    
    Regards,
    
    -- 
    Bertrand Drouvot
    PostgreSQL Contributors Team
    RDS Open Source Databases
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  5. Re: pg_buffercache: Add per-relation summary stats

    Haibo Yan <tristan.yim@gmail.com> — 2026-03-17T04:21:22Z

    Hi Lukas,
    
    
    
    I have read the patch, and I have a few questions/comments while going through it:
    
    Could this use RelFileLocator plus ForkNumber instead of open-coding BufferRelStatsKey? That seems closer to existing PostgreSQL abstractions for physical relation identity.
    
    I wonder whether pg_buffercache_relation_stats() is the best name here. The function is really aggregating by relation file identity plus fork, and it is producing a summary of the current buffer contents rather than what many readers might assume from “relation stats”. Would something with summary be clearer than stats?
    
    Why are OUT relforknumber and OUT relfilenode exposed as int2 and oid respectively? Internally these are represented as ForkNumber and RelFileNumber, so I wonder whether the SQL interface should reflect that more clearly, or at least whether the current choice should be explained.
    
    The comment says, “Hash key for pg_buffercache_relation_stats — groups by relation identity”, but that seems imprecise. It is really grouping by relfilenode plus fork, i.e. physical relation-file identity rather than relation identity in a more logical sense.
    
    Is PARALLEL SAFE actually desirable here, as opposed to merely technically safe? A parallel query could cause multiple workers to perform full shared-buffer scans independently, which does not seem obviously desirable for this kind of diagnostic function.
    
    
    
    Best regards,
    
    Haibo Yan
    
    
    > On Feb 28, 2026, at 3:58 PM, Lukas Fittl <lukas@fittl.com> wrote:
    > 
    > 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.
    > 
    > 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=# 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 = 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=# SELECT * FROM pg_buffercache_relation_stats() WHERE
    > relfilenode = 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.
    > 
    > Thanks,
    > Lukas
    > 
    > [0]: https://pganalyze.com/blog/tracking-postgres-buffer-cache-statistics
    > 
    > -- 
    > Lukas Fittl
    > <v1-0001-pg_buffercache-Add-pg_buffercache_relation_stats-.patch>
    
    
  6. Re: pg_buffercache: Add per-relation summary stats

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-24T19:09:13Z

    Hi Lukas,
    
    On Sat, Feb 28, 2026 at 3:59 PM Lukas Fittl <lukas@fittl.com> wrote:
    >
    > 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=# 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 = 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=# SELECT * FROM pg_buffercache_relation_stats() WHERE
    > relfilenode = 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 — groups by relation identity.
    + */
    +typedef struct
    +{
    +   RelFileNumber relfilenumber;
    +   Oid         reltablespace;
    +   Oid         reldatabase;
    +   ForkNumber  forknum;
    +} BufferRelStatsKey;
    +
    +/*
    + * Hash entry for pg_buffercache_relation_stats — accumulates per-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 = 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 = (BufferRelStatsEntry *) hash_seq_search(&hash_seq)) != NULL)
    +   {
    +       if (entry->buffers == 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").
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  7. Re: pg_buffercache: Add per-relation summary stats

    Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-03-25T06:24:24Z

    On Wed, Mar 25, 2026 at 12:40 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Hi Lukas,
    >
    > On Sat, Feb 28, 2026 at 3:59 PM Lukas Fittl <lukas@fittl.com> wrote:
    > >
    > > 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=# 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 = 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=# SELECT * FROM pg_buffercache_relation_stats() WHERE
    > > relfilenode = 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 — groups by relation identity.
    > + */
    > +typedef struct
    > +{
    > +   RelFileNumber relfilenumber;
    > +   Oid         reltablespace;
    > +   Oid         reldatabase;
    > +   ForkNumber  forknum;
    > +} BufferRelStatsKey;
    > +
    > +/*
    > + * Hash entry for pg_buffercache_relation_stats — accumulates per-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 = 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 = (BufferRelStatsEntry *) hash_seq_search(&hash_seq)) != NULL)
    > +   {
    > +       if (entry->buffers == 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.
    
    -- 
    Best Wishes,
    Ashutosh Bapat
    
    
    
    
  8. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-25T06:46:25Z

    Hi Ashutosh,
    
    On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    <ashutosh.bapat.oss@gmail.com> wrote:
    > 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?
    
    I think the problem this solves for, which is a very common question I
    hear from end users, is "how much of this table/index is in cache" and
    "was our query slow because the cache contents changed?".
    
    It can't provide a perfect answer to all questions regarding what's in
    the cache (i.e. it won't tell you which part of the table is cached),
    but its in line with other statistics we do already provide in
    pg_stat_user_tables etc., which are all aggregate counts, not further
    breakdowns.
    
    Its also a reasonable compromise on providing something usable that
    can be shown on dashboards, as I've seen in collecting this
    information using the existing methods from small production systems
    in practice over the last ~1.5 years.
    
    > Another problem is the maintenance cost these functions bring. For
    > example, with the resizable shared buffer project we have another
    > function to stress test.
    
    Can you expand how your testing would be impacted? I hear you on not
    adding many unnecessary functions, but the basic paradigm of how it
    iterates over buffers here is very similar to the other functions in
    pg_buffercache, its just shifting the aggregation to be at a different
    level.
    
    > 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.
    
    I don't think this is fixable outside the function, and I'd be
    surprised if you could get comparable performance, unless you had an
    extreme case where it was < 100 buffers per relation. There are just
    too many layers involved where we'd keep the full set of buffer
    entries vs the grouped version. I'm happy to be convinced otherwise,
    but I won't be the one pushing forward that effort myself.
    
    Thanks,
    Lukas
    
    -- 
    Lukas Fittl
    
    
    
    
  9. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-25T06:52:13Z

    Hi Haibo,
    
    Thanks for your review!
    
    On Mon, Mar 16, 2026 at 9:21 PM Haibo Yan <tristan.yim@gmail.com> wrote:
    > Could this use RelFileLocator plus ForkNumber instead of open-coding BufferRelStatsKey? That seems closer to existing PostgreSQL abstractions for physical relation identity.
    
    Yes, that was noted by other reviewers as well, and makes sense.
    
    > I wonder whether pg_buffercache_relation_stats() is the best name here. The function is really aggregating by relation file identity plus fork, and it is producing a summary of the current buffer contents rather than what many readers might assume from “relation stats”. Would something with summary be clearer than stats?
    
    Per the most recent feedback, I'll rename this to
    "pg_buffercache_relations" for now.
    
    > Why are OUT relforknumber and OUT relfilenode exposed as int2 and oid respectively? Internally these are represented as ForkNumber and RelFileNumber, so I wonder whether the SQL interface should reflect that more clearly, or at least whether the current choice should be explained.
    
    This is consistent with how pg_buffercache_pages represents them - I
    think those are the correct mappings of the int ForkNumber (which we
    know to be small in practice) and RelFileNumber is a typedef of Oid.
    
    > The comment says, “Hash key for pg_buffercache_relation_stats — groups by relation identity”, but that seems imprecise. It is really grouping by relfilenode plus fork, i.e. physical relation-file identity rather than relation identity in a more logical sense.
    
    Good point. I'll adapt this to "groups by relation file" for now.
    
    > Is PARALLEL SAFE actually desirable here, as opposed to merely technically safe? A parallel query could cause multiple workers to perform full shared-buffer scans independently, which does not seem obviously desirable for this kind of diagnostic function.
    
    I see your point, but I don't think a parallel plan would happen in
    practice when just the function is being queried. Since other
    pg_buffercache functions are also PARALLEL SAFE, I'll keep this as is
    for now - if we want to adjust it we should be consistent I think.
    
    Thanks,
    Lukas
    
    -- 
    Lukas Fittl
    
    
    
    
  10. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-25T06:54:18Z

    Hi Bertrand,
    
    Thanks for your review, and sorry it took me a while to get to it -
    other patches took more of my attention unexpectedly.
    
    On Mon, Mar 9, 2026 at 2:15 AM Bertrand Drouvot
    <bertranddrouvot.pg@gmail.com> wrote:
    > === 1
    >
    > +typedef struct
    > +{
    > +   RelFileNumber relfilenumber;
    > +   Oid         reltablespace;
    > +   Oid         reldatabase;
    > +   ForkNumber  forknum;
    > +} BufferRelStatsKey;
    >
    > What about making use of RelFileLocator (instead of 3 members relfilenumber,
    > reltablespace and reldatabase)?
    
    Agreed, and noted by others later too - will adjust.
    
    > +  <para>
    > +   The <function>pg_buffercache_relation_stats()</function> function returns a
    > +   set of rows summarizing the state of all shared buffers, aggregated by
    > +   relation and fork number.  Similar and more detailed information is
    > +   provided by the <structname>pg_buffercache</structname> view, but
    > +   <function>pg_buffercache_relation_stats()</function> is significantly
    > +   cheaper.
    > +  </para>
    >
    > I'm not 100% sure about the name of the function since the stats are "reset"
    > after a rewrite. What about pg_buffercache_relfilenode or
    > pg_buffercache_aggregated?
    
    I'll gone with "pg_buffercache_relations" for now in v2, but I could
    also see "pg_buffercache_relfilenodes" making sense, if we wanted to
    be more clear about the fact that these are physical relation files,
    not logical relations.
    
    Thanks,
    Lukas
    
    -- 
    Lukas Fittl
    
    
    
    
  11. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-25T07:29:20Z

    Hi Masahiko-san, Paul and Khoa,
    
    Thanks for the review!
    
    On Tue, Mar 24, 2026 at 12:09 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > ---
    > -   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.
    
    Makes sense, adjusted.
    
    > Can we move these typedefs above function prototypes as other typedefs
    > are defined there?
    
    Makes sense, done.
    
    > +   relstats_hash = 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.
    
    Good point, adjusted.
    
    > +   while ((entry = (BufferRelStatsEntry *) hash_seq_search(&hash_seq)) != NULL)
    > +   {
    > +       if (entry->buffers == 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.
    
    Sure, that makes sense.
    
    > 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've renamed this to "pg_buffercache_relations", though per Bertrand's
    earlier email, I could also see that it makes sense to incorporate the
    fact more clearly that we're returning physical relfilenodes, not
    logical relations.
    
    See attached v2 that incorporates the review feedback.
    
    Thank you all for reviewing!
    
    Thanks,
    Lukas
    
    --
    Lukas Fittl
    
  12. Re: pg_buffercache: Add per-relation summary stats

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-25T16:48:52Z

    On Tue, Mar 24, 2026 at 11:47 PM Lukas Fittl <lukas@fittl.com> wrote:
    >
    > Hi Ashutosh,
    >
    > On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    > <ashutosh.bapat.oss@gmail.com> wrote:
    > > 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?
    >
    > I think the problem this solves for, which is a very common question I
    > hear from end users, is "how much of this table/index is in cache" and
    > "was our query slow because the cache contents changed?".
    >
    > It can't provide a perfect answer to all questions regarding what's in
    > the cache (i.e. it won't tell you which part of the table is cached),
    > but its in line with other statistics we do already provide in
    > pg_stat_user_tables etc., which are all aggregate counts, not further
    > breakdowns.
    >
    > Its also a reasonable compromise on providing something usable that
    > can be shown on dashboards, as I've seen in collecting this
    > information using the existing methods from small production systems
    > in practice over the last ~1.5 years.
    
    Regarding the proposed statistics, I find them reasonably useful for
    many users. I'm not sure we need to draw a strict line on what belongs
    in the module. If a proposed function does exactly what most
    pg_buffercache users want or are already writing themselves, that is
    good enough motivation to include it.
    
    I think pg_visibility is a good precedent here. In that module, we
    have both pg_visibility_map() and pg_visibility_map_summary(), even
    though we can retrieve the exact same results as the latter by simply
    using the former:
    
    select sum(all_visible::int), sum(all_frozen::int) from
    pg_visibility_map('test') ;
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  13. Re: pg_buffercache: Add per-relation summary stats

    Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-03-26T04:21:00Z

    On Wed, Mar 25, 2026 at 10:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Tue, Mar 24, 2026 at 11:47 PM Lukas Fittl <lukas@fittl.com> wrote:
    > >
    > > Hi Ashutosh,
    > >
    > > On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    > > <ashutosh.bapat.oss@gmail.com> wrote:
    > > > 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?
    > >
    > > I think the problem this solves for, which is a very common question I
    > > hear from end users, is "how much of this table/index is in cache" and
    > > "was our query slow because the cache contents changed?".
    > >
    > > It can't provide a perfect answer to all questions regarding what's in
    > > the cache (i.e. it won't tell you which part of the table is cached),
    > > but its in line with other statistics we do already provide in
    > > pg_stat_user_tables etc., which are all aggregate counts, not further
    > > breakdowns.
    > >
    > > Its also a reasonable compromise on providing something usable that
    > > can be shown on dashboards, as I've seen in collecting this
    > > information using the existing methods from small production systems
    > > in practice over the last ~1.5 years.
    >
    > Regarding the proposed statistics, I find them reasonably useful for
    > many users. I'm not sure we need to draw a strict line on what belongs
    > in the module. If a proposed function does exactly what most
    > pg_buffercache users want or are already writing themselves, that is
    > good enough motivation to include it.
    >
    > I think pg_visibility is a good precedent here. In that module, we
    > have both pg_visibility_map() and pg_visibility_map_summary(), even
    > though we can retrieve the exact same results as the latter by simply
    > using the former:
    >
    > select sum(all_visible::int), sum(all_frozen::int) from
    > pg_visibility_map('test') ;
    >
    
    A summary may still be ok, but this proposal is going a bit farther,
    it's grouping by one subset which should really be done by GROUP BY in
    SQL. And I do
    
    I am afraid that at some point, we will start finding all of these to
    be a maintenance burden. At that point, removing them will become a
    real pain for the backward compatibility reason. For example
    1. The proposed function is going to add one more test to an already
    huge testing exercise for shared buffers resizing.
    2. If we change the way to manage buffer cache e.g. use a tree based
    cache instead of hash + array cache, each of the functions which
    traverses the buffer cache array is going to add work - adjusting it
    to the new data structure - and make a hard project even harder. In
    this case we have other ways to get the summary, so the code level
    scan of buffer cache is entirely avoidable.
    
    If I am the only one opposing it, and there are more senior
    contributors in favour of adding this function, we can accept it.
    
    -- 
    Best Wishes,
    Ashutosh Bapat
    
    
    
    
  14. Re: pg_buffercache: Add per-relation summary stats

    Dmitry Dolgov <9erthalion6@gmail.com> — 2026-03-27T18:11:03Z

    Thanks for the patch, I like the idea.
    
    > On Sat, Feb 28, 2026 at 03:58:34PM -0800, Lukas Fittl wrote:
    >
    > 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.
    
    I see how relation footprint on the buffer cache can be useful, e.g. how
    many buffers per relation are used as well as how many of them are
    dirty. But how one can benefit from number of pinned buffers and average
    usage count per relation, is there a clear understanding of what to do
    about those numbers?
    
    > The <function>pg_buffercache_summary()</function> function returns a
    > single row summarizing the state of all shared buffers. 
    
    > The <function>pg_buffercache_relations()</function> function returns
    > a set of rows summarizing the state of all shared buffers, aggregated by
    > relation and fork number. 
    
    Maybe it was already asked before, but given those two functions
    (pg_buffercache_summary and pg_buffercache_relations) seems to have a
    very similar goal and the summary is just happening in different ways,
    is there a way to somehow unify them and have one function instead of
    two separate? It could be a unified function implementation and still
    two different interfaces to call, or even a single pg_buffercache_summary
    with an argument, specifying how to summarize.
    
    Also, would it be useful to have a numa-aware counterpart for the
    per-relation summary? Something like: this relation has so and so many
    buffers, and 50% of them are located on the node 1, while the other 50%
    on the node 2.
    
    
    
    
  15. Re: pg_buffercache: Add per-relation summary stats

    Tomas Vondra <tomas@vondra.me> — 2026-03-27T22:58:52Z

    On 3/26/26 05:21, Ashutosh Bapat wrote:
    > On Wed, Mar 25, 2026 at 10:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >>
    >> On Tue, Mar 24, 2026 at 11:47 PM Lukas Fittl <lukas@fittl.com> wrote:
    >>>
    >>> Hi Ashutosh,
    >>>
    >>> On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    >>> <ashutosh.bapat.oss@gmail.com> wrote:
    >>>> 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?
    >>>
    >>> I think the problem this solves for, which is a very common question I
    >>> hear from end users, is "how much of this table/index is in cache" and
    >>> "was our query slow because the cache contents changed?".
    >>>
    >>> It can't provide a perfect answer to all questions regarding what's in
    >>> the cache (i.e. it won't tell you which part of the table is cached),
    >>> but its in line with other statistics we do already provide in
    >>> pg_stat_user_tables etc., which are all aggregate counts, not further
    >>> breakdowns.
    >>>
    >>> Its also a reasonable compromise on providing something usable that
    >>> can be shown on dashboards, as I've seen in collecting this
    >>> information using the existing methods from small production systems
    >>> in practice over the last ~1.5 years.
    >>
    >> Regarding the proposed statistics, I find them reasonably useful for
    >> many users. I'm not sure we need to draw a strict line on what belongs
    >> in the module. If a proposed function does exactly what most
    >> pg_buffercache users want or are already writing themselves, that is
    >> good enough motivation to include it.
    >>
    >> I think pg_visibility is a good precedent here. In that module, we
    >> have both pg_visibility_map() and pg_visibility_map_summary(), even
    >> though we can retrieve the exact same results as the latter by simply
    >> using the former:
    >>
    >> select sum(all_visible::int), sum(all_frozen::int) from
    >> pg_visibility_map('test') ;
    >>
    > 
    > A summary may still be ok, but this proposal is going a bit farther,
    > it's grouping by one subset which should really be done by GROUP BY in
    > SQL. And I do
    > 
    > I am afraid that at some point, we will start finding all of these to
    > be a maintenance burden. At that point, removing them will become a
    > real pain for the backward compatibility reason. For example
    > 1. The proposed function is going to add one more test to an already
    > huge testing exercise for shared buffers resizing.
    > 2. If we change the way to manage buffer cache e.g. use a tree based
    > cache instead of hash + array cache, each of the functions which
    > traverses the buffer cache array is going to add work - adjusting it
    > to the new data structure - and make a hard project even harder. In
    > this case we have other ways to get the summary, so the code level
    > scan of buffer cache is entirely avoidable.
    > 
    > If I am the only one opposing it, and there are more senior
    > contributors in favour of adding this function, we can accept it.
    > 
    
    I understand this argument - we have SQL, which allows us to process the
    data in a flexible way, without hard-coding all interesting groupings.
    The question is whether this particular grouping is special enough to
    warrant a custom *faster* function.
    
    The main argument here seems to be the performance, and the initial
    message demonstrates a 10x speedup (2ms vs. 20ms) on a cluster with
    128MB shared buffers. Unless I misunderstood what config it uses.
    
    I gave it a try on an azure VM with 32GB shared buffers, to make it a
    bit more realistic, and my timings are 10ms vs. 700ms. But I also wonder
    if the original timings really were from a cluster with 128MB, because
    for me that shows 0.3ms vs. 3ms (so an order of magnitude faster than
    what was reported). But I suppose that's also hw specific.
    
    Nevertheless, it is much faster. I haven't profiled this but I assume
    it's thanks to not having to write the entries into a tuplestore (and
    possibly into a tempfile).
    
    But is it actually needed / worth it? I wonder what timings does Lukas
    observe when running this on larger clusters. Because in a later email
    he says:
    
       ... we currently run this on a 10 minute schedule when enabled, and
       that seems to work in terms of understanding large swings in cache
       contents.
    
    I'm all in for optimizing stuff, but if you're running a monitoring task
    every 10 minutes, does it matter if it's running for 1 or 5 seconds? I
    find that a bit hard to believe.
    
    Let's assume it's worth it. I wonder what similar summaries might be
    interesting for users. I'd probably want to see a per-database summary,
    especially on a shared / multi-tenant cluster. But AFAICS I can
    calculate that from the pg_buffercache_relations() result, except that
    I'll have to recalculate the usagecount.
    
    I don't have clear opinion if we should do this. I kinda doubt it's a
    significant maintenance burden. It'd add one more place the patch for
    on-line resizing of shared buffers needs to worry about. Surely that
    should not be very difficult, considering there are ~5 other places in
    this very extension doing this already?
    
    One thing we lose by doing ad hoc aggregation (instead of just relying
    on the regular SQL aggregation operators) is lack of memory limit.
    There's a simple in-memory hash table, no spilling to disk etc. The
    simple pg_buffercache view does not have this issue, because the
    tuplestore will spill to disk after hitting work_mem. Simplehash won't.
    
    The entries are ~48B, so there would need to be buffers for ~100k
    (relfilenode,forknum) combinations to overflow 4MB. It's not very
    common, but I've seen systems with more relations that this. Would be
    good to show some numbers showing it's not an issue.
    
    
    A couple minor comments about the code:
    
    1) Isn't this check unnecessary? All entries should have buffers > 0.
    
        if (entry->buffers == 0)
            continue;
    
    2) Shouldn't this check BM_TAG_VALID too? Or is BM_VALID enough to look
    at the bufHdr->tag?
    
        /* Skip unused/invalid buffers */
        if (!(buf_state & BM_VALID))
            continue;
    
    3) I think "buffers" argument should be renamed to "buffers_unused" for
    consistency with pg_buffercache_summary.
    
    
    Overall, I'm -0.1 on this. I'm not opposed to doing this, but I'm also
    not quite convinced it's worth it.
    
    
    regards
    
    -- 
    Tomas Vondra
    
    
    
    
    
  16. Re: pg_buffercache: Add per-relation summary stats

    Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-03-28T04:18:26Z

    On Sat, Mar 28, 2026 at 4:28 AM Tomas Vondra <tomas@vondra.me> wrote:
    >
    > On 3/26/26 05:21, Ashutosh Bapat wrote:
    > > On Wed, Mar 25, 2026 at 10:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >>
    > >> On Tue, Mar 24, 2026 at 11:47 PM Lukas Fittl <lukas@fittl.com> wrote:
    > >>>
    > >>> Hi Ashutosh,
    > >>>
    > >>> On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    > >>> <ashutosh.bapat.oss@gmail.com> wrote:
    > >>>> 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?
    > >>>
    > >>> I think the problem this solves for, which is a very common question I
    > >>> hear from end users, is "how much of this table/index is in cache" and
    > >>> "was our query slow because the cache contents changed?".
    > >>>
    > >>> It can't provide a perfect answer to all questions regarding what's in
    > >>> the cache (i.e. it won't tell you which part of the table is cached),
    > >>> but its in line with other statistics we do already provide in
    > >>> pg_stat_user_tables etc., which are all aggregate counts, not further
    > >>> breakdowns.
    > >>>
    > >>> Its also a reasonable compromise on providing something usable that
    > >>> can be shown on dashboards, as I've seen in collecting this
    > >>> information using the existing methods from small production systems
    > >>> in practice over the last ~1.5 years.
    > >>
    > >> Regarding the proposed statistics, I find them reasonably useful for
    > >> many users. I'm not sure we need to draw a strict line on what belongs
    > >> in the module. If a proposed function does exactly what most
    > >> pg_buffercache users want or are already writing themselves, that is
    > >> good enough motivation to include it.
    > >>
    > >> I think pg_visibility is a good precedent here. In that module, we
    > >> have both pg_visibility_map() and pg_visibility_map_summary(), even
    > >> though we can retrieve the exact same results as the latter by simply
    > >> using the former:
    > >>
    > >> select sum(all_visible::int), sum(all_frozen::int) from
    > >> pg_visibility_map('test') ;
    > >>
    > >
    > > A summary may still be ok, but this proposal is going a bit farther,
    > > it's grouping by one subset which should really be done by GROUP BY in
    > > SQL. And I do
    > >
    > > I am afraid that at some point, we will start finding all of these to
    > > be a maintenance burden. At that point, removing them will become a
    > > real pain for the backward compatibility reason. For example
    > > 1. The proposed function is going to add one more test to an already
    > > huge testing exercise for shared buffers resizing.
    > > 2. If we change the way to manage buffer cache e.g. use a tree based
    > > cache instead of hash + array cache, each of the functions which
    > > traverses the buffer cache array is going to add work - adjusting it
    > > to the new data structure - and make a hard project even harder. In
    > > this case we have other ways to get the summary, so the code level
    > > scan of buffer cache is entirely avoidable.
    > >
    > > If I am the only one opposing it, and there are more senior
    > > contributors in favour of adding this function, we can accept it.
    > >
    >
    > I understand this argument - we have SQL, which allows us to process the
    > data in a flexible way, without hard-coding all interesting groupings.
    > The question is whether this particular grouping is special enough to
    > warrant a custom *faster* function.
    >
    > The main argument here seems to be the performance, and the initial
    > message demonstrates a 10x speedup (2ms vs. 20ms) on a cluster with
    > 128MB shared buffers. Unless I misunderstood what config it uses.
    >
    > I gave it a try on an azure VM with 32GB shared buffers, to make it a
    > bit more realistic, and my timings are 10ms vs. 700ms. But I also wonder
    > if the original timings really were from a cluster with 128MB, because
    > for me that shows 0.3ms vs. 3ms (so an order of magnitude faster than
    > what was reported). But I suppose that's also hw specific.
    >
    > Nevertheless, it is much faster. I haven't profiled this but I assume
    > it's thanks to not having to write the entries into a tuplestore (and
    > possibly into a tempfile).
    
    Parallely myself and Palak Chaturvedi developed a quick patch to
    modernise pg_buffercache_pages() and use tuplestore so that it doesn't
    have to rely on NBuffers being the same between start of the scan,
    when memory allocated, when the scan ends - a condition possible with
    resizing buffer cache. It seems to improve the timings by about 10-30%
    on my laptop for 128MB buffercache size. Without this patch the time
    taken to execute Lukas's query varies between 10-15ms on my laptop.
    With this patch it varies between 8-9ms. So the timing is more stable
    as a side effect. It's not a 10x improvement that we are looking for
    but it looks like a step in the right direction. That improvement
    seems to come purely because we avoid creating a heap tuple. I wonder
    if there are some places up in the execution tree where full
    heaptuples get formed again instead of continuing to use minimal
    tuples or places where we perform some extra actions that are not
    required.
    
    I didn't dig into the history to find out why we didn't modernize
    pg_buffercache_pages(). I don't see any hazard though.
    
    Lukas's patch allocates the hash table in memory entirely, whereas
    tuplestore restricts memory usage to work_mem, so it might cause the
    function to use more memory than user expects it to use when size of
    the hash table grows beyond work_mem.
    
    -- 
    Best Wishes,
    Ashutosh Bapat
    
  17. Re: pg_buffercache: Add per-relation summary stats

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-28T05:36:51Z

    On Fri, Mar 27, 2026 at 3:58 PM Tomas Vondra <tomas@vondra.me> wrote:
    >
    > On 3/26/26 05:21, Ashutosh Bapat wrote:
    > > On Wed, Mar 25, 2026 at 10:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >>
    > >> On Tue, Mar 24, 2026 at 11:47 PM Lukas Fittl <lukas@fittl.com> wrote:
    > >>>
    > >>> Hi Ashutosh,
    > >>>
    > >>> On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    > >>> <ashutosh.bapat.oss@gmail.com> wrote:
    > >>>> 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?
    > >>>
    > >>> I think the problem this solves for, which is a very common question I
    > >>> hear from end users, is "how much of this table/index is in cache" and
    > >>> "was our query slow because the cache contents changed?".
    > >>>
    > >>> It can't provide a perfect answer to all questions regarding what's in
    > >>> the cache (i.e. it won't tell you which part of the table is cached),
    > >>> but its in line with other statistics we do already provide in
    > >>> pg_stat_user_tables etc., which are all aggregate counts, not further
    > >>> breakdowns.
    > >>>
    > >>> Its also a reasonable compromise on providing something usable that
    > >>> can be shown on dashboards, as I've seen in collecting this
    > >>> information using the existing methods from small production systems
    > >>> in practice over the last ~1.5 years.
    > >>
    > >> Regarding the proposed statistics, I find them reasonably useful for
    > >> many users. I'm not sure we need to draw a strict line on what belongs
    > >> in the module. If a proposed function does exactly what most
    > >> pg_buffercache users want or are already writing themselves, that is
    > >> good enough motivation to include it.
    > >>
    > >> I think pg_visibility is a good precedent here. In that module, we
    > >> have both pg_visibility_map() and pg_visibility_map_summary(), even
    > >> though we can retrieve the exact same results as the latter by simply
    > >> using the former:
    > >>
    > >> select sum(all_visible::int), sum(all_frozen::int) from
    > >> pg_visibility_map('test') ;
    > >>
    > >
    > > A summary may still be ok, but this proposal is going a bit farther,
    > > it's grouping by one subset which should really be done by GROUP BY in
    > > SQL. And I do
    > >
    > > I am afraid that at some point, we will start finding all of these to
    > > be a maintenance burden. At that point, removing them will become a
    > > real pain for the backward compatibility reason. For example
    > > 1. The proposed function is going to add one more test to an already
    > > huge testing exercise for shared buffers resizing.
    > > 2. If we change the way to manage buffer cache e.g. use a tree based
    > > cache instead of hash + array cache, each of the functions which
    > > traverses the buffer cache array is going to add work - adjusting it
    > > to the new data structure - and make a hard project even harder. In
    > > this case we have other ways to get the summary, so the code level
    > > scan of buffer cache is entirely avoidable.
    > >
    > > If I am the only one opposing it, and there are more senior
    > > contributors in favour of adding this function, we can accept it.
    > >
    >
    > I understand this argument - we have SQL, which allows us to process the
    > data in a flexible way, without hard-coding all interesting groupings.
    > The question is whether this particular grouping is special enough to
    > warrant a custom *faster* function.
    >
    > The main argument here seems to be the performance, and the initial
    > message demonstrates a 10x speedup (2ms vs. 20ms) on a cluster with
    > 128MB shared buffers. Unless I misunderstood what config it uses.
    >
    > I gave it a try on an azure VM with 32GB shared buffers, to make it a
    > bit more realistic, and my timings are 10ms vs. 700ms. But I also wonder
    > if the original timings really were from a cluster with 128MB, because
    > for me that shows 0.3ms vs. 3ms (so an order of magnitude faster than
    > what was reported). But I suppose that's also hw specific.
    >
    > Nevertheless, it is much faster. I haven't profiled this but I assume
    > it's thanks to not having to write the entries into a tuplestore (and
    > possibly into a tempfile).
    >
    > But is it actually needed / worth it? I wonder what timings does Lukas
    > observe when running this on larger clusters. Because in a later email
    > he says:
    >
    >    ... we currently run this on a 10 minute schedule when enabled, and
    >    that seems to work in terms of understanding large swings in cache
    >    contents.
    >
    > I'm all in for optimizing stuff, but if you're running a monitoring task
    > every 10 minutes, does it matter if it's running for 1 or 5 seconds? I
    > find that a bit hard to believe.
    
    I imagined such a query is just one of many monitoring queries running
    concurrently, so the cumulative overhead can still matter.
    
    > I don't have clear opinion if we should do this. I kinda doubt it's a
    > significant maintenance burden. It'd add one more place the patch for
    > on-line resizing of shared buffers needs to worry about. Surely that
    > should not be very difficult, considering there are ~5 other places in
    > this very extension doing this already?
    
    Yeah, I've not looked at the online shared buffer resizing patch, but
    I hope that the patch somewhat abstructs the access to shared buffers
    that might be being resized so that we don't need to worry about the
    complex part when writing code accessing the shared buffers.
    
    > One thing we lose by doing ad hoc aggregation (instead of just relying
    > on the regular SQL aggregation operators) is lack of memory limit.
    > There's a simple in-memory hash table, no spilling to disk etc. The
    > simple pg_buffercache view does not have this issue, because the
    > tuplestore will spill to disk after hitting work_mem. Simplehash won't.
    >
    > The entries are ~48B, so there would need to be buffers for ~100k
    > (relfilenode,forknum) combinations to overflow 4MB. It's not very
    > common, but I've seen systems with more relations that this. Would be
    > good to show some numbers showing it's not an issue.
    
    Good point. I agree that we should not introduce the function in a way
    that there is a risk of using excessive memory while not respecting
    work_mem or other GUC parameters.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  18. Re: pg_buffercache: Add per-relation summary stats

    Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-03-28T16:12:07Z

    On Sat, Mar 28, 2026 at 11:07 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Fri, Mar 27, 2026 at 3:58 PM Tomas Vondra <tomas@vondra.me> wrote:
    > >
    > > On 3/26/26 05:21, Ashutosh Bapat wrote:
    > > > On Wed, Mar 25, 2026 at 10:19 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > > >>
    > > >> On Tue, Mar 24, 2026 at 11:47 PM Lukas Fittl <lukas@fittl.com> wrote:
    > > >>>
    > > >>> Hi Ashutosh,
    > > >>>
    > > >>> On Tue, Mar 24, 2026 at 11:24 PM Ashutosh Bapat
    > > >>> <ashutosh.bapat.oss@gmail.com> wrote:
    > > >>>> 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?
    > > >>>
    > > >>> I think the problem this solves for, which is a very common question I
    > > >>> hear from end users, is "how much of this table/index is in cache" and
    > > >>> "was our query slow because the cache contents changed?".
    > > >>>
    > > >>> It can't provide a perfect answer to all questions regarding what's in
    > > >>> the cache (i.e. it won't tell you which part of the table is cached),
    > > >>> but its in line with other statistics we do already provide in
    > > >>> pg_stat_user_tables etc., which are all aggregate counts, not further
    > > >>> breakdowns.
    > > >>>
    > > >>> Its also a reasonable compromise on providing something usable that
    > > >>> can be shown on dashboards, as I've seen in collecting this
    > > >>> information using the existing methods from small production systems
    > > >>> in practice over the last ~1.5 years.
    > > >>
    > > >> Regarding the proposed statistics, I find them reasonably useful for
    > > >> many users. I'm not sure we need to draw a strict line on what belongs
    > > >> in the module. If a proposed function does exactly what most
    > > >> pg_buffercache users want or are already writing themselves, that is
    > > >> good enough motivation to include it.
    > > >>
    > > >> I think pg_visibility is a good precedent here. In that module, we
    > > >> have both pg_visibility_map() and pg_visibility_map_summary(), even
    > > >> though we can retrieve the exact same results as the latter by simply
    > > >> using the former:
    > > >>
    > > >> select sum(all_visible::int), sum(all_frozen::int) from
    > > >> pg_visibility_map('test') ;
    > > >>
    > > >
    > > > A summary may still be ok, but this proposal is going a bit farther,
    > > > it's grouping by one subset which should really be done by GROUP BY in
    > > > SQL. And I do
    > > >
    > > > I am afraid that at some point, we will start finding all of these to
    > > > be a maintenance burden. At that point, removing them will become a
    > > > real pain for the backward compatibility reason. For example
    > > > 1. The proposed function is going to add one more test to an already
    > > > huge testing exercise for shared buffers resizing.
    > > > 2. If we change the way to manage buffer cache e.g. use a tree based
    > > > cache instead of hash + array cache, each of the functions which
    > > > traverses the buffer cache array is going to add work - adjusting it
    > > > to the new data structure - and make a hard project even harder. In
    > > > this case we have other ways to get the summary, so the code level
    > > > scan of buffer cache is entirely avoidable.
    > > >
    > > > If I am the only one opposing it, and there are more senior
    > > > contributors in favour of adding this function, we can accept it.
    > > >
    > >
    > > I understand this argument - we have SQL, which allows us to process the
    > > data in a flexible way, without hard-coding all interesting groupings.
    > > The question is whether this particular grouping is special enough to
    > > warrant a custom *faster* function.
    
    Well-said. Thanks.
    
    > >
    > > The main argument here seems to be the performance, and the initial
    > > message demonstrates a 10x speedup (2ms vs. 20ms) on a cluster with
    > > 128MB shared buffers. Unless I misunderstood what config it uses.
    > >
    > > I gave it a try on an azure VM with 32GB shared buffers, to make it a
    > > bit more realistic, and my timings are 10ms vs. 700ms. But I also wonder
    > > if the original timings really were from a cluster with 128MB, because
    > > for me that shows 0.3ms vs. 3ms (so an order of magnitude faster than
    > > what was reported). But I suppose that's also hw specific.
    > >
    > > Nevertheless, it is much faster. I haven't profiled this but I assume
    > > it's thanks to not having to write the entries into a tuplestore (and
    > > possibly into a tempfile).
    > >
    > > But is it actually needed / worth it? I wonder what timings does Lukas
    > > observe when running this on larger clusters. Because in a later email
    > > he says:
    > >
    > >    ... we currently run this on a 10 minute schedule when enabled, and
    > >    that seems to work in terms of understanding large swings in cache
    > >    contents.
    > >
    > > I'm all in for optimizing stuff, but if you're running a monitoring task
    > > every 10 minutes, does it matter if it's running for 1 or 5 seconds? I
    > > find that a bit hard to believe.
    >
    > I imagined such a query is just one of many monitoring queries running
    > concurrently, so the cumulative overhead can still matter.
    >
    
    What kind of cumulative overhead, do you see? Reduced TPS, increased
    memory/CPU consumption? I think itd will be good to see some metric
    evidence of this, rather than relying on the assumption.
    
    > > I don't have a clear opinion if we should do this. I kinda doubt it's a
    > > significant maintenance burden. It'd add one more place the patch for
    > > on-line resizing of shared buffers needs to worry about. Surely that
    > > should not be very difficult, considering there are ~5 other places in
    > > this very extension doing this already?
    >
    > Yeah, I've not looked at the online shared buffer resizing patch, but
    > I hope that the patch somewhat abstructs the access to shared buffers
    > that might be being resized so that we don't need to worry about the
    > complex part when writing code accessing the shared buffers.
    
    The code to abstract isn't there in the patch yet, but I agree that
    regular scan code shouldn't receive a lot of changes in the resizing
    implementation patch. I have been toying with the idea that we provide
    an abstraction to walk the buffer cache (foreach_buffer for example),
    which may hide any complexity, if required. So, yes, there will be
    some abstraction as you envision. However, testing will still be
    required. It's the testing effort, increase in test time etc. which I
    am worried about. However, I may be overestimating it.
    
    >
    > > One thing we lose by doing ad hoc aggregation (instead of just relying
    > > on the regular SQL aggregation operators) is lack of memory limit.
    > > There's a simple in-memory hash table, no spilling to disk etc. The
    > > simple pg_buffercache view does not have this issue, because the
    > > tuplestore will spill to disk after hitting work_mem. Simplehash won't.
    > >
    > > The entries are ~48B, so there would need to be buffers for ~100k
    > > (relfilenode,forknum) combinations to overflow 4MB. It's not very
    > > common, but I've seen systems with more relations that this. Would be
    > > good to show some numbers showing it's not an issue.
    >
    > Good point. I agree that we should not introduce the function in a way
    > that there is a risk of using excessive memory while not respecting
    > work_mem or other GUC parameters.
    
    +1.
    
    -- 
    Best Wishes,
    Ashutosh Bapat
    
    
    
    
  19. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-28T18:51:36Z

    On Fri, Mar 27, 2026 at 10:37 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Fri, Mar 27, 2026 at 3:58 PM Tomas Vondra <tomas@vondra.me> wrote:
    > > One thing we lose by doing ad hoc aggregation (instead of just relying
    > > on the regular SQL aggregation operators) is lack of memory limit.
    > > There's a simple in-memory hash table, no spilling to disk etc. The
    > > simple pg_buffercache view does not have this issue, because the
    > > tuplestore will spill to disk after hitting work_mem. Simplehash won't.
    > >
    > > The entries are ~48B, so there would need to be buffers for ~100k
    > > (relfilenode,forknum) combinations to overflow 4MB. It's not very
    > > common, but I've seen systems with more relations that this. Would be
    > > good to show some numbers showing it's not an issue.
    >
    > Good point. I agree that we should not introduce the function in a way
    > that there is a risk of using excessive memory while not respecting
    > work_mem or other GUC parameters.
    
    Yeah, I agree that is problematic regarding work_mem.
    
    FWIW, I could see two methods to address that specifically, if we
    wanted the special purpose function:
    
    1) Error out if our hash table grows too large and require the user to
    increase work_mem to get the data - seems inconvenient, but might be
    okay if we are typically below work_mem limit anyway (I haven't run
    the numbers on that yet)
    
    2) Implement disk spill logic using a LogicalTapeSet or similar - I
    think that'd be substantially more code, doesn't seem worth it just
    for this (but if a situation like this recurs, we could consider a
    more generalized facility)
    
    Thanks,
    Lukas
    
    -- 
    Lukas Fittl
    
    
    
    
  20. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-28T19:14:14Z

    On Sat, Mar 28, 2026 at 9:12 AM Ashutosh Bapat
    <ashutosh.bapat.oss@gmail.com> wrote:
    >
    > On Sat, Mar 28, 2026 at 11:07 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Fri, Mar 27, 2026 at 3:58 PM Tomas Vondra <tomas@vondra.me> wrote:
    > > >
    > > > On 3/26/26 05:21, Ashutosh Bapat wrote:
    > > >
    > > > The main argument here seems to be the performance, and the initial
    > > > message demonstrates a 10x speedup (2ms vs. 20ms) on a cluster with
    > > > 128MB shared buffers. Unless I misunderstood what config it uses.
    > > >
    > > > I gave it a try on an azure VM with 32GB shared buffers, to make it a
    > > > bit more realistic, and my timings are 10ms vs. 700ms. But I also wonder
    > > > if the original timings really were from a cluster with 128MB, because
    > > > for me that shows 0.3ms vs. 3ms (so an order of magnitude faster than
    > > > what was reported). But I suppose that's also hw specific.
    > > >
    > > > Nevertheless, it is much faster. I haven't profiled this but I assume
    > > > it's thanks to not having to write the entries into a tuplestore (and
    > > > possibly into a tempfile).
    > > >
    > > > But is it actually needed / worth it? I wonder what timings does Lukas
    > > > observe when running this on larger clusters. Because in a later email
    > > > he says:
    > > >
    > > >    ... we currently run this on a 10 minute schedule when enabled, and
    > > >    that seems to work in terms of understanding large swings in cache
    > > >    contents.
    > > >
    > > > I'm all in for optimizing stuff, but if you're running a monitoring task
    > > > every 10 minutes, does it matter if it's running for 1 or 5 seconds? I
    > > > find that a bit hard to believe.
    > >
    > > I imagined such a query is just one of many monitoring queries running
    > > concurrently, so the cumulative overhead can still matter.
    > >
    >
    > What kind of cumulative overhead, do you see? Reduced TPS, increased
    > memory/CPU consumption? I think itd will be good to see some metric
    > evidence of this, rather than relying on the assumption.
    
    On my part, the overhead that I've specifically seen in the field,
    besides CPU utilization (which isn't great, but could be worse) is
    temporary file use for large shared_buffers, due to writing out one
    row to the tuplestore per buffer entry.
    
    Here is an example from a production database, running Postgres 16
    with 200GB shared_buffers:
    
    SHOW shared_buffers;
    
     shared_buffers
    ----------------
     207873040kB
    (1 row)
    
    EXPLAIN (ANALYZE, BUFFERS) SELECT reldatabase, relfilenode, count(*)
    FROM pg_buffercache
    WHERE reldatabase IS NOT NULL
    GROUP BY 1, 2;
    
                                                                    QUERY
    PLAN
    -------------------------------------------------------------------------------------------------------------------------------------------
     HashAggregate  (cost=17.46..19.46 rows=200 width=16) (actual
    time=12999.311..12999.526 rows=1683 loops=1)
       Group Key: p.reldatabase, p.relfilenode
       Batches: 1  Memory Usage: 209kB
       Buffers: temp read=158595 written=158595
       I/O Timings: temp read=246.513 write=1013.565
       ->  Function Scan on pg_buffercache_pages p  (cost=0.00..10.00
    rows=995 width=8) (actual time=5403.944..8864.129 rows=25984130
    loops=1)
             Filter: (reldatabase IS NOT NULL)
             Buffers: temp read=158595 written=158595
             I/O Timings: temp read=246.513 write=1013.565
     Planning:
       Buffers: shared hit=5
     Planning Time: 0.101 ms
     Execution Time: 13200.972 ms
    (13 rows)
    
    In this case we used a ~1.2GB temporary file to write out 25 million
    rows, what could have been a ~100kb allocation in memory instead (~40
    bytes BufferRelStatsEntry in v2 * 2048 slots in simplehash).
    
    
    Thanks,
    Lukas
    
    --
    Lukas Fittl
    
    
    
    
  21. Re: pg_buffercache: Add per-relation summary stats

    Lukas Fittl <lukas@fittl.com> — 2026-03-28T19:38:04Z

    Hi Tomas,
    
    Thanks for reviewing - just a quick response on your code review
    comments specifically:
    
    On Fri, Mar 27, 2026 at 3:58 PM Tomas Vondra <tomas@vondra.me> wrote:
    > I gave it a try on an azure VM with 32GB shared buffers, to make it a
    > bit more realistic, and my timings are 10ms vs. 700ms. But I also wonder
    > if the original timings really were from a cluster with 128MB, because
    > for me that shows 0.3ms vs. 3ms (so an order of magnitude faster than
    > what was reported). But I suppose that's also hw specific.
    
    Yeah, those initial numbers were from my Apple Silicon M3 ARM laptop
    without any special configuration, just for reference.
    
    > A couple minor comments about the code:
    >
    > 1) Isn't this check unnecessary? All entries should have buffers > 0.
    >
    >     if (entry->buffers == 0)
    >         continue;
    
    Yeah, good point, that is there to protect the division for the avg
    usage count, but I agree in practice this shouldn't be reached. I
    could make it an assert, just in case.
    
    > 2) Shouldn't this check BM_TAG_VALID too? Or is BM_VALID enough to look
    > at the bufHdr->tag?
    >
    >     /* Skip unused/invalid buffers */
    >     if (!(buf_state & BM_VALID))
    >         continue;
    >
    
    Good point, I think that makes sense to check BM_TAG_VALID here as well.
    
    FWIW, the function as-is does not lock the buffer header with
    LockBufHdr (intentionally to lower overhead), which means we can read
    a stale relation reference. I think that's okay for aggregate level /
    monitoring type information, but just want to call it out in this
    context.
    
    > 3) I think "buffers" argument should be renamed to "buffers_unused" for
    > consistency with pg_buffercache_summary.
    
    I assume you meant "buffers_used" instead of "buffers_unused" -
    assuming yes, that makes sense for consistency.
    
    ---
    
    I'll hold off on posting a new version for now, since I think we'd
    have to figure out a solution to the work_mem question at the very
    least, and it sounds like right now its also a toss-up in terms of
    overall interest to get this committed.
    
    
    
    Thanks,
    Lukas
    
    --
    Lukas Fittl
    
    
    
    
  22. Re: pg_buffercache: Add per-relation summary stats

    Heikki Linnakangas <hlinnaka@iki.fi> — 2026-04-07T13:07:45Z

    On 28/03/2026 06:18, Ashutosh Bapat wrote:
    > Parallely myself and Palak Chaturvedi developed a quick patch to
    > modernise pg_buffercache_pages() and use tuplestore so that it doesn't
    > have to rely on NBuffers being the same between start of the scan,
    > when memory allocated, when the scan ends - a condition possible with
    > resizing buffer cache. It seems to improve the timings by about 10-30%
    > on my laptop for 128MB buffercache size. Without this patch the time
    > taken to execute Lukas's query varies between 10-15ms on my laptop.
    > With this patch it varies between 8-9ms. So the timing is more stable
    > as a side effect. It's not a 10x improvement that we are looking for
    > but it looks like a step in the right direction. That improvement
    > seems to come purely because we avoid creating a heap tuple. I wonder
    > if there are some places up in the execution tree where full
    > heaptuples get formed again instead of continuing to use minimal
    > tuples or places where we perform some extra actions that are not
    > required.
    > 
    > I didn't dig into the history to find out why we didn't modernize
    > pg_buffercache_pages(). I don't see any hazard though.
    
    Committed this modernization patch, thanks!
    
    It would be nice to have a proper row-at-a-time mode that would avoid 
    materializing the result, but collecting all the data in a temporary 
    array is clearly worse than just putting them to the tuplestore 
    directly. The only reason I can think of why we'd prefer to use a 
    temporary array like that is to get a more consistent snapshot of all 
    the buffers, by keeping the time spent scanning the buffers as short as 
    possible. But we're not getting a consistent view anyway, it's just a 
    matter of degree.
    
    I wondered about this in pg_buffercache_pages.c:
    
    > 	/*
    > 	 * To smoothly support upgrades from version 1.0 of this extension
    > 	 * transparently handle the (non-)existence of the pinning_backends
    > 	 * column. We unfortunately have to get the result type for that... - we
    > 	 * can't use the result type determined by the function definition without
    > 	 * potentially crashing when somebody uses the old (or even wrong)
    > 	 * function definition though.
    > 	 */
    > 	if (get_call_result_type(fcinfo, NULL, &expected_tupledesc) != TYPEFUNC_COMPOSITE)
    > 		elog(ERROR, "return type must be a row type");
    > 
    > 	if (expected_tupledesc->natts < NUM_BUFFERCACHE_PAGES_MIN_ELEM ||
    > 		expected_tupledesc->natts > NUM_BUFFERCACHE_PAGES_ELEM)
    > 		elog(ERROR, "incorrect number of output arguments");
    
    I guess it's still needed, if you have pg_upgraded all the way from 1.0. 
    To test that, I created this view to match the old 1.0 definition:
    
    CREATE VIEW public.legacy_pg_buffercache AS
      SELECT bufferid,
         relfilenode,
         reltablespace,
         reldatabase,
         relforknumber,
         relblocknumber,
         isdirty,
         usagecount
        FROM public.pg_buffercache_pages() p(bufferid integer, relfilenode 
    oid, reltablespace oid, reldatabase oid, relforknumber smallint, 
    relblocknumber bigint, isdirty boolean, usagecount smallint);
    
    "select * from public.legacy_pg_buffercache" still works, so all good.
    
    - Heikki
    
    
    
    
  23. Re: pg_buffercache: Add per-relation summary stats

    Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-04-07T13:23:46Z

    On Tue, Apr 7, 2026 at 6:37 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 28/03/2026 06:18, Ashutosh Bapat wrote:
    > > Parallely myself and Palak Chaturvedi developed a quick patch to
    > > modernise pg_buffercache_pages() and use tuplestore so that it doesn't
    > > have to rely on NBuffers being the same between start of the scan,
    > > when memory allocated, when the scan ends - a condition possible with
    > > resizing buffer cache. It seems to improve the timings by about 10-30%
    > > on my laptop for 128MB buffercache size. Without this patch the time
    > > taken to execute Lukas's query varies between 10-15ms on my laptop.
    > > With this patch it varies between 8-9ms. So the timing is more stable
    > > as a side effect. It's not a 10x improvement that we are looking for
    > > but it looks like a step in the right direction. That improvement
    > > seems to come purely because we avoid creating a heap tuple. I wonder
    > > if there are some places up in the execution tree where full
    > > heaptuples get formed again instead of continuing to use minimal
    > > tuples or places where we perform some extra actions that are not
    > > required.
    > >
    > > I didn't dig into the history to find out why we didn't modernize
    > > pg_buffercache_pages(). I don't see any hazard though.
    >
    > Committed this modernization patch, thanks!
    >
    > It would be nice to have a proper row-at-a-time mode that would avoid
    > materializing the result, but collecting all the data in a temporary
    > array is clearly worse than just putting them to the tuplestore
    > directly. The only reason I can think of why we'd prefer to use a
    > temporary array like that is to get a more consistent snapshot of all
    > the buffers, by keeping the time spent scanning the buffers as short as
    > possible. But we're not getting a consistent view anyway, it's just a
    > matter of degree.
    >
    
    Thanks a lot. Makes code in buffer resizing a bit simpler esp. code
    changes in this module. Probably it won't need any code changes now in
    the buffer resizing patches.
    
    > I wondered about this in pg_buffercache_pages.c:
    >
    > >       /*
    > >        * To smoothly support upgrades from version 1.0 of this extension
    > >        * transparently handle the (non-)existence of the pinning_backends
    > >        * column. We unfortunately have to get the result type for that... - we
    > >        * can't use the result type determined by the function definition without
    > >        * potentially crashing when somebody uses the old (or even wrong)
    > >        * function definition though.
    > >        */
    > >       if (get_call_result_type(fcinfo, NULL, &expected_tupledesc) != TYPEFUNC_COMPOSITE)
    > >               elog(ERROR, "return type must be a row type");
    > >
    > >       if (expected_tupledesc->natts < NUM_BUFFERCACHE_PAGES_MIN_ELEM ||
    > >               expected_tupledesc->natts > NUM_BUFFERCACHE_PAGES_ELEM)
    > >               elog(ERROR, "incorrect number of output arguments");
    >
    > I guess it's still needed, if you have pg_upgraded all the way from 1.0.
    > To test that, I created this view to match the old 1.0 definition:
    >
    > CREATE VIEW public.legacy_pg_buffercache AS
    >   SELECT bufferid,
    >      relfilenode,
    >      reltablespace,
    >      reldatabase,
    >      relforknumber,
    >      relblocknumber,
    >      isdirty,
    >      usagecount
    >     FROM public.pg_buffercache_pages() p(bufferid integer, relfilenode
    > oid, reltablespace oid, reldatabase oid, relforknumber smallint,
    > relblocknumber bigint, isdirty boolean, usagecount smallint);
    >
    > "select * from public.legacy_pg_buffercache" still works, so all good.
    
    Yeah. At some point we should get rid of this code, but it would
    require some "upgrade" action from the customer as well.
    
    -- 
    Best Wishes,
    Ashutosh Bapat
    
    
    
    
  24. Re: pg_buffercache: Add per-relation summary stats

    Andres Freund <andres@anarazel.de> — 2026-04-07T13:47:26Z

    Hi,
    
    On 2026-04-07 16:07:45 +0300, Heikki Linnakangas wrote:
    > On 28/03/2026 06:18, Ashutosh Bapat wrote:
    > > Parallely myself and Palak Chaturvedi developed a quick patch to
    > > modernise pg_buffercache_pages() and use tuplestore so that it doesn't
    > > have to rely on NBuffers being the same between start of the scan,
    > > when memory allocated, when the scan ends - a condition possible with
    > > resizing buffer cache. It seems to improve the timings by about 10-30%
    > > on my laptop for 128MB buffercache size. Without this patch the time
    > > taken to execute Lukas's query varies between 10-15ms on my laptop.
    > > With this patch it varies between 8-9ms. So the timing is more stable
    > > as a side effect. It's not a 10x improvement that we are looking for
    > > but it looks like a step in the right direction. That improvement
    > > seems to come purely because we avoid creating a heap tuple. I wonder
    > > if there are some places up in the execution tree where full
    > > heaptuples get formed again instead of continuing to use minimal
    > > tuples or places where we perform some extra actions that are not
    > > required.
    
    I don't think that's the reason for the improvement - tuplestore_putvalues()
    forms a minimal tuple, and the cost to form a minimal tuple and a heap tuple
    aren't meaningfully different.
    
    I think the problem is that we materialize rowmode SRFs as a tuplestore if
    they are in the from list.  You can easily see this even with just
    generate_series():
    
    postgres[1520825][1]=# SELECT count(*) FROM generate_series(1, 1000000);
    ┌─────────┐
    │  count  │
    ├─────────┤
    │ 1000000 │
    └─────────┘
    (1 row)
    
    Time: 117.939 ms
    postgres[1520825][1]=# SELECT count(*) FROM (SELECT generate_series(1, 1000000));
    ┌─────────┐
    │  count  │
    ├─────────┤
    │ 1000000 │
    └─────────┘
    (1 row)
    
    Time: 58.914 ms
    
    
    Of course, because pg_buffercache_pages() is archaicially defined without
    defininig its output columns, you can't actually use it in the select list.
    
    But that can be fixed:
    
    CREATE FUNCTION pg_buffercache_pages_fast(OUT bufferid integer, OUT relfilenode oid, OUT reltablespace oid, OUT reldatabase oid,
             OUT relforknumber int2, OUT relblocknumber int8, OUT isdirty bool, OUT usagecount int2,
             OUT pinning_backends int4)
    RETURNS SETOF RECORD
    AS '$libdir/pg_buffercache', 'pg_buffercache_pages'
    LANGUAGE C PARALLEL SAFE;
    
    60GB of s_b, mostly filled, with 257c8231bf97a77378f6fedb826b1243f0a41612
    reverted.
    
    SELECT count(*) FROM (SELECT pg_buffercache_pages_fast());
    Time: 1518.704 ms (00:01.519)
    
    SELECT count(*) FROM pg_buffercache_pages_fast();
    Time: 2008.101 ms (00:02.008)
    
    
    > > I didn't dig into the history to find out why we didn't modernize
    > > pg_buffercache_pages(). I don't see any hazard though.
    >
    > Committed this modernization patch, thanks!
    >
    > It would be nice to have a proper row-at-a-time mode that would avoid
    > materializing the result, but collecting all the data in a temporary array
    > is clearly worse than just putting them to the tuplestore directly. The only
    > reason I can think of why we'd prefer to use a temporary array like that is
    > to get a more consistent snapshot of all the buffers, by keeping the time
    > spent scanning the buffers as short as possible. But we're not getting a
    > consistent view anyway, it's just a matter of degree.
    
    Seems like a reasonably large difference in degree whether you have a snapshot
    collected in one loop, or you do things like spilling a tuplestore to disk in
    between.
    
    Greetings,
    
    Andres Freund
    
    
    
    
  25. Re: pg_buffercache: Add per-relation summary stats

    Heikki Linnakangas <hlinnaka@iki.fi> — 2026-04-07T15:55:23Z

    On 07/04/2026 16:47, Andres Freund wrote:
    > On 2026-04-07 16:07:45 +0300, Heikki Linnakangas wrote:
    >> On 28/03/2026 06:18, Ashutosh Bapat wrote:
    >>> Parallely myself and Palak Chaturvedi developed a quick patch to
    >>> modernise pg_buffercache_pages() and use tuplestore so that it doesn't
    >>> have to rely on NBuffers being the same between start of the scan,
    >>> when memory allocated, when the scan ends - a condition possible with
    >>> resizing buffer cache. It seems to improve the timings by about 10-30%
    >>> on my laptop for 128MB buffercache size. Without this patch the time
    >>> taken to execute Lukas's query varies between 10-15ms on my laptop.
    >>> With this patch it varies between 8-9ms. So the timing is more stable
    >>> as a side effect. It's not a 10x improvement that we are looking for
    >>> but it looks like a step in the right direction. That improvement
    >>> seems to come purely because we avoid creating a heap tuple. I wonder
    >>> if there are some places up in the execution tree where full
    >>> heaptuples get formed again instead of continuing to use minimal
    >>> tuples or places where we perform some extra actions that are not
    >>> required.
    > 
    > I don't think that's the reason for the improvement - tuplestore_putvalues()
    > forms a minimal tuple, and the cost to form a minimal tuple and a heap tuple
    > aren't meaningfully different.
    
    Yeah, I wasn't fully convinced of that part either, which is why I left 
    it out of the commit message. I mostly wanted to get rid of the 
    double-buffering where we first accumulated all the data in an array.
    
    > I think the problem is that we materialize rowmode SRFs as a tuplestore if
    > they are in the from list.  You can easily see this even with just
    > generate_series():
    > 
    > postgres[1520825][1]=# SELECT count(*) FROM generate_series(1, 1000000);
    > ┌─────────┐
    > │  count  │
    > ├─────────┤
    > │ 1000000 │
    > └─────────┘
    > (1 row)
    > 
    > Time: 117.939 ms
    > postgres[1520825][1]=# SELECT count(*) FROM (SELECT generate_series(1, 1000000));
    > ┌─────────┐
    > │  count  │
    > ├─────────┤
    > │ 1000000 │
    > └─────────┘
    > (1 row)
    > 
    > Time: 58.914 ms
    
    Oh, to be honest I didn't remember that we *don't* materialize when it's 
    in the target list.
    
    > Of course, because pg_buffercache_pages() is archaicially defined without
    > defininig its output columns, you can't actually use it in the select list.
    > 
    > But that can be fixed:
    > 
    > CREATE FUNCTION pg_buffercache_pages_fast(OUT bufferid integer, OUT relfilenode oid, OUT reltablespace oid, OUT reldatabase oid,
    >           OUT relforknumber int2, OUT relblocknumber int8, OUT isdirty bool, OUT usagecount int2,
    >           OUT pinning_backends int4)
    > RETURNS SETOF RECORD
    > AS '$libdir/pg_buffercache', 'pg_buffercache_pages'
    > LANGUAGE C PARALLEL SAFE;
    > 
    > 60GB of s_b, mostly filled, with 257c8231bf97a77378f6fedb826b1243f0a41612
    > reverted.
    > 
    > SELECT count(*) FROM (SELECT pg_buffercache_pages_fast());
    > Time: 1518.704 ms (00:01.519)
    > 
    > SELECT count(*) FROM pg_buffercache_pages_fast();
    > Time: 2008.101 ms (00:02.008)
    
    Hmm, we could easily go back to ValuePerCall mode, while still getting 
    rid of the temporary array and the other modernization. But "SELECT * 
    FROM pg_buffercache_pages_fast()" doesn't actually produce the result we 
    want:
    
    postgres=# SELECT pg_buffercache_pages_fast() limit 1;
      pg_buffercache_pages_fast
    ---------------------------
      (1,1262,1664,0,0,0,f,5,0)
    (1 row)
    
    Can we turn that into the right shaep for the pg_buffercache view? This 
    works:
    
    postgres=# SELECT (pg_buffercache_pages_fast()).* limit 1;
      bufferid | relfilenode | reltablespace | reldatabase | relforknumber | 
    relblocknumber | isdirty | usagecount | pinning_backends
    ----------+-------------+---------------+-------------+---------------+----------------+---------+------------+------------------
             1 |        1262 |          1664 |           0 |             0 | 
                  0 | f       |          5 |                0
    (1 row)
    
    But that doesn't seem to be faster anymore, despite avoiding the 
    tuplestore, because of overheads elsewhere in the executor.
    
    >>> I didn't dig into the history to find out why we didn't modernize
    >>> pg_buffercache_pages(). I don't see any hazard though.
    >>
    >> Committed this modernization patch, thanks!
    >>
    >> It would be nice to have a proper row-at-a-time mode that would avoid
    >> materializing the result, but collecting all the data in a temporary array
    >> is clearly worse than just putting them to the tuplestore directly. The only
    >> reason I can think of why we'd prefer to use a temporary array like that is
    >> to get a more consistent snapshot of all the buffers, by keeping the time
    >> spent scanning the buffers as short as possible. But we're not getting a
    >> consistent view anyway, it's just a matter of degree.
    > 
    > Seems like a reasonably large difference in degree whether you have a snapshot
    > collected in one loop, or you do things like spilling a tuplestore to disk in
    > between.
    
    Looking at the original discussion when pg_buffercache was introduced 
    [1], the first patch version didn't have the array, but it was added in 
    v2 to avoid holding BufMappingLock for long. But we gave up on getting a 
    consistent snapshot and stopped holding the lock in commit 6e654546fb.
    
    To summarize my current thinking, I think this is fine as committed. I'm 
    not worried about the more "stretched out" snapshot that you get. And it 
    would be nice if we had a better, faster ValuePerCall mode that also 
    worked with FunctionScans, but we don't.
    
    [1] https://www.postgresql.org/message-id/42297D6E.3000505%40coretech.co.nz
    
    - Heikki
    
    
    
    
    
  26. Re: pg_buffercache: Add per-relation summary stats

    Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-04-08T06:36:32Z

    On Tue, Apr 7, 2026 at 9:25 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 07/04/2026 16:47, Andres Freund wrote:
    > > On 2026-04-07 16:07:45 +0300, Heikki Linnakangas wrote:
    > >> On 28/03/2026 06:18, Ashutosh Bapat wrote:
    > >>> Parallely myself and Palak Chaturvedi developed a quick patch to
    > >>> modernise pg_buffercache_pages() and use tuplestore so that it doesn't
    > >>> have to rely on NBuffers being the same between start of the scan,
    > >>> when memory allocated, when the scan ends - a condition possible with
    > >>> resizing buffer cache. It seems to improve the timings by about 10-30%
    > >>> on my laptop for 128MB buffercache size. Without this patch the time
    > >>> taken to execute Lukas's query varies between 10-15ms on my laptop.
    > >>> With this patch it varies between 8-9ms. So the timing is more stable
    > >>> as a side effect. It's not a 10x improvement that we are looking for
    > >>> but it looks like a step in the right direction. That improvement
    > >>> seems to come purely because we avoid creating a heap tuple. I wonder
    > >>> if there are some places up in the execution tree where full
    > >>> heaptuples get formed again instead of continuing to use minimal
    > >>> tuples or places where we perform some extra actions that are not
    > >>> required.
    > >
    > > I don't think that's the reason for the improvement - tuplestore_putvalues()
    > > forms a minimal tuple, and the cost to form a minimal tuple and a heap tuple
    > > aren't meaningfully different.
    >
    > Yeah, I wasn't fully convinced of that part either, which is why I left
    > it out of the commit message. I mostly wanted to get rid of the
    > double-buffering where we first accumulated all the data in an array.
    >
    
    Just because of the name of the function, I thought
    tuplestore_puttuple stores virtual tuple. But looking at the function
    it's clearly using minimal tuples. Sorry for my misunderstanding.
    
    > > I think the problem is that we materialize rowmode SRFs as a tuplestore if
    > > they are in the from list.  You can easily see this even with just
    > > generate_series():
    > >
    > > postgres[1520825][1]=# SELECT count(*) FROM generate_series(1, 1000000);
    > > ┌─────────┐
    > > │  count  │
    > > ├─────────┤
    > > │ 1000000 │
    > > └─────────┘
    > > (1 row)
    > >
    > > Time: 117.939 ms
    > > postgres[1520825][1]=# SELECT count(*) FROM (SELECT generate_series(1, 1000000));
    > > ┌─────────┐
    > > │  count  │
    > > ├─────────┤
    > > │ 1000000 │
    > > └─────────┘
    > > (1 row)
    > >
    > > Time: 58.914 ms
    >
    > Oh, to be honest I didn't remember that we *don't* materialize when it's
    > in the target list.
    >
    
    IIUC, query using SRF in targetlist runs faster because it does "not"
    use tuplestore. I consistently saw 10%-30% performance improvement
    after using tuplestore in pg_buffercache_pages(). Is that purely
    because of avoiding an in-memory array?
    
    --
    Best Wishes,
    Ashutosh Bapat
    
    
    
    
  27. Re: pg_buffercache: Add per-relation summary stats

    Robert Haas <robertmhaas@gmail.com> — 2026-06-18T14:09:43Z

    On Fri, Mar 27, 2026 at 6:59 PM Tomas Vondra <tomas@vondra.me> wrote:
    > The main argument here seems to be the performance, and the initial
    > message demonstrates a 10x speedup (2ms vs. 20ms) on a cluster with
    > 128MB shared buffers. Unless I misunderstood what config it uses.
    
    So, my opinion on this point is that the results Lukas shows later in
    the thread are compelling. The query takes 13s and writes 1.2GB for
    what should be a trivial monitoring query. That seems like it's pretty
    clearly enough overhead to be a problem for a monitoring query. The
    problem isn't even just that you can't afford to wait 13s for a query
    you run every 10m -- it's that the query itself is consuming enough
    system resources to skew your other monitoring. For example, if you're
    monitoring your system load average or CPU usage or disk usage over
    time, you're going to see spikes when this query runs. That's not the
    worst thing that has ever happened to anyone, but it's definitely not
    great, and I can totally understand someone not being willing to incur
    that much overhead. I don't believe we should accept the argument that
    this patch doesn't save enough to matter; I think it does.
    
    > Let's assume it's worth it. I wonder what similar summaries might be
    > interesting for users. I'd probably want to see a per-database summary,
    > especially on a shared / multi-tenant cluster. But AFAICS I can
    > calculate that from the pg_buffercache_relations() result, except that
    > I'll have to recalculate the usagecount.
    
    I think it would be better to return the total usagecount and let the
    caller divide if they want, rather the average.
    
    But more generally, I agree that we don't want something that is
    overly specific to one person's use case. Thinking about how to make a
    function like this useful to as many people as possible is a
    worthwhile activity. I don't know what more we can do that makes
    sense. For instance, we could add a database OID argument that can be
    NULL or the OID of a database and it filters out everything else. I'm
    not sure that would pull its weight -- the big gains are probably
    coming from doing the aggregation using bespoke code, rather than
    filtering out rows beforehand -- but maybe.
    
    On the whole, I'm inclined to think we should accept this. There's
    plenty of cases where it won't save much, and it is also true that it
    would be nice to improve the core infrastructure so that queries like
    this can be better-optimized. But I don't think that's going to happen
    right away, and even when it does happen I bet the savings from a
    patch like this will still be pretty significant. I also believe that
    aggregating the pg_buffercache results by relation is probably a very
    common use case, so it's doesn't seem to me as though there would be
    ten other equally-compelling versions of this.
    
    > One thing we lose by doing ad hoc aggregation (instead of just relying
    > on the regular SQL aggregation operators) is lack of memory limit.
    > There's a simple in-memory hash table, no spilling to disk etc. The
    > simple pg_buffercache view does not have this issue, because the
    > tuplestore will spill to disk after hitting work_mem. Simplehash won't.
    >
    > The entries are ~48B, so there would need to be buffers for ~100k
    > (relfilenode,forknum) combinations to overflow 4MB. It's not very
    > common, but I've seen systems with more relations that this. Would be
    > good to show some numbers showing it's not an issue.
    
    This is a good point, but I'm not sure I believe there's a real issue
    here. It seems as though the kinds of systems where this function is
    likely to be important for performance  are probably those with 100GB+
    of shared_buffers, so 12m+ buffers, so ... half a gigabyte? Maybe
    somewhat more with memory allocation overheads and so forth? I feel
    like if you have 100GB of shared_buffers, you probably have work_mem
    set to 1GB+, or at least have that much memory free. And even then you
    only need that if every single shared buffer belongs to a different
    relation, which seems like a thing that will not occur in practice.
    
    Obviously, there are things we could do to limit memory consumption
    here. I think the easiest thing might be to just write out the entire
    hash table in hash value order to a temporary file every time we
    exhaust work_mem, and then do a merge pass over all those temporary
    files at the end. I don't think we can plausibly need more than one
    merge pass to keep memory usage within acceptable limits, and I don't
    think this would need to be a crazy amount of code. But I'm also not
    sure I believe we really need it. The concern about code maintenance
    that has been raised is valid here as it is for all patches, so we
    shouldn't bloat the patch with code that it doesn't really need, and I
    think it's worth considering whether spill-to-disk code falls into
    that category.
    
    -- 
    Robert Haas
    EDB: http://www.enterprisedb.com