Thread: Summary function for pg_buffercache

Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi hackers,

Added a pg_buffercache_summary() function to retrieve an aggregated summary information with less cost.

It's often useful to know only how many buffers are used, how many of them are dirty etc. for monitoring purposes.
This info can already be retrieved by pg_buffercache. The extension currently creates a row with many details for each buffer, then summary info can be aggregated from that returned table. 
But it is quite expensive to run regularly for monitoring.

The attached patch adds a pg_buffercache_summary() function to get this summary info faster. 
New function only collects following info and returns them in a single row:
- used_buffers = number of buffers with a valid relfilenode (both dirty and not)
- unused_buffers = number of buffers with invalid relfilenode
- dirty_buffers = number of dirty buffers.
- pinned_buffers = number of buffers that have at least one pinning backend (i.e. refcount > 0)
- average usagecount of used buffers

One other difference between pg_buffercache_summary and pg_buffercache_pages is that pg_buffercache_summary does not get locks on buffer headers as opposed to pg_buffercache_pages.
Since the purpose of pg_buffercache_summary is just to give us an overall idea about shared buffers and to be a cheaper function, locks are not strictly needed. 

To compare pg_buffercache_summary() and  pg_buffercache_pages(), I used a simple query to aggregate the summary information above by calling   pg_buffercache_pages().
Here is the result:

postgres=# show shared_buffers;
 shared_buffers
----------------
 16GB
(1 row)

Time: 0.756 ms
postgres=# SELECT relfilenode <> 0 AS is_valid, isdirty, count(*) FROM pg_buffercache GROUP BY relfilenode <> 0, isdirty;
 is_valid | isdirty |  count
----------+---------+---------
 t        | f       |     209
          |         | 2096904
 t        | t       |      39
(3 rows)

Time: 1434.870 ms (00:01.435)
postgres=# select * from pg_buffercache_summary();
 used_buffers | unused_buffers | dirty_buffers | pinned_buffers | avg_usagecount
--------------+----------------+---------------+----------------+----------------
          248 |        2096904 |            39 |              0 |       3.141129
(1 row)

Time: 9.712 ms

There is a significant difference between timings of those two functions, even though they return similar results. 

I would appreciate any feedback/comment on this change.

Thanks,
Melih
Attachment

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi hackers,

I also added documentation changes into the patch.
You can find it attached.

 I would appreciate any feedback about this pg_buffercache_summary function.

Best,
Melih
Attachment

Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi Melih,

> I would appreciate any feedback/comment on this change.

Another benefit of pg_buffercache_summary() you didn't mention is that
it allocates much less memory than pg_buffercache_pages() does.

Here is v3 where I added this to the documentation. The patch didn't
apply to the current master branch with the following error:

```
pg_buffercache_pages.c:286:19: error: no member named 'rlocator' in
'struct buftag'
                if (bufHdr->tag.rlocator.relNumber != InvalidOid)
                    ~~~~~~~~~~~ ^
1 error generated.
```

I fixed this too. Additionally, the patch was pgindent'ed and some
typos were fixed.

However I'm afraid you can't examine BufferDesc's without taking
locks. This is explicitly stated in buf_internals.h:

"""
Buffer header lock (BM_LOCKED flag) must be held to EXAMINE or change
TAG, state or wait_backend_pgprocno fields.
"""

Let's consider this code again (this is after my fix):

```
if (RelFileNumberIsValid(BufTagGetRelNumber(bufHdr))) {
 /* ... */
}
```

When somebody modifies relNumber concurrently (e.g. calls
ClearBufferTag()) this will cause an undefined behaviour.

I suggest we focus on saving the memory first and then think about the
performance, if necessary.

-- 
Best regards,
Aleksander Alekseev

Attachment

Re: Summary function for pg_buffercache

From
Nathan Bossart
Date:
On Fri, Sep 09, 2022 at 05:36:45PM +0300, Aleksander Alekseev wrote:
> However I'm afraid you can't examine BufferDesc's without taking
> locks. This is explicitly stated in buf_internals.h:

Yeah, when I glanced at this patch earlier, I wondered about this.

> I suggest we focus on saving the memory first and then think about the
> performance, if necessary.

+1

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com



Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi hackers,

> > I suggest we focus on saving the memory first and then think about the
> > performance, if necessary.
>
> +1

I made a mistake in v3 cfbot complained about. It should have been:

```
if (RelFileNumberIsValid(BufTagGetRelNumber(&bufHdr->tag)))
```

Here is the corrected patch.

-- 
Best regards,
Aleksander Alekseev

Attachment

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi Aleksander and Nathan,

Thanks for your comments.

Aleksander Alekseev <aleksander@timescale.com>, 9 Eyl 2022 Cum, 17:36 tarihinde şunu yazdı:
However I'm afraid you can't examine BufferDesc's without taking
locks. This is explicitly stated in buf_internals.h:

"""
Buffer header lock (BM_LOCKED flag) must be held to EXAMINE or change
TAG, state or wait_backend_pgprocno fields.
"""

I wasn't aware of this explanation. Thanks for pointing it out.

When somebody modifies relNumber concurrently (e.g. calls
ClearBufferTag()) this will cause an undefined behaviour.

I thought that it wouldn't really be a problem even if relNumber is modified concurrently, since the function does not actually rely on the actual values.
I'm not sure about what undefined behaviour could harm this badly. It seemed to me that it would read an invalid relNumber in the worst case scenario. 
But I'm not actually familiar with buffer related parts of the code, so I might be wrong.
And I'm okay with taking header locks if necessary. 

In the attached patch, I added buffer header locks just before examining tag as follows:

+ buf_state = LockBufHdr(bufHdr);
+
+ /* Invalid RelFileNumber means the buffer is unused */
+ if (RelFileNumberIsValid(BufTagGetRelNumber(&bufHdr->tag)))
+ {
...
+ }
...
+ UnlockBufHdr(bufHdr, buf_state);


> > I suggest we focus on saving the memory first and then think about the
> > performance, if necessary.
>
> +1

I again did the same quick benchmarking, here are the numbers with locks.

postgres=# show shared_buffers;
 shared_buffers
----------------
 16GB
(1 row)

postgres=#  SELECT relfilenode <> 0 AS is_valid, isdirty, count(*) FROM pg_buffercache GROUP BY relfilenode <> 0, isdirty;
 is_valid | isdirty |  count
----------+---------+---------
 t        | f       |     256
          |         | 2096876
 t        | t       |      20
(3 rows)

Time: 1024.456 ms (00:01.024)

postgres=#  select * from pg_buffercache_summary();
 used_buffers | unused_buffers | dirty_buffers | pinned_buffers | avg_usagecount
--------------+----------------+---------------+----------------+----------------
          282 |        2096870 |            20 |              0 |      3.4574468
(1 row)

Time: 33.074 ms

Yes, locks slowed pg_buffercache_summary down. But there is still quite a bit of performance improvement, plus memory saving as you mentioned.
 
Here is the corrected patch.
 
Also thanks for corrections.

Best,
Melih
 
Attachment

Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi Melih,

> I'm not sure about what undefined behaviour could harm this badly.

You are right that in practice nothing wrong will (probably) happen on x86/x64 architecture with (most?) modern C compilers. This is not true in the general case though. It's up to the compiler to decide how reading the bufHdr->tag is going to be actually implemented. This can be one assembly instruction or several instructions. This reading can be optimized-out if the compiler believes the required value is already in the register, etc. Since the result will be different depending on the assembly code used this is an undefined behaviour and we can't use code like this.

> In the attached patch, I added buffer header locks just before examining tag as follows

Many thanks for the updated patch! It looks better now.

However I have somewhat mixed feelings about avg_usagecount. Generally AVG() is a relatively useless methric for monitoring. What if the user wants MIN(), MAX() or let's say a 99th percentile? I suggest splitting it into usagecount_min, usagecount_max and usagecount_sum. AVG() can be derived as usercount_sum / used_buffers.

Also I suggest changing the names of the columns in order to make them consistent with the rest of the system. If you consider pg_stat_activity and family [1] you will notice that the columns are named (entity)_(property), e.g. backend_xid, backend_type, client_addr, etc. So instead of used_buffers and unused_buffers the naming should be buffers_used and buffers_unused.

[1]: https://www.postgresql.org/docs/current/monitoring-stats.html

--
Best regards,
Aleksander Alekseev

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hello Aleksander,

> I'm not sure about what undefined behaviour could harm this badly.

You are right that in practice nothing wrong will (probably) happen on x86/x64 architecture with (most?) modern C compilers. This is not true in the general case though. It's up to the compiler to decide how reading the bufHdr->tag is going to be actually implemented. This can be one assembly instruction or several instructions. This reading can be optimized-out if the compiler believes the required value is already in the register, etc. Since the result will be different depending on the assembly code used this is an undefined behaviour and we can't use code like this.

Got it. Thanks for explaining.
 
However I have somewhat mixed feelings about avg_usagecount. Generally AVG() is a relatively useless methric for monitoring. What if the user wants MIN(), MAX() or let's say a 99th percentile? I suggest splitting it into usagecount_min, usagecount_max and usagecount_sum. AVG() can be derived as usercount_sum / used_buffers.

Won't be usagecount_max almost always 5 as "BM_MAX_USAGE_COUNT" set to 5 in buf_internals.h? I'm not sure about how much usagecount_min would add either. 
A usagecount is always an integer between 0 and 5, it's not something unbounded. I think the 99th percentile would be much better than average if strong outlier values could occur. But in this case, I feel like an average value would be sufficiently useful as well. 
usagecount_sum would actually be useful since average can be derived from it. If you think that the sum of usagecounts has a meaning just by itself, it makes sense to include it. Otherwise, wouldn't showing directly averaged value be more useful?

 
Also I suggest changing the names of the columns in order to make them consistent with the rest of the system. If you consider pg_stat_activity and family [1] you will notice that the columns are named (entity)_(property), e.g. backend_xid, backend_type, client_addr, etc. So instead of used_buffers and unused_buffers the naming should be buffers_used and buffers_unused.

[1]: https://www.postgresql.org/docs/current/monitoring-stats.html

You're right. I will change the names accordingly. Thanks.


Regards,
Melih 

Re: Summary function for pg_buffercache

From
Andres Freund
Date:
Hi,

On 2022-09-09 17:36:45 +0300, Aleksander Alekseev wrote:
> I suggest we focus on saving the memory first and then think about the
> performance, if necessary.

Personally I think the locks part is at least as important - it's what makes
the production impact higher.

Greetings,

Andres Freund



Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi,

Also I suggest changing the names of the columns in order to make them consistent with the rest of the system. If you consider pg_stat_activity and family [1] you will notice that the columns are named (entity)_(property), e.g. backend_xid, backend_type, client_addr, etc. So instead of used_buffers and unused_buffers the naming should be buffers_used and buffers_unused.

[1]: https://www.postgresql.org/docs/current/monitoring-stats.html
 
I changed these names and updated the patch.

However I have somewhat mixed feelings about avg_usagecount. Generally AVG() is a relatively useless methric for monitoring. What if the user wants MIN(), MAX() or let's say a 99th percentile? I suggest splitting it into usagecount_min, usagecount_max and usagecount_sum. AVG() can be derived as usercount_sum / used_buffers.

Won't be usagecount_max almost always 5 as "BM_MAX_USAGE_COUNT" set to 5 in buf_internals.h? I'm not sure about how much usagecount_min would add either. 
A usagecount is always an integer between 0 and 5, it's not something unbounded. I think the 99th percentile would be much better than average if strong outlier values could occur. But in this case, I feel like an average value would be sufficiently useful as well. 
usagecount_sum would actually be useful since average can be derived from it. If you think that the sum of usagecounts has a meaning just by itself, it makes sense to include it. Otherwise, wouldn't showing directly averaged value be more useful?

Aleksander, do you still think the average usagecount is a bit useless? Or does it make sense to you to keep it like this?

> I suggest we focus on saving the memory first and then think about the
> performance, if necessary.

Personally I think the locks part is at least as important - it's what makes
the production impact higher.

I agree that it's important due to its high impact. I'm not sure how to avoid any undefined behaviour without locks though.
Even with locks, performance is much better. But is it good enough for production?


Thanks,
Melih
 
Attachment

Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi Melih,

> I changed these names and updated the patch.

Thanks for the updated patch!

> Aleksander, do you still think the average usagecount is a bit useless? Or does it make sense to you to keep it like
this?

I don't mind keeping the average.

> I'm not sure how to avoid any undefined behaviour without locks though.
> Even with locks, performance is much better. But is it good enough for production?

Potentially you could avoid taking locks by utilizing atomic
operations and lock-free algorithms. But these algorithms are
typically error-prone and not always produce a faster code than the
lock-based ones. I'm pretty confident this is out of scope of this
particular patch.

The patch v6 had several defacts:

* Trailing whitespaces (can be checked by applying the patch with `git am`)
* Wrong code formatting (can be fixed with pgindent)
* Several empty lines were removed which is not related to the
proposed change (can be seen with `git diff`)
* An unlikely division by zero if buffers_used = 0
* Missing part of the commit message added in v4

Here is a corrected patch v7. To me it seems to be in pretty good
shape, unless cfbot and/or other hackers will report any issues.

-- 
Best regards,
Aleksander Alekseev

Attachment

Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi hackers,

> Here is a corrected patch v7. To me it seems to be in pretty good
> shape, unless cfbot and/or other hackers will report any issues.

There was a missing empty line in pg_buffercache.out which made the
tests fail. Here is a corrected v8 patch.

-- 
Best regards,
Aleksander Alekseev

Attachment

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Aleksander Alekseev <aleksander@timescale.com>, 20 Eyl 2022 Sal, 13:57 tarihinde şunu yazdı:
There was a missing empty line in pg_buffercache.out which made the
tests fail. Here is a corrected v8 patch.

I was just sending a corrected patch without the missing line. 

Thanks a lot for all these reviews and the corrected patch.

Best,
Melih  

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi,

Seems like cfbot tests are passing now:

Best,
Melih

Melih Mutlu <m.melihmutlu@gmail.com>, 20 Eyl 2022 Sal, 14:00 tarihinde şunu yazdı:
Aleksander Alekseev <aleksander@timescale.com>, 20 Eyl 2022 Sal, 13:57 tarihinde şunu yazdı:
There was a missing empty line in pg_buffercache.out which made the
tests fail. Here is a corrected v8 patch.

I was just sending a corrected patch without the missing line. 

Thanks a lot for all these reviews and the corrected patch.

Best,
Melih  

Re: Summary function for pg_buffercache

From
Zhang Mingli
Date:
Hi,

Correct me if I’m wrong.

The doc says we don’t take lock during pg_buffercache_summary, but I see locks in the v8 patch, Isn’t it?

```
Similar to <function>pg_buffercache_pages</function> function
 <function>pg_buffercache_summary</function> doesn't take buffer manager
 locks, thus the result is not consistent across all buffers. This is
 intentional. The purpose of this function is to provide a general idea about
 the state of shared buffers as fast as possible. Additionally,
 <function>pg_buffercache_summary</function> allocates much less memory.

```




Regards,
Zhang Mingli
On Sep 20, 2022, 20:10 +0800, Melih Mutlu <m.melihmutlu@gmail.com>, wrote:
Hi,

Seems like cfbot tests are passing now:

Best,
Melih

Melih Mutlu <m.melihmutlu@gmail.com>, 20 Eyl 2022 Sal, 14:00 tarihinde şunu yazdı:
Aleksander Alekseev <aleksander@timescale.com>, 20 Eyl 2022 Sal, 13:57 tarihinde şunu yazdı:
There was a missing empty line in pg_buffercache.out which made the
tests fail. Here is a corrected v8 patch.

I was just sending a corrected patch without the missing line. 

Thanks a lot for all these reviews and the corrected patch.

Best,
Melih  

Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi Zhang,

> The doc says we don’t take lock during pg_buffercache_summary, but I see locks in the v8 patch, Isn’t it?
>
> ```
> Similar to <function>pg_buffercache_pages</function> function
>  <function>pg_buffercache_summary</function> doesn't take buffer manager
>  locks [...]
> ```

Correct, the procedure doesn't take the locks of the buffer manager.
It does take the locks of every individual buffer.

I agree that the text is somewhat confusing, but it is consistent with
the current description of pg_buffercache [1]. I think this is a
problem worth addressing but it also seems to be out of scope of the
proposed patch.

[1]: https://www.postgresql.org/docs/current/pgbuffercache.html

--
Best regards,
Aleksander Alekseev



Re: Summary function for pg_buffercache

From
Zhang Mingli
Date:
Hi,

Regards,
Zhang Mingli
On Sep 20, 2022, 20:43 +0800, Aleksander Alekseev <aleksander@timescale.com>, wrote:

Correct, the procedure doesn't take the locks of the buffer manager.
It does take the locks of every individual buffer.
Ah, now I get it, thanks.

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi Zhang,

Those are two different locks.
The locks that are taken in the patch are for buffer headers. This locks only the current buffer and makes that particular buffer's info consistent within itself.

However, the lock mentioned in the doc is for buffer manager which would prevent changes on any buffer if it's held. 
pg_buffercache_summary (and pg_buffercache_pages) does not hold buffer manager lock. Therefore, consistency across all buffers is not guaranteed.

For pg_buffercache_pages, self-consistent buffer information is useful since it shows each buffer separately.

For pg_buffercache_summary, even self-consistency may not matter much since results are aggregated and we can't see individual buffer information.
Consistency across all buffers is also not a concern since its purpose is to give an overall idea about the state of buffers.

I see that these two different locks in the same context can be confusing. I hope it is a bit more clear now.

Best,
Melih

Re: Summary function for pg_buffercache

From
Zhang Mingli
Date:
Hi,
On Sep 20, 2022, 20:49 +0800, Melih Mutlu <m.melihmutlu@gmail.com>, wrote:
Hi Zhang,

Those are two different locks.
The locks that are taken in the patch are for buffer headers. This locks only the current buffer and makes that particular buffer's info consistent within itself.

However, the lock mentioned in the doc is for buffer manager which would prevent changes on any buffer if it's held. 
pg_buffercache_summary (and pg_buffercache_pages) does not hold buffer manager lock. Therefore, consistency across all buffers is not guaranteed.

For pg_buffercache_pages, self-consistent buffer information is useful since it shows each buffer separately.

For pg_buffercache_summary, even self-consistency may not matter much since results are aggregated and we can't see individual buffer information.
Consistency across all buffers is also not a concern since its purpose is to give an overall idea about the state of buffers.

I see that these two different locks in the same context can be confusing. I hope it is a bit more clear now.

Best,
Melih
Thanks for your explanation, LGTM.

Re: Summary function for pg_buffercache

From
Andres Freund
Date:
Hi,

On 2022-09-20 12:45:24 +0300, Aleksander Alekseev wrote:
> > I'm not sure how to avoid any undefined behaviour without locks though.
> > Even with locks, performance is much better. But is it good enough for production?
>
> Potentially you could avoid taking locks by utilizing atomic
> operations and lock-free algorithms. But these algorithms are
> typically error-prone and not always produce a faster code than the
> lock-based ones. I'm pretty confident this is out of scope of this
> particular patch.

Why would you need lockfree operations?  All you need to do is to read
BufferDesc->state into a local variable and then make decisions based on that?


> +    for (int i = 0; i < NBuffers; i++)
> +    {
> +        BufferDesc *bufHdr;
> +        uint32        buf_state;
> +
> +        bufHdr = GetBufferDescriptor(i);
> +
> +        /* Lock each buffer header before inspecting. */
> +        buf_state = LockBufHdr(bufHdr);
> +
> +        /* Invalid RelFileNumber means the buffer is unused */
> +        if (RelFileNumberIsValid(BufTagGetRelNumber(&bufHdr->tag)))
> +        {
> +            buffers_used++;
> +            usagecount_avg += BUF_STATE_GET_USAGECOUNT(buf_state);
> +
> +            if (buf_state & BM_DIRTY)
> +                buffers_dirty++;
> +        }
> +        else
> +            buffers_unused++;
> +
> +        if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
> +            buffers_pinned++;
> +
> +        UnlockBufHdr(bufHdr, buf_state);
> +    }

I.e. instead of locking the buffer header as done above, this could just do
something along these lines:

        BufferDesc *bufHdr;
        uint32      buf_state;

        bufHdr = GetBufferDescriptor(i);

        buf_state = pg_atomic_read_u32(&bufHdr->state);

        if (buf_state & BM_VALID)
        {
            buffers_used++;
            usagecount_avg += BUF_STATE_GET_USAGECOUNT(buf_state);

            if (buf_state & BM_DIRTY)
                buffers_dirty++;
        }
        else
            buffers_unused++;

        if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
            buffers_pinned++;


Without a memory barrier you can get very slightly "out-of-date" values of the
state, but that's fine in this case.

Greetings,

Andres Freund



Re: Summary function for pg_buffercache

From
Aleksander Alekseev
Date:
Hi Andres,

> All you need to do is to read BufferDesc->state into a local variable and then make decisions based on that

You are right, thanks.

Here is the corrected patch.

-- 
Best regards,
Aleksander Alekseev

Attachment

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi,

Since header locks are removed again, I put some doc changes and comments back.

Thanks,
Melih
Attachment

Re: Summary function for pg_buffercache

From
Andres Freund
Date:
Hi,

On 2022-09-22 18:22:44 +0300, Melih Mutlu wrote:
> Since header locks are removed again, I put some doc changes and comments
> back.

Due to the merge of the meson build system, this needs to adjust meson.build
as well.


> --- a/contrib/pg_buffercache/expected/pg_buffercache.out
> +++ b/contrib/pg_buffercache/expected/pg_buffercache.out
> @@ -8,3 +8,12 @@ from pg_buffercache;
>   t
>  (1 row)
>
> +select buffers_used + buffers_unused > 0,
> +        buffers_dirty < buffers_used,
> +        buffers_pinned < buffers_used

Doesn't these have to be "<=" instead of "<"?


> +    for (int i = 0; i < NBuffers; i++)
> +    {
> +        BufferDesc *bufHdr;
> +        uint32        buf_state;
> +
> +        /*
> +         * No need to get locks on buffer headers as we don't rely on the
> +         * results in detail. Therefore, we don't get a consistent snapshot
> +         * across all buffers and it is not guaranteed that the information of
> +         * each buffer is self-consistent as opposed to pg_buffercache_pages.
> +         */

I think the "consistent snapshot" bit is misleading - even taking buffer
header locks wouldn't give you that.


> +    if (buffers_used != 0)
> +        usagecount_avg = usagecount_avg / buffers_used;

Perhaps the average should be NULL in the buffers_used == 0 case?


> + <para>
> +  <function>pg_buffercache_pages</function> function
> +  returns a set of records, plus a view <structname>pg_buffercache</structname> that wraps the function for
> +  convenient use is provided.
> + </para>
> +
> + <para>
> +  <function>pg_buffercache_summary</function> function returns a table with a single row
> +  that contains summarized and aggregated information about shared buffer caches.
>   </para>

I think these sentences are missing a "The " at the start?

"shared buffer caches" isn't right - I think I'd just drop the "caches".


> +  <para>
> +   There is a single row to show summarized information of all shared buffers.
> +   <function>pg_buffercache_summary</function> is not interested
> +   in the state of each shared buffer, only shows aggregated information.
> +  </para>
> +
> +  <para>
> +   <function>pg_buffercache_summary</function> doesn't take buffer manager
> +   locks. Unlike <function>pg_buffercache_pages</function> function
> +   <function>pg_buffercache_summary</function> doesn't take buffer headers locks
> +   either, thus the result is not consistent. This is intentional. The purpose
> +   of this function is to provide a general idea about the state of shared
> +   buffers as fast as possible. Additionally, <function>pg_buffercache_summary</function>
> +   allocates much less memory.
> +  </para>
> + </sect2>

I don't think this mentioning of buffer header locks is useful for users - nor
is it I think correct. Acquiring the buffer header locks wouldn't add *any*
additional consistency.

Greetings,

Andres Freund



Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi Andres,

Adjusted the patch so that it will work with meson now.

Also addressed your other reviews as well. 
I hope explanations in comments/docs are better now.

Best,
Melih
Attachment

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi all,

The patch needed a rebase due to recent changes on pg_buffercache.
You can find the updated version attached.

Best,
Melih

Attachment

Re: Summary function for pg_buffercache

From
Zhang Mingli
Date:

Regards,
Zhang Mingli
On Sep 28, 2022, 21:50 +0800, Melih Mutlu <m.melihmutlu@gmail.com>, wrote:
Hi all,

The patch needed a rebase due to recent changes on pg_buffercache.
You can find the updated version attached.

Best,
Melih


```
+
+ if (buffers_used != 0)
usagecount_avg = usagecount_avg / buffers_used;
+
+ memset(nulls, 0, sizeof(nulls));
+ values[0] = Int32GetDatum(buffers_used);
+ values[1] = Int32GetDatum(buffers_unused);
+ values[2] = Int32GetDatum(buffers_dirty);
+ values[3] = Int32GetDatum(buffers_pinned);
+
+ if (buffers_used != 0)
+ {
usagecount_avg = usagecount_avg / buffers_used;
+ values[4] = Float4GetDatum(usagecount_avg);
+ }
+ else
+ {
+ nulls[4] = true;
+ }
```

Why compute usagecount_avg twice? 

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:


Zhang Mingli <zmlpostgres@gmail.com>, 28 Eyl 2022 Çar, 17:31 tarihinde şunu yazdı:
Why compute usagecount_avg twice? 

I should have removed the first one, but I think I missed it.
Nice catch.

Attached an updated version.

Thanks,
Melih

Attachment

Re: Summary function for pg_buffercache

From
Zhang Mingli
Date:
Hi,

On Sep 28, 2022, 22:41 +0800, Melih Mutlu <m.melihmutlu@gmail.com>, wrote:


Zhang Mingli <zmlpostgres@gmail.com>, 28 Eyl 2022 Çar, 17:31 tarihinde şunu yazdı:
Why compute usagecount_avg twice? 

I should have removed the first one, but I think I missed it.
Nice catch.

Attached an updated version.

Thanks,
Melih

Hmm, I just apply v13 patch but failed.

Part of errors:
```
error: patch failed: contrib/pg_buffercache/pg_buffercache.control:1 error: contrib/pg_buffercache/pg_buffercache.control: patch does not apply
Checking patch contrib/pg_buffercache/pg_buffercache_pages.c...
error: while searching for:
 */
PG_FUNCTION_INFO_V1(pg_buffercache_pages);
PG_FUNCTION_INFO_V1(pg_buffercache_pages_v1_4);
```

Rebase on master and then apply our changes again?

Regards,
Zhang Mingli

Re: Summary function for pg_buffercache

From
Melih Mutlu
Date:
Hi,

Seems like the commit a448e49bcbe40fb72e1ed85af910dd216d45bad8 reverts the changes on pg_buffercache.

Why compute usagecount_avg twice? 
Then, I'm going back to v11 + the fix for this.

Thanks,
Melih
Attachment

Re: Summary function for pg_buffercache

From
Zhang Mingli
Date:
Hi,

On Sep 28, 2022, 23:20 +0800, Melih Mutlu <m.melihmutlu@gmail.com>, wrote:
Hi,

Seems like the commit a448e49bcbe40fb72e1ed85af910dd216d45bad8 reverts the changes on pg_buffercache.

Why compute usagecount_avg twice? 
Then, I'm going back to v11 + the fix for this.

Thanks,
Melih
Looks good to me.

Regards,
Zhang Mingli

Re: Summary function for pg_buffercache

From
Andres Freund
Date:
Hi,

On 2022-09-28 18:19:57 +0300, Melih Mutlu wrote:
> diff --git a/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql
b/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql
> new file mode 100644
> index 0000000000..77e250b430
> --- /dev/null
> +++ b/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql
> @@ -0,0 +1,13 @@
> +/* contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql */
> +
> +-- complain if script is sourced in psql, rather than via ALTER EXTENSION
> +\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.4'" to load this file. \quit
> +
> +CREATE FUNCTION pg_buffercache_summary()
> +RETURNS TABLE (buffers_used int4, buffers_unused int4, buffers_dirty int4,
> +                buffers_pinned int4, usagecount_avg real)
> +AS 'MODULE_PATHNAME', 'pg_buffercache_summary'
> +LANGUAGE C PARALLEL SAFE;

I think using RETURNS TABLE isn't quite right here, as it implies 'SETOF'. But
the function doesn't return a set of rows. I changed this to use OUT
parameters.


> +-- Don't want these to be available to public.
> +REVOKE ALL ON FUNCTION pg_buffercache_summary() FROM PUBLIC;

I think this needs to grant to pg_monitor too. See
pg_buffercache--1.2--1.3.sql

I added a test verifying the permissions are right, with the hope that it'll
make future contributors try to add a parallel test and notice the permissions
aren't right.


> +    /* Construct a tuple descriptor for the result rows. */
> +    tupledesc = CreateTemplateTupleDesc(NUM_BUFFERCACHE_SUMMARY_ELEM);

Given that we define the return type on the SQL level, it imo is nicer to use
get_call_result_type() here.


> +    TupleDescInitEntry(tupledesc, (AttrNumber) 5, "usagecount_avg",
> +                       FLOAT4OID, -1, 0);

I changed this to FLOAT8. Not that the precision will commonly be useful, but
it doesn't seem worth having to even think about whether there are cases where
it'd matter.

I also changed it so that the accumulation happens in an int64 variable named
usagecount_total, which gets converted to a double only when actually
computing the result.


>   <para>
>    The <filename>pg_buffercache</filename> module provides a means for
> -  examining what's happening in the shared buffer cache in real time.
> +  examining what's happening in the shared buffer in real time.
>   </para>

This seems to be an unnecessary / unrelated change. I suspect you made it in
response to
https://postgr.es/m/20220922161014.copbzwdl3ja4nt6z%40awork3.anarazel.de
but that was about a different sentence, where you said 'shared buffer caches'
(even though there is only a single shared buffer cache).


>   <indexterm>
> @@ -17,10 +17,19 @@
>   </indexterm>
>  
>   <para>
> -  The module provides a C function <function>pg_buffercache_pages</function>
> -  that returns a set of records, plus a view
> -  <structname>pg_buffercache</structname> that wraps the function for
> -  convenient use.
> +  The module provides C functions <function>pg_buffercache_pages</function>
> +  and <function>pg_buffercache_summary</function>.
> + </para>
> +
> + <para>
> +  The <function>pg_buffercache_pages</function> function
> +  returns a set of records, plus a view <structname>pg_buffercache</structname> that wraps the function for
> +  convenient use is provided.
> + </para>

I rephrased this, because it sounds like the function returns a set of records
and a view.


> + <para>
> +  The <function>pg_buffercache_summary</function> function returns a table with a single row
> +  that contains summarized and aggregated information about shared buffer.
>   </para>

"summarized and aggregated" is quite redundant.


> +  <table id="pgbuffercachesummary-columns">
> +   <title><structname>pg_buffercachesummary</structname> Columns</title>

Missing underscore.


> +     <row>
> +      <entry role="catalog_table_entry"><para role="column_definition">
> +       <structfield>buffers_unused</structfield> <type>int4</type>
> +      </para>
> +      <para>
> +        Number of shared buffers that not currently being used
> +      </para></entry>
> +     </row>

There's a missing 'are' in here, I think. I rephrased all of these to
"Number of (used|unused|dirty|pinned) shared buffers"


> +     <row>
> +      <entry role="catalog_table_entry"><para role="column_definition">
> +       <structfield>buffers_dirty</structfield> <type>int4</type>
> +      </para>
> +      <para>
> +       Number of dirty shared buffers
> +      </para></entry>
> +     </row>
> +
> +     <row>
> +      <entry role="catalog_table_entry"><para role="column_definition">
> +       <structfield>buffers_pinned</structfield> <type>int4</type>
> +      </para>
> +      <para>
> +       Number of shared buffers that has a pinned backend
> +      </para></entry>
> +     </row>

Backends pin buffers, not the other way round...


> +  <para>
> +   There is a single row to show summarized information of all shared buffers.
> +   <function>pg_buffercache_summary</function> is not interested
> +   in the state of each shared buffer, only shows aggregated information.
> +  </para>
> +
> +  <para>
> +   The <function>pg_buffercache_summary</function> doesn't provide a result
> +   that is consistent across all buffers. This is intentional. The purpose
> +   of this function is to provide a general idea about the state of shared
> +   buffers as fast as possible. Additionally, <function>pg_buffercache_summary</function>
> +   allocates much less memory.
> +  </para>

I still didn't like this comment. Please see the attached.


I intentionally put my changes into a fixup commit, in case you want to look
at the differences.

Greetings,

Andres Freund

Attachment

Re: Summary function for pg_buffercache

From
Andres Freund
Date:
Hi,

On 2022-10-12 12:27:54 -0700, Andres Freund wrote:
> I intentionally put my changes into a fixup commit, in case you want to look
> at the differences.

I pushed the (combined) patch now. Thanks for your contribution!

Greetings,

Andres Freund