public inbox for [email protected]
help / color / mirror / Atom feedFrom: Nazir Bilal Yavuz <[email protected]>
To: Andres Freund <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: Add pg_buffercache_evict_all() and pg_buffercache_mark_dirty[_all]() functions
Date: Fri, 14 Mar 2025 16:25:00 +0300
Message-ID: <CAN55FZ2mNTVK81jqsy2NXg9k_ex_LrdU6JmL1wvsW9-GPy9XuA@mail.gmail.com> (raw)
In-Reply-To: <CAN55FZ1Pq_80vcuHZK6dSuTfG-5jBoK0Bd2k1YUSpKT+7hOj6A@mail.gmail.com>
References: <CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw@mail.gmail.com>
<bbxepmv3w2iapbxehkypzsut545valywzf62qlizsrzkbrdnfy@dzw5ju3itwgs>
<CAN55FZ1Pq_80vcuHZK6dSuTfG-5jBoK0Bd2k1YUSpKT+7hOj6A@mail.gmail.com>
Hi,
Here is the v2. Changes prior to v1 are:
- pg_buffercache_evict_relation() function is introduced. Which takes
a relation as an argument and evicts all shared buffers in the
relation.
- It is mentioned in the docs that the #buffers_flushed in the
pg_buffercache_evict_relation() and pg_buffercache_evict_all()
functions do not necessarily mean these buffers are flushed by these
functions.
Remaining questions from the v1:
On Wed, 12 Mar 2025 at 19:15, Nazir Bilal Yavuz <[email protected]> wrote:
> On Mon, 17 Feb 2025 at 19:59, Andres Freund <[email protected]> wrote:
> > On 2024-12-25 15:57:34 +0300, Nazir Bilal Yavuz wrote:
> > > + */
> > > +Datum
> > > +pg_buffercache_evict_all(PG_FUNCTION_ARGS)
> > > +{
> > > + Datum result;
> > > + TupleDesc tupledesc;
> > > + HeapTuple tuple;
> > > + Datum values[NUM_BUFFERCACHE_EVICT_ALL_ELEM];
> > > + bool nulls[NUM_BUFFERCACHE_EVICT_ALL_ELEM] = {0};
> > > +
> > > + int32 buffers_evicted = 0;
> > > + int32 buffers_flushed = 0;
> > > + bool flushed;
> > > +
> > > + if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
> > > + elog(ERROR, "return type must be a row type");
> > > +
> > > + if (!superuser())
> > > + ereport(ERROR,
> > > + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
> > > + errmsg("must be superuser to use pg_buffercache_evict_all function")));
> > > +
> > > + for (int buf = 1; buf < NBuffers; buf++)
> > > + {
> > > + if (EvictUnpinnedBuffer(buf, &flushed))
> > > + buffers_evicted++;
> > > + if (flushed)
> > > + buffers_flushed++;
> >
> > I'd probably add a pre-check for the buffer not being in use. We don't need an
> > external function call, with an unconditional LockBufHdr() inside, for that
> > case.
>
> I did not understand why we would need this. Does not
> EvictUnpinnedBuffer() already check that the buffer is not in use?
> > > +/*
> > > + * MarkUnpinnedBufferDirty
> > > + *
> > > + * This function is intended for testing/development use only!
> > > + *
> > > + * To succeed, the buffer must not be pinned on entry, so if the caller had a
> > > + * particular block in mind, it might already have been replaced by some other
> > > + * block by the time this function runs. It's also unpinned on return, so the
> > > + * buffer might be occupied and flushed by the time control is returned. This
> > > + * inherent raciness without other interlocking makes the function unsuitable
> > > + * for non-testing usage.
> > > + *
> > > + * Returns true if the buffer was not dirty and it has now been marked as
> > > + * dirty. Returns false if it wasn't valid, if it couldn't be marked as dirty
> > > + * due to a pin, or if the buffer was already dirty.
> > > + */
> >
> > Hm. One potentially problematic thing with this is that it can pin and dirty a
> > buffer even while its relation is locked exclusively. Which i think some code
> > doesn't expect. Not sure if there's a way around that :(
>
> I see, I did not think about that. Since this function can be used
> only by superusers, that problem might not be a big issue. What do you
> think?
--
Regards,
Nazir Bilal Yavuz
Microsoft
Attachments:
[text/x-patch] v2-0001-Add-pg_buffercache_evict_-relation-all-functions-.patch (13.1K, ../CAN55FZ2mNTVK81jqsy2NXg9k_ex_LrdU6JmL1wvsW9-GPy9XuA@mail.gmail.com/2-v2-0001-Add-pg_buffercache_evict_-relation-all-functions-.patch)
download | inline diff:
From 2090816c1a2d97b257f136ea7ab36c1c86dc1e2a Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <[email protected]>
Date: Fri, 20 Dec 2024 14:06:47 +0300
Subject: [PATCH v2 1/2] Add pg_buffercache_evict_[relation | all]() functions
for testing
pg_buffercache_evict_relation(): Evicts all shared buffers in a
relation at once.
pg_buffercache_evict_all(): Evicts all shared buffers at once.
Both functions provide mechanism to evict multiple shared buffers at
once. They are designed to address the inefficiency of repeatedly calling
pg_buffercache_evict() for each individual buffer, which can be
time-consuming when dealing with large shared buffer pools.
(e.g., ~790ms vs. ~270ms for 16GB of shared buffers).
These functions are intended for developer testing and debugging
purposes and are available to superusers only.
---
src/include/storage/bufmgr.h | 2 +-
src/backend/storage/buffer/bufmgr.c | 13 +-
doc/src/sgml/pgbuffercache.sgml | 44 +++++-
contrib/pg_buffercache/Makefile | 3 +-
contrib/pg_buffercache/meson.build | 1 +
.../pg_buffercache--1.5--1.6.sql | 15 ++
contrib/pg_buffercache/pg_buffercache.control | 2 +-
contrib/pg_buffercache/pg_buffercache_pages.c | 131 +++++++++++++++++-
8 files changed, 201 insertions(+), 10 deletions(-)
create mode 100644 contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 79a89f87fcc..16c92383969 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -297,7 +297,7 @@ extern uint32 GetAdditionalLocalPinLimit(void);
extern void LimitAdditionalPins(uint32 *additional_pins);
extern void LimitAdditionalLocalPins(uint32 *additional_pins);
-extern bool EvictUnpinnedBuffer(Buffer buf);
+extern bool EvictUnpinnedBuffer(Buffer buf, uint32 buf_state, bool *flushed);
/* in buf_init.c */
extern void BufferManagerShmemInit(void);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 8243f4b2445..53bbdd92e53 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6056,12 +6056,13 @@ ResOwnerPrintBufferPin(Datum res)
* or if the buffer becomes dirty again while we're trying to write it out.
*/
bool
-EvictUnpinnedBuffer(Buffer buf)
+EvictUnpinnedBuffer(Buffer buf, uint32 buf_state, bool *flushed)
{
BufferDesc *desc;
- uint32 buf_state;
bool result;
+ *flushed = false;
+
/* Make sure we can pin the buffer. */
ResourceOwnerEnlarge(CurrentResourceOwner);
ReservePrivateRefCountEntry();
@@ -6069,8 +6070,11 @@ EvictUnpinnedBuffer(Buffer buf)
Assert(!BufferIsLocal(buf));
desc = GetBufferDescriptor(buf - 1);
- /* Lock the header and check if it's valid. */
- buf_state = LockBufHdr(desc);
+ /* Lock the header if it is not already locked. */
+ if (!buf_state)
+ buf_state = LockBufHdr(desc);
+
+ /* Check if it's valid. */
if ((buf_state & BM_VALID) == 0)
{
UnlockBufHdr(desc, buf_state);
@@ -6092,6 +6096,7 @@ EvictUnpinnedBuffer(Buffer buf)
LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_SHARED);
FlushBuffer(desc, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL);
LWLockRelease(BufferDescriptorGetContentLock(desc));
+ *flushed = true;
}
/* This will return false if it becomes dirty or someone else pins it. */
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index 802a5112d77..83950ca5cce 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -31,8 +31,10 @@
This module provides the <function>pg_buffercache_pages()</function>
function (wrapped in the <structname>pg_buffercache</structname> view),
the <function>pg_buffercache_summary()</function> function, the
- <function>pg_buffercache_usage_counts()</function> function and
- the <function>pg_buffercache_evict()</function> function.
+ <function>pg_buffercache_usage_counts()</function> function, the
+ <function>pg_buffercache_evict()</function>, the
+ <function>pg_buffercache_evict_relation()</function>, and the
+ <function>pg_buffercache_evict_all()</function> function.
</para>
<para>
@@ -65,6 +67,22 @@
function is restricted to superusers only.
</para>
+ <para>
+ The <function>pg_buffercache_evict_relation()</function> function allows all
+ buffers in the relation to be evicted from the buffer pool given a relation
+ identifier. It returns how many buffers are evicted and flushed. Flushed buffers
+ do not have to be flushed by this function call, they might be flushed by
+ something else. Use of this function is restricted to superusers only.
+ </para>
+
+ <para>
+ The <function>pg_buffercache_evict_all()</function> function allows all
+ buffers to be evicted in the buffer pool. It returns how many buffers are
+ evicted and flushed. Flushed buffers do not have to be flushed by this
+ function call, they might be flushed by something else. Use of this function
+ is restricted to superusers only.
+ </para>
+
<sect2 id="pgbuffercache-pg-buffercache">
<title>The <structname>pg_buffercache</structname> View</title>
@@ -378,6 +396,28 @@
</para>
</sect2>
+ <sect2 id="pgbuffercache-pg-buffercache-evict-relation">
+ <title>The <structname>pg_buffercache_evict_relation</structname> Function</title>
+ <para>
+ The <function>pg_buffercache_evict_relation()</function> function is very similar
+ to <function>pg_buffercache_evict()</function> function. The difference is that
+ <function>pg_buffercache_evict_relation()</function> takes a relation
+ identifier instead of buffer identifier. Then, it tries to evict all
+ buffers in that relation. The function is intended for developer testing only.
+ </para>
+ </sect2>
+
+ <sect2 id="pgbuffercache-pg-buffercache-evict-all">
+ <title>The <structname>pg_buffercache_evict_all</structname> Function</title>
+ <para>
+ The <function>pg_buffercache_evict_all()</function> function is very similar
+ to <function>pg_buffercache_evict()</function> function. The difference is,
+ the <function>pg_buffercache_evict_all()</function> does not take argument;
+ instead it tries to evict all buffers in the buffer pool. The function is
+ intended for developer testing only.
+ </para>
+ </sect2>
+
<sect2 id="pgbuffercache-sample-output">
<title>Sample Output</title>
diff --git a/contrib/pg_buffercache/Makefile b/contrib/pg_buffercache/Makefile
index eae65ead9e5..2a33602537e 100644
--- a/contrib/pg_buffercache/Makefile
+++ b/contrib/pg_buffercache/Makefile
@@ -8,7 +8,8 @@ OBJS = \
EXTENSION = pg_buffercache
DATA = pg_buffercache--1.2.sql pg_buffercache--1.2--1.3.sql \
pg_buffercache--1.1--1.2.sql pg_buffercache--1.0--1.1.sql \
- pg_buffercache--1.3--1.4.sql pg_buffercache--1.4--1.5.sql
+ pg_buffercache--1.3--1.4.sql pg_buffercache--1.4--1.5.sql \
+ pg_buffercache--1.5--1.6.sql
PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"
REGRESS = pg_buffercache
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
index 12d1fe48717..9b2e9393410 100644
--- a/contrib/pg_buffercache/meson.build
+++ b/contrib/pg_buffercache/meson.build
@@ -23,6 +23,7 @@ install_data(
'pg_buffercache--1.2.sql',
'pg_buffercache--1.3--1.4.sql',
'pg_buffercache--1.4--1.5.sql',
+ 'pg_buffercache--1.5--1.6.sql',
'pg_buffercache.control',
kwargs: contrib_data_args,
)
diff --git a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
new file mode 100644
index 00000000000..3a02d56a89f
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
@@ -0,0 +1,15 @@
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.6'" to load this file. \quit
+
+CREATE FUNCTION pg_buffercache_evict_relation(
+ IN regclass,
+ IN fork text default 'main',
+ OUT buffers_evicted int4,
+ OUT buffers_flushed int4)
+AS 'MODULE_PATHNAME', 'pg_buffercache_evict_relation'
+LANGUAGE C PARALLEL SAFE VOLATILE;
+
+CREATE FUNCTION pg_buffercache_evict_all(
+ OUT buffers_evicted int4,
+ OUT buffers_flushed int4)
+AS 'MODULE_PATHNAME', 'pg_buffercache_evict_all'
+LANGUAGE C PARALLEL SAFE VOLATILE;
diff --git a/contrib/pg_buffercache/pg_buffercache.control b/contrib/pg_buffercache/pg_buffercache.control
index 5ee875f77dd..b030ba3a6fa 100644
--- a/contrib/pg_buffercache/pg_buffercache.control
+++ b/contrib/pg_buffercache/pg_buffercache.control
@@ -1,5 +1,5 @@
# pg_buffercache extension
comment = 'examine the shared buffer cache'
-default_version = '1.5'
+default_version = '1.6'
module_pathname = '$libdir/pg_buffercache'
relocatable = true
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 3ae0a018e10..9932a19735a 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -9,16 +9,21 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/relation.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
#define NUM_BUFFERCACHE_PAGES_MIN_ELEM 8
#define NUM_BUFFERCACHE_PAGES_ELEM 9
#define NUM_BUFFERCACHE_SUMMARY_ELEM 5
#define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4
+#define NUM_BUFFERCACHE_EVICT_RELATION_ELEM 2
+#define NUM_BUFFERCACHE_EVICT_ALL_ELEM 2
PG_MODULE_MAGIC;
@@ -64,6 +69,8 @@ PG_FUNCTION_INFO_V1(pg_buffercache_pages);
PG_FUNCTION_INFO_V1(pg_buffercache_summary);
PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
PG_FUNCTION_INFO_V1(pg_buffercache_evict);
+PG_FUNCTION_INFO_V1(pg_buffercache_evict_relation);
+PG_FUNCTION_INFO_V1(pg_buffercache_evict_all);
Datum
pg_buffercache_pages(PG_FUNCTION_ARGS)
@@ -356,6 +363,7 @@ Datum
pg_buffercache_evict(PG_FUNCTION_ARGS)
{
Buffer buf = PG_GETARG_INT32(0);
+ bool flushed;
if (!superuser())
ereport(ERROR,
@@ -365,5 +373,126 @@ pg_buffercache_evict(PG_FUNCTION_ARGS)
if (buf < 1 || buf > NBuffers)
elog(ERROR, "bad buffer ID: %d", buf);
- PG_RETURN_BOOL(EvictUnpinnedBuffer(buf));
+ PG_RETURN_BOOL(EvictUnpinnedBuffer(buf, 0, &flushed));
+}
+
+/*
+ * Try to evict specified relation.
+ */
+Datum
+pg_buffercache_evict_relation(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ TupleDesc tupledesc;
+ HeapTuple tuple;
+ Datum values[NUM_BUFFERCACHE_EVICT_RELATION_ELEM];
+ bool nulls[NUM_BUFFERCACHE_EVICT_RELATION_ELEM] = {0};
+
+ Oid relOid;
+ Relation rel;
+ int32 buffers_evicted = 0;
+ int32 buffers_flushed = 0;
+ bool flushed;
+
+ if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use pg_buffercache_evict function")));
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relation cannot be null")));
+
+
+ relOid = PG_GETARG_OID(0);
+
+ /* Open relation. */
+ rel = relation_open(relOid, AccessShareLock);
+
+ if (RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relation uses local buffers,"
+ "pg_buffercache_evict_relation function is intended to"
+ "used for shared buffers only")));
+
+ for (int buf = 1; buf < NBuffers; buf++)
+ {
+ uint32 buf_state;
+ BufferDesc *bufHdr = GetBufferDescriptor(buf - 1);
+
+ /*
+ * No need to call UnlockBufHdr() if BufTagMatchesRelFileLocator()
+ * returns true, EvictUnpinnedBuffer() will take care of it.
+ */
+ buf_state = LockBufHdr(bufHdr);
+ if (BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator))
+ {
+ if (EvictUnpinnedBuffer(buf, buf_state, &flushed))
+ buffers_evicted++;
+ if (flushed)
+ buffers_flushed++;
+ }
+ else
+ UnlockBufHdr(bufHdr, buf_state);
+ }
+
+ /* Close relation, release lock. */
+ relation_close(rel, AccessShareLock);
+
+ values[0] = Int32GetDatum(buffers_evicted);
+ values[1] = Int32GetDatum(buffers_flushed);
+
+ /* Build and return the tuple. */
+ tuple = heap_form_tuple(tupledesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+
+/*
+ * Try to evict all shared buffers.
+ */
+Datum
+pg_buffercache_evict_all(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ TupleDesc tupledesc;
+ HeapTuple tuple;
+ Datum values[NUM_BUFFERCACHE_EVICT_ALL_ELEM];
+ bool nulls[NUM_BUFFERCACHE_EVICT_ALL_ELEM] = {0};
+
+ int32 buffers_evicted = 0;
+ int32 buffers_flushed = 0;
+ bool flushed;
+
+ if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use pg_buffercache_evict_all function")));
+
+ for (int buf = 1; buf < NBuffers; buf++)
+ {
+ if (EvictUnpinnedBuffer(buf, 0, &flushed))
+ buffers_evicted++;
+ if (flushed)
+ buffers_flushed++;
+ }
+
+ values[0] = Int32GetDatum(buffers_evicted);
+ values[1] = Int32GetDatum(buffers_flushed);
+
+ /* Build and return the tuple. */
+ tuple = heap_form_tuple(tupledesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
}
--
2.47.2
[text/x-patch] v2-0002-Add-pg_buffercache_mark_dirty-_all-functions-for-.patch (8.7K, ../CAN55FZ2mNTVK81jqsy2NXg9k_ex_LrdU6JmL1wvsW9-GPy9XuA@mail.gmail.com/3-v2-0002-Add-pg_buffercache_mark_dirty-_all-functions-for-.patch)
download | inline diff:
From 56ef07473155d3e5f568dab710501bd92f8718ff Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <[email protected]>
Date: Wed, 25 Dec 2024 15:46:10 +0300
Subject: [PATCH v2 2/2] Add pg_buffercache_mark_dirty[_all]() functions for
testing
This commit introduces two new functions for marking shared buffers as
dirty:
pg_buffercache_mark_dirty(): Marks a specific shared buffer as dirty.
pg_buffercache_mark_dirty_all(): Marks all shared buffers as dirty in a
single operation.
The pg_buffercache_mark_dirty_all() function provides an efficient
way to dirty the entire buffer pool (e.g., ~550ms vs. ~70ms for 16GB of
shared buffers), complementing pg_buffercache_mark_dirty() for more
granular control.
These functions are intended for developer testing and debugging
scenarios, enabling users to simulate various buffer pool states and
test write-back behavior. Both functions are superuser-only.
---
src/include/storage/bufmgr.h | 1 +
src/backend/storage/buffer/bufmgr.c | 62 +++++++++++++++++++
doc/src/sgml/pgbuffercache.sgml | 40 +++++++++++-
.../pg_buffercache--1.5--1.6.sql | 10 +++
contrib/pg_buffercache/pg_buffercache_pages.c | 43 +++++++++++++
5 files changed, 154 insertions(+), 2 deletions(-)
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 16c92383969..fbd16440e55 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -298,6 +298,7 @@ extern void LimitAdditionalPins(uint32 *additional_pins);
extern void LimitAdditionalLocalPins(uint32 *additional_pins);
extern bool EvictUnpinnedBuffer(Buffer buf, uint32 buf_state, bool *flushed);
+extern bool MarkUnpinnedBufferDirty(Buffer buf);
/* in buf_init.c */
extern void BufferManagerShmemInit(void);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 53bbdd92e53..e3c84a5342c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6106,3 +6106,65 @@ EvictUnpinnedBuffer(Buffer buf, uint32 buf_state, bool *flushed)
return result;
}
+
+/*
+ * MarkUnpinnedBufferDirty
+ *
+ * This function is intended for testing/development use only!
+ *
+ * To succeed, the buffer must not be pinned on entry, so if the caller had a
+ * particular block in mind, it might already have been replaced by some other
+ * block by the time this function runs. It's also unpinned on return, so the
+ * buffer might be occupied and flushed by the time control is returned. This
+ * inherent raciness without other interlocking makes the function unsuitable
+ * for non-testing usage.
+ *
+ * Returns true if the buffer was not dirty and it has now been marked as
+ * dirty. Returns false if it wasn't valid, if it couldn't be marked as dirty
+ * due to a pin, or if the buffer was already dirty.
+ */
+bool
+MarkUnpinnedBufferDirty(Buffer buf)
+{
+ BufferDesc *desc;
+ uint32 buf_state;
+ bool result = false;
+
+ Assert(!BufferIsLocal(buf));
+
+ /* Make sure we can pin the buffer. */
+ ResourceOwnerEnlarge(CurrentResourceOwner);
+ ReservePrivateRefCountEntry();
+
+ desc = GetBufferDescriptor(buf - 1);
+
+ /* Lock the header and check if it's valid. */
+ buf_state = LockBufHdr(desc);
+ if ((buf_state & BM_VALID) == 0)
+ {
+ UnlockBufHdr(desc, buf_state);
+ return false;
+ }
+
+ /* Check that it's not pinned already. */
+ if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
+ {
+ UnlockBufHdr(desc, buf_state);
+ return false;
+ }
+
+ PinBuffer_Locked(desc); /* releases spinlock */
+
+ /* If it was not already dirty, mark it as dirty. */
+ if (!(buf_state & BM_DIRTY))
+ {
+ LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_EXCLUSIVE);
+ MarkBufferDirty(buf);
+ LWLockRelease(BufferDescriptorGetContentLock(desc));
+ result = true;
+ }
+
+ UnpinBuffer(desc);
+
+ return result;
+}
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index 83950ca5cce..b0c19d56283 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -33,8 +33,10 @@
the <function>pg_buffercache_summary()</function> function, the
<function>pg_buffercache_usage_counts()</function> function, the
<function>pg_buffercache_evict()</function>, the
- <function>pg_buffercache_evict_relation()</function>, and the
- <function>pg_buffercache_evict_all()</function> function.
+ <function>pg_buffercache_evict_relation()</function>, the
+ <function>pg_buffercache_evict_all()</function>, the
+ <function>pg_buffercache_mark_dirty()</function> and the
+ <function>pg_buffercache_mark_dirty_all()</function> function.
</para>
<para>
@@ -83,6 +85,18 @@
is restricted to superusers only.
</para>
+ <para>
+ The <function>pg_buffercache_mark_dirty()</function> function allows a block
+ to be marked as dirty from the buffer pool given a buffer identifier. Use of
+ this function is restricted to superusers only.
+ </para>
+
+ <para>
+ The <function>pg_buffercache_mark_dirty_all()</function> function tries to
+ mark all buffers dirty in the buffer pool. It returns how many buffers are
+ dirtied. Use of this function is restricted to superusers only.
+ </para>
+
<sect2 id="pgbuffercache-pg-buffercache">
<title>The <structname>pg_buffercache</structname> View</title>
@@ -418,6 +432,28 @@
</para>
</sect2>
+ <sect2 id="pgbuffercache-pg-buffercache-mark-dirty">
+ <title>The <structname>pg_buffercache_mark_dirty</structname> Function</title>
+ <para>
+ The <function>pg_buffercache_mark_dirty()</function> function takes a buffer
+ identifier, as shown in the <structfield>bufferid</structfield> column of
+ the <structname>pg_buffercache</structname> view. It returns true on success,
+ and false if the buffer wasn't valid or if it couldn't be marked as dirty
+ because it was pinned. The function is intended for developer testing only.
+ </para>
+ </sect2>
+
+ <sect2 id="pgbuffercache-pg-buffercache-mark-dirty-all">
+ <title>The <structname>pg_buffercache_mark_dirty_all</structname> Function</title>
+ <para>
+ The <function>pg_buffercache_mark_dirty_all()</function> function is very similar
+ to <function>pg_buffercache_mark_dirty()</function> function. The difference is,
+ the <function>pg_buffercache_mark_dirty_all()</function> does not take argument;
+ instead it tries to mark all buffers dirty in the buffer pool. The function is
+ intended for developer testing only.
+ </para>
+ </sect2>
+
<sect2 id="pgbuffercache-sample-output">
<title>Sample Output</title>
diff --git a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
index 3a02d56a89f..9aff57a8729 100644
--- a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
+++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
@@ -13,3 +13,13 @@ CREATE FUNCTION pg_buffercache_evict_all(
OUT buffers_flushed int4)
AS 'MODULE_PATHNAME', 'pg_buffercache_evict_all'
LANGUAGE C PARALLEL SAFE VOLATILE;
+
+CREATE FUNCTION pg_buffercache_mark_dirty(IN int)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty'
+LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
+
+CREATE FUNCTION pg_buffercache_mark_dirty_all()
+RETURNS INT4
+AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty_all'
+LANGUAGE C PARALLEL SAFE VOLATILE;
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 9932a19735a..d20dced2077 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -71,6 +71,8 @@ PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
PG_FUNCTION_INFO_V1(pg_buffercache_evict);
PG_FUNCTION_INFO_V1(pg_buffercache_evict_relation);
PG_FUNCTION_INFO_V1(pg_buffercache_evict_all);
+PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty);
+PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_all);
Datum
pg_buffercache_pages(PG_FUNCTION_ARGS)
@@ -496,3 +498,44 @@ pg_buffercache_evict_all(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * Try to mark dirty a shared buffer.
+ */
+Datum
+pg_buffercache_mark_dirty(PG_FUNCTION_ARGS)
+{
+ Buffer buf = PG_GETARG_INT32(0);
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use pg_buffercache_mark_dirty function")));
+
+ if (buf < 1 || buf > NBuffers)
+ elog(ERROR, "bad buffer ID: %d", buf);
+
+ PG_RETURN_BOOL(MarkUnpinnedBufferDirty(buf));
+}
+
+/*
+ * Try to mark dirty all shared buffers.
+ */
+Datum
+pg_buffercache_mark_dirty_all(PG_FUNCTION_ARGS)
+{
+ int32 buffers_dirtied = 0;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to use pg_buffercache_mark_dirty_all function")));
+
+ for (int buf = 1; buf < NBuffers; buf++)
+ {
+ if (MarkUnpinnedBufferDirty(buf))
+ buffers_dirtied++;
+ }
+
+ PG_RETURN_INT32(buffers_dirtied);
+}
--
2.47.2
view thread (29+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected]
Subject: Re: Add pg_buffercache_evict_all() and pg_buffercache_mark_dirty[_all]() functions
In-Reply-To: <CAN55FZ2mNTVK81jqsy2NXg9k_ex_LrdU6JmL1wvsW9-GPy9XuA@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox