Hi,

Thanks for the review!

On Wed, 2 Apr 2025 at 20:06, Andres Freund <and...@anarazel.de> wrote:
>
> Hi,
>
> On 2025-03-31 19:49:25 +0300, Nazir Bilal Yavuz wrote:
> > > After this discussion, I think it would be helpful if one of the more
> > > experienced
> > > hackers could take a look at the overall picture (perhaps we should set
> > > the
> > > status "Ready for Committer"? To be honest, I'm not sure what step that
> > > status
> > > should be set at).
> >
> > I agree. I will mark this as 'ready for committer' after writing the tests.
>
> Are you still working on tests?

Sorry, I forgot. They are attached as 0004.

> > > Also, there's a little thing about declaring functions as PARALLEL SAFE.
> > > To be honest,
> > > I don't really have any solid arguments against it. I just have some
> > > doubts. For
> > > example, how will it work if the plan is split up and we try to work on
> > > an object in
> > > one part, while the other part of the plan evicts the pages of that
> > > object or marks
> > > them as dirty... I can't really say for sure about that. And in that
> > > context, I'd
> > > suggest referring to that awesome statement in the documentation: "If in
> > > doubt,
> > > functions should be labeled as UNSAFE, which is the default."
> >
> > You may be right. I thought they are parallel safe as we acquire the
> > buffer header lock for each buffer but now, I have the same doubts as
> > you. I want to hear other people's opinions on that before labeling
> > them as UNSAFE.
>
> I don't see a problem with them being parallel safe.
>
>
> > From a59be4b50d7691ba952d0abd32198e972d4a6ea0 Mon Sep 17 00:00:00 2001
> > From: Nazir Bilal Yavuz <byavu...@gmail.com>
> > Date: Mon, 24 Mar 2025 13:52:04 +0300
> > Subject: [PATCH v5 1/3] 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.
>
>
> > +     if (!superuser())
> > +             ereport(ERROR,
> > +                             (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
> > +                              errmsg("must be superuser to use 
> > pg_buffercache_evict_relation function")));
>
> I think it'd be nicer if we didn't ask translators to translate 3 different
> versions of this message, for different functions. Why not pass in the functio
> name as a parameter?

Ah, I didn't think of this aspect, done.

> > +     if (RelationUsesLocalBuffers(rel))
> > +             ereport(ERROR,
> > +                             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> > +                              errmsg("relation uses local buffers,"
> > +                                             
> > "pg_buffercache_evict_relation function is intended to"
> > +                                             "be used for shared buffers 
> > only")));
>
> We try not to break messages across multiple lines, as that makes searching
> for them harder.

Done.

> > +     EvictRelUnpinnedBuffers(rel, &buffers_evicted, &buffers_flushed);
> > +
> > +     /* Close relation, release lock. */
>
> This comment imo isn't useful, it just restates the code verbatim.

Removed.

>
> > +     relation_close(rel, AccessExclusiveLock);
>
> Hm. Why are we dropping the lock here early? It's probably ok, but it's not
> clear to me why we should do so.

We are dropping the lock after we processed the relation. I didn't
understand what could be the problem here. Why do you think it is
early?

> > +     /* Build and return the tuple. */
>
> Similar to above, the comment is just a restatement of a short piece of code.

Done. It exists in other places in the pg_buffercache_pages.c file, I
only removed the ones that I added.

> > + <para>
> > +  The <function>pg_buffercache_evict_relation()</function> function allows 
> > all
> > +  shared buffers in the relation to be evicted from the buffer pool given a
> > +  relation identifier.  Use of this function is restricted to superusers 
> > only.
> > + </para>
>
> I'd say "all unpinned shared buffers".

Done.

> I wonder if these functions ought to also return the number of buffers that
> could not be evicted?

I didn't add this as I thought this information could be gathered if wanted.

> Should this, or the longer comment for the function, mention that buffers for
> all relation forks are evicted?

I added this to a longer comment for the function.

> > + <para>
> > +  The <function>pg_buffercache_evict_all()</function> function allows all
> > +  shared buffers to be evicted in the buffer pool.  Use of this function is
> > +  restricted to superusers only.
> > + </para>
>
> Basically the same comments as above, except for the fork portion.

Done.

> > diff --git a/src/backend/storage/buffer/bufmgr.c 
> > b/src/backend/storage/buffer/bufmgr.c
> > index f9681d09e1e..5d82e3fa297 100644
> > --- a/src/backend/storage/buffer/bufmgr.c
> > +++ b/src/backend/storage/buffer/bufmgr.c
> > @@ -6512,26 +6512,47 @@ ResOwnerPrintBufferPin(Datum res)
> >   * even by the same block.  This inherent raciness without other 
> > interlocking
> >   * makes the function unsuitable for non-testing usage.
> >   *
> > + * buf_state is used to check if the buffer header lock has been acquired
> > + * before calling this function.  If buf_state has a non-zero value, it 
> > means
> > + * that the buffer header has already been locked.  This information is 
> > useful
> > + * for evicting a specific relation's buffers, as the buffers' headers 
> > need to
> > + * be locked before this function can be called with such an intention.
>
> I don't like this aspect of the API changes one bit.  IMO something callable
> from outside storage/buffer should have no business knowing about buf_state.
>
> And detecting whether already hold a lock by checking whether the buf_state is
> 0 feels really wrong to me.

I changed this. I basically copied EvictUnpinnedBuffer() to the inside
of EvictRelUnpinnedBuffers(), we don't need any hacky methods now.

> > +/*
> > + * Try to evict all the shared buffers containing provided relation's 
> > pages.
> > + *
> > + * This function is intended for testing/development use only!
> > + *
> > + * We need this helper function because of the following reason.
> > + * ReservePrivateRefCountEntry() needs to be called before acquiring the
> > + * buffer header lock but ReservePrivateRefCountEntry() is static and it 
> > would
> > + * be better to have it as static. Hence, it can't be called from outside 
> > of
> > + * this file. This helper function is created to bypass that problem.
>
> I think there are other reasons - we should minimize the amount of code
> outside of storage/buffer that needs to know about BuferDescs etc, it's a
> layering violation to access that outside.

I added that as a comment.

> > + * Before calling this function, it is important to acquire
> > + * AccessExclusiveLock on the specified relation to avoid replacing the
> > + * current block of this relation with another one during execution.
>
> What do you mean with "current block of this relation"?

It is used timewise, like a snapshot of the blocks when the function
is called. I updated this with:

'The caller must hold at least AccessShareLock on the relation to
prevent the relation from being dropped.' [1]

> > + * buffers_evicted and buffers_flushed are set the total number of buffers
> > + * evicted and flushed respectively.
> > + */
> > +void
> > +EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted, int32 
> > *buffers_flushed)
> > +{
> > +     bool            flushed;
> > +
> > +     Assert(buffers_evicted && buffers_flushed);
> > +     *buffers_evicted = *buffers_flushed = 0;
>
> I'd personally not bother with the Assert, the next line would just crash
> anyway...

AIO tests already use EvictUnpinnedBuffer() without flushed
information. I made '*buffers_evicted, *buffers_flushed in the
EvictRelUnpinnedBuffers() and *flushed in the EvictUnpinnedBuffer()'
optional as Aidar suggested at upthread.

> > 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 2e255f3fc10..d54bb1fd6f8 100644
> > --- a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
> > +++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
> > @@ -1,5 +1,13 @@
> >  \echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.6'" to load this 
> > file. \quit
> >
> > +DROP FUNCTION pg_buffercache_evict(integer);
> > +CREATE FUNCTION pg_buffercache_evict(
> > +    IN int,
> > +    OUT evicted boolean,
> > +    OUT flushed boolean)
> > +AS 'MODULE_PATHNAME', 'pg_buffercache_evict'
> > +LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
>
> I assume the function has to be dropped because the return type changes?

Correct.

v6 is attached. Additional changes prior to v5:

* Tests are added.
* Andres said off-list that AccessExclusiveLock is too much, all the
lock needs to do is to prevent the relation from being dropped. So,
pg_buffercache_evict_relation() opens a relation with AccessShareLock
now. Function comment in EvictRelUnpinnedBuffers() is updated
regarding that. [1]
* EvictRelUnpinnedBuffers() does not call EvictUnpinnedBuffer() now. I
basically copied EvictUnpinnedBuffer() to the inside of
EvictRelUnpinnedBuffers() with the needed updates.

--
Regards,
Nazir Bilal Yavuz
Microsoft
From 88624ea0f768ebf1a015cc0c06d2c72c822577a8 Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavu...@gmail.com>
Date: Fri, 4 Apr 2025 13:22:00 +0300
Subject: [PATCH v6 1/4] Return buffer flushed information in
 pg_buffercache_evict function

pg_buffercache_evict() function shows buffer flushed information now.
This feature will be used in the following commits.

Author: Nazir Bilal Yavuz <byavu...@gmail.com>
Suggested-by: Joseph Koshakow <kosh...@gmail.com>
Reviewed-by: Aidar Imamov <a.ima...@postgrespro.ru>
Reviewed-by: Andres Freund <and...@anarazel.de>
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com
---
 src/include/storage/bufmgr.h                  |  2 +-
 src/backend/storage/buffer/bufmgr.c           | 11 +++++++++-
 src/test/modules/test_aio/test_aio.c          |  4 ++--
 doc/src/sgml/pgbuffercache.sgml               | 15 ++++++++-----
 contrib/pg_buffercache/Makefile               |  3 ++-
 contrib/pg_buffercache/meson.build            |  1 +
 .../pg_buffercache--1.5--1.6.sql              |  9 ++++++++
 contrib/pg_buffercache/pg_buffercache.control |  2 +-
 contrib/pg_buffercache/pg_buffercache_pages.c | 22 +++++++++++++++++--
 9 files changed, 55 insertions(+), 14 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 f2192ceb271..fab65824b18 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -304,7 +304,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, 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 1c37d7dfe2f..e585b72e5fa 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6508,17 +6508,24 @@ ResOwnerPrintBufferPin(Datum res)
  * even by the same block.  This inherent raciness without other interlocking
  * makes the function unsuitable for non-testing usage.
  *
+ * *flushed is optional. If it is provided, it is set to true if the buffer
+ * was dirty and has been flushed.  However, this does not necessarily mean
+ * that we flushed the buffer, it could have been flushed by someone else.
+ *
  * Returns true if the buffer was valid and it has now been made invalid.
  * Returns false if it wasn't valid, if it couldn't be evicted due to a pin,
  * or if the buffer becomes dirty again while we're trying to write it out.
  */
 bool
-EvictUnpinnedBuffer(Buffer buf)
+EvictUnpinnedBuffer(Buffer buf, bool *flushed)
 {
 	BufferDesc *desc;
 	uint32		buf_state;
 	bool		result;
 
+	if (flushed)
+		*flushed = false;
+
 	/* Make sure we can pin the buffer. */
 	ResourceOwnerEnlarge(CurrentResourceOwner);
 	ReservePrivateRefCountEntry();
@@ -6548,6 +6555,8 @@ EvictUnpinnedBuffer(Buffer buf)
 	{
 		LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_SHARED);
 		FlushBuffer(desc, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL);
+		if (flushed)
+			*flushed = true;
 		LWLockRelease(BufferDescriptorGetContentLock(desc));
 	}
 
diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c
index bef0ecd9007..f9cf6ce8a26 100644
--- a/src/test/modules/test_aio/test_aio.c
+++ b/src/test/modules/test_aio/test_aio.c
@@ -237,7 +237,7 @@ modify_rel_block(PG_FUNCTION_ARGS)
 	if (BufferIsLocal(buf))
 		InvalidateLocalBuffer(GetLocalBufferDescriptor(-buf - 1), true);
 	else
-		EvictUnpinnedBuffer(buf);
+		EvictUnpinnedBuffer(buf, NULL);
 
 	/*
 	 * Now modify the page as asked for by the caller.
@@ -493,7 +493,7 @@ invalidate_rel_block(PG_FUNCTION_ARGS)
 
 			if (BufferIsLocal(buf))
 				InvalidateLocalBuffer(GetLocalBufferDescriptor(-buf - 1), true);
-			else if (!EvictUnpinnedBuffer(buf))
+			else if (!EvictUnpinnedBuffer(buf, NULL))
 				elog(ERROR, "couldn't evict");
 		}
 	}
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index 802a5112d77..187e13e8cda 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -369,12 +369,15 @@
   <para>
    The <function>pg_buffercache_evict()</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, if it couldn't be evicted because it
-   was pinned, or if it became dirty again after an attempt to write it out.
-   The result is immediately out of date upon return, as the buffer might
-   become valid again at any time due to concurrent activity.  The function is
-   intended for developer testing only.
+   the <structname>pg_buffercache</structname> view.  It returns the
+   information about whether the buffer is evicted and flushed.  evicted
+   column is true on success, and false if the buffer wasn't valid, if it
+   couldn't be evicted because it was pinned, or if it became dirty again
+   after an attempt to write it out.  flushed column is true if the buffer is
+   flushed.  This does not necessarily mean that buffer is flushed by us, it
+   might be flushed by someone else.  The result is immediately out of date
+   upon return, as the buffer might become valid again at any time due to
+   concurrent activity. The function is intended for developer testing only.
   </para>
  </sect2>
 
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..18597923af8
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
@@ -0,0 +1,9 @@
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.6'" to load this file. \quit
+
+DROP FUNCTION pg_buffercache_evict(integer);
+CREATE FUNCTION pg_buffercache_evict(
+    IN int,
+    OUT evicted boolean,
+    OUT flushed boolean)
+AS 'MODULE_PATHNAME', 'pg_buffercache_evict'
+LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
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 62602af1775..9493a40e804 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -19,6 +19,7 @@
 #define NUM_BUFFERCACHE_PAGES_ELEM	9
 #define NUM_BUFFERCACHE_SUMMARY_ELEM 5
 #define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4
+#define NUM_BUFFERCACHE_EVICT_ELEM 2
 
 PG_MODULE_MAGIC_EXT(
 					.name = "pg_buffercache",
@@ -358,15 +359,32 @@ pg_buffercache_usage_counts(PG_FUNCTION_ARGS)
 Datum
 pg_buffercache_evict(PG_FUNCTION_ARGS)
 {
+	Datum		result;
+	TupleDesc	tupledesc;
+	HeapTuple	tuple;
+	Datum		values[NUM_BUFFERCACHE_EVICT_ELEM];
+	bool		nulls[NUM_BUFFERCACHE_EVICT_ELEM] = {0};
+
 	Buffer		buf = PG_GETARG_INT32(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")));
+				 errmsg("must be superuser to use %s()",
+						"pg_buffercache_evict")));
 
 	if (buf < 1 || buf > NBuffers)
 		elog(ERROR, "bad buffer ID: %d", buf);
 
-	PG_RETURN_BOOL(EvictUnpinnedBuffer(buf));
+	values[0] = BoolGetDatum(EvictUnpinnedBuffer(buf, &flushed));
+	values[1] = BoolGetDatum(flushed);
+
+	tuple = heap_form_tuple(tupledesc, values, nulls);
+	result = HeapTupleGetDatum(tuple);
+
+	PG_RETURN_DATUM(result);
 }
-- 
2.47.2

From fd6f751c14cc5e637a60f8f2bcae8871861321c9 Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavu...@gmail.com>
Date: Fri, 4 Apr 2025 13:35:17 +0300
Subject: [PATCH v6 2/4] 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.

Author: Nazir Bilal Yavuz <byavu...@gmail.com>
Reviewed-by: Andres Freund <and...@anarazel.de>
Reviewed-by: Aidar Imamov <a.ima...@postgrespro.ru>
Reviewed-by: Joseph Koshakow <kosh...@gmail.com>
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com
---
 src/include/storage/bufmgr.h                  |  2 +
 src/backend/storage/buffer/bufmgr.c           | 92 +++++++++++++++++
 doc/src/sgml/pgbuffercache.sgml               | 58 ++++++++++-
 .../pg_buffercache--1.5--1.6.sql              | 13 +++
 contrib/pg_buffercache/pg_buffercache_pages.c | 98 +++++++++++++++++++
 5 files changed, 261 insertions(+), 2 deletions(-)

diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index fab65824b18..48f5182635c 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -305,6 +305,8 @@ extern void LimitAdditionalPins(uint32 *additional_pins);
 extern void LimitAdditionalLocalPins(uint32 *additional_pins);
 
 extern bool EvictUnpinnedBuffer(Buffer buf, bool *flushed);
+extern void EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted,
+									int32 *buffers_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 e585b72e5fa..e2bc155b700 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6568,6 +6568,98 @@ EvictUnpinnedBuffer(Buffer buf, bool *flushed)
 	return result;
 }
 
+/*
+ * Try to evict all the shared buffers containing provided relation's pages.
+ *
+ * This function is intended for testing/development use only!
+ *
+ * We need this helper function because of the following reason.
+ * ReservePrivateRefCountEntry() needs to be called before acquiring the
+ * buffer header lock but ReservePrivateRefCountEntry() is static and it would
+ * be better to have it as static. Hence, it can't be called from outside of
+ * this file. This helper function is created to bypass that problem.
+ *
+ * Also, we should minimize the amount of code outside of storage/buffer that
+ * needs to know about BuferDescs etc., it is a layering violation to access
+ * that outside.
+ *
+ * The caller must hold at least AccessShareLock on the relation to prevent
+ * the relation from being dropped.
+ *
+ * buffers_evicted and buffers_flushed are optional. If they are provided,
+ * they are set the total number of buffers evicted and flushed respectively.
+ */
+void
+EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted, int32 *buffers_flushed)
+{
+	if (buffers_evicted)
+		*buffers_evicted = 0;
+	if (buffers_flushed)
+		*buffers_flushed = 0;
+
+	Assert(!RelationUsesLocalBuffers(rel));
+
+	for (int buf = 1; buf <= NBuffers; buf++)
+	{
+		uint32		buf_state = 0;
+		BufferDesc *bufHdr = GetBufferDescriptor(buf - 1);
+
+		/* An unlocked precheck should be safe and saves some cycles. */
+		if (!BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator))
+			continue;
+
+		/* Make sure we can pin the buffer. */
+		ResourceOwnerEnlarge(CurrentResourceOwner);
+		ReservePrivateRefCountEntry();
+
+		buf_state = LockBufHdr(bufHdr);
+		if (BufTagMatchesRelFileLocator(&bufHdr->tag, &rel->rd_locator))
+		{
+			/*
+			 * This is basically copy of the EvictUnpinnedBuffer(). This is
+			 * required because buffer header lock needs to be acquired before
+			 * calling the EvictUnpinnedBuffer().
+			 */
+
+			if ((buf_state & BM_VALID) == 0)
+			{
+				UnlockBufHdr(bufHdr, buf_state);
+				continue;
+			}
+
+			/* Check that it's not pinned already. */
+			if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
+			{
+				UnlockBufHdr(bufHdr, buf_state);
+				continue;
+			}
+
+			PinBuffer_Locked(bufHdr);	/* releases spinlock */
+
+			/* If it was dirty, try to clean it once. */
+			if (buf_state & BM_DIRTY)
+			{
+				LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
+				FlushBuffer(bufHdr, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL);
+				if (buffers_flushed)
+					(*buffers_flushed)++;
+				LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
+			}
+
+			/*
+			 * This will return false if it becomes dirty or someone else pins
+			 * it.
+			 */
+			if (InvalidateVictimBuffer(bufHdr) && buffers_evicted)
+				(*buffers_evicted)++;
+
+			UnpinBuffer(bufHdr);
+		}
+		else
+			UnlockBufHdr(bufHdr, buf_state);
+	}
+}
+
 /*
  * Generic implementation of the AIO handle staging callback for readv/writev
  * on local/shared buffers.
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index 187e13e8cda..c7e10f97ab6 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -27,12 +27,22 @@
   <primary>pg_buffercache_evict</primary>
  </indexterm>
 
+ <indexterm>
+  <primary>pg_buffercache_evict_relation</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>pg_buffercache_evict_all</primary>
+ </indexterm>
+
  <para>
   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> function and the
+  <function>pg_buffercache_evict_all()</function> function.
  </para>
 
  <para>
@@ -65,6 +75,19 @@
   function is restricted to superusers only.
  </para>
 
+ <para>
+  The <function>pg_buffercache_evict_relation()</function> function allows all
+  unpinned shared buffers in the relation to be evicted from the buffer pool
+  given a relation identifier.  Use of this function is restricted to
+  superusers only.
+ </para>
+
+ <para>
+  The <function>pg_buffercache_evict_all()</function> function allows all
+  unpinned shared buffers to be evicted in the buffer pool.  Use of this
+  function is restricted to superusers only.
+ </para>
+
  <sect2 id="pgbuffercache-pg-buffercache">
   <title>The <structname>pg_buffercache</structname> View</title>
 
@@ -381,6 +404,37 @@
   </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 the <function>pg_buffercache_evict()</function> function.  The
+   difference is that the <function>pg_buffercache_evict_relation()</function>
+   takes a relation identifier instead of buffer identifier.  Then, it tries
+   to evict all buffers for all forks in that relation.  It returns the number
+   of evicted and flushed buffers.  Flushed buffers aren't necessarily flushed
+   by us, they might be flushed by someone else.  The result is immediately
+   out of date upon return, as the buffer might become valid again at any time
+   due to concurrent activity.  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 the <function>pg_buffercache_evict()</function> function.  The
+   difference is, the <function>pg_buffercache_evict_all()</function> function
+   does not take an argument; instead it tries to evict all buffers in the
+   buffer pool.  It returns the number of evicted and flushed buffers.
+   Flushed buffers aren't necessarily flushed by us, they might be flushed by
+   someone else.  The result is immediately out of date upon return, as the
+   buffer might become valid again at any time due to concurrent activity.
+   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 18597923af8..d54bb1fd6f8 100644
--- a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
+++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
@@ -7,3 +7,16 @@ CREATE FUNCTION pg_buffercache_evict(
     OUT flushed boolean)
 AS 'MODULE_PATHNAME', 'pg_buffercache_evict'
 LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
+
+CREATE FUNCTION pg_buffercache_evict_relation(
+    IN regclass,
+    OUT buffers_evicted int4,
+    OUT buffers_flushed int4)
+AS 'MODULE_PATHNAME', 'pg_buffercache_evict_relation'
+LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
+
+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_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 9493a40e804..b6eba76fd27 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -9,10 +9,12 @@
 #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/rel.h"
 
 
 #define NUM_BUFFERCACHE_PAGES_MIN_ELEM	8
@@ -20,6 +22,8 @@
 #define NUM_BUFFERCACHE_SUMMARY_ELEM 5
 #define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4
 #define NUM_BUFFERCACHE_EVICT_ELEM 2
+#define NUM_BUFFERCACHE_EVICT_RELATION_ELEM 2
+#define NUM_BUFFERCACHE_EVICT_ALL_ELEM 2
 
 PG_MODULE_MAGIC_EXT(
 					.name = "pg_buffercache",
@@ -68,6 +72,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)
@@ -388,3 +394,95 @@ pg_buffercache_evict(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * 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;
+
+	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 %s()",
+						"pg_buffercache_evict_relation")));
+
+	relOid = PG_GETARG_OID(0);
+
+	rel = relation_open(relOid, AccessShareLock);
+
+	if (RelationUsesLocalBuffers(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("relation uses local buffers, %s() is intended to be used for shared buffers only",
+						"pg_buffercache_evict_relation")));
+
+	EvictRelUnpinnedBuffers(rel, &buffers_evicted, &buffers_flushed);
+
+	relation_close(rel, AccessShareLock);
+
+	values[0] = Int32GetDatum(buffers_evicted);
+	values[1] = Int32GetDatum(buffers_flushed);
+
+	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 %s()",
+						"pg_buffercache_evict_all")));
+
+	for (int buf = 1; buf <= NBuffers; buf++)
+	{
+		if (EvictUnpinnedBuffer(buf, &flushed))
+			buffers_evicted++;
+		if (flushed)
+			buffers_flushed++;
+	}
+
+	values[0] = Int32GetDatum(buffers_evicted);
+	values[1] = Int32GetDatum(buffers_flushed);
+
+	tuple = heap_form_tuple(tupledesc, values, nulls);
+	result = HeapTupleGetDatum(tuple);
+
+	PG_RETURN_DATUM(result);
+}
-- 
2.47.2

From 9d8948f4f78fe1c9ed1e757bf1e5a190403c82ba Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavu...@gmail.com>
Date: Fri, 4 Apr 2025 13:39:49 +0300
Subject: [PATCH v6 3/4] 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.

Author: Nazir Bilal Yavuz <byavu...@gmail.com>
Reviewed-by: Andres Freund <and...@anarazel.de>
Reviewed-by: Aidar Imamov <a.ima...@postgrespro.ru>
Reviewed-by: Joseph Koshakow <kosh...@gmail.com>
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com
---
 src/include/storage/bufmgr.h                  |  1 +
 src/backend/storage/buffer/bufmgr.c           | 54 +++++++++++++++++++
 doc/src/sgml/pgbuffercache.sgml               | 54 +++++++++++++++++--
 .../pg_buffercache--1.5--1.6.sql              | 10 ++++
 contrib/pg_buffercache/pg_buffercache_pages.c | 45 ++++++++++++++++
 5 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 48f5182635c..ddb442b600d 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -307,6 +307,7 @@ extern void LimitAdditionalLocalPins(uint32 *additional_pins);
 extern bool EvictUnpinnedBuffer(Buffer buf, bool *flushed);
 extern void EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted,
 									int32 *buffers_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 e2bc155b700..4b7b0534c67 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6660,6 +6660,60 @@ EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted, int32 *buffers_flu
 	}
 }
 
+/*
+ * Try to mark the provided shared buffer as dirty.
+ *
+ * This function is intended for testing/development use only!
+ *
+ * Same as EvictUnpinnedBuffer() but with MarkBufferDirty() call inside.
+ *
+ * Returns true if the buffer was already dirty or it has successfully been
+ * marked as dirty.
+ */
+bool
+MarkUnpinnedBufferDirty(Buffer buf)
+{
+	BufferDesc *desc;
+	uint32		buf_state;
+
+	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));
+	}
+
+	UnpinBuffer(desc);
+
+	return true;
+}
+
 /*
  * Generic implementation of the AIO handle staging callback for readv/writev
  * on local/shared buffers.
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index c7e10f97ab6..83818fa6328 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -35,14 +35,24 @@
   <primary>pg_buffercache_evict_all</primary>
  </indexterm>
 
+ <indexterm>
+  <primary>pg_buffercache_mark_dirty</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>pg_buffercache_mark_dirty_all</primary>
+ </indexterm>
+
  <para>
   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, the
-  <function>pg_buffercache_evict()</function>, the
-  <function>pg_buffercache_evict_relation()</function> function and the
-  <function>pg_buffercache_evict_all()</function> function.
+  <function>pg_buffercache_evict()</function> function, the
+  <function>pg_buffercache_evict_relation()</function> function, the
+  <function>pg_buffercache_evict_all()</function> function, the
+  <function>pg_buffercache_mark_dirty()</function> function and the
+  <function>pg_buffercache_mark_dirty_all()</function> function.
  </para>
 
  <para>
@@ -88,6 +98,18 @@
   function 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.  Use of this function is
+  restricted to superusers only.
+ </para>
+
  <sect2 id="pgbuffercache-pg-buffercache">
   <title>The <structname>pg_buffercache</structname> View</title>
 
@@ -435,6 +457,32 @@
   </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 result is immediately out of
+   date upon return, as the buffer might become valid again at any time due to
+   concurrent activity.  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 the <function>pg_buffercache_mark_dirty()</function> function.
+   The difference is, the <function>pg_buffercache_mark_dirty_all()</function>
+   function does not take an argument; instead it tries to mark all buffers
+   dirty in the buffer pool.  The result is immediately out of date upon
+   return, as the buffer might become valid again at any time due to
+   concurrent activity.  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 d54bb1fd6f8..b40ee2599a4 100644
--- a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
+++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql
@@ -20,3 +20,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 b6eba76fd27..1afe70b3eb2 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -74,6 +74,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)
@@ -486,3 +488,46 @@ pg_buffercache_evict_all(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * Try to mark a shared buffer as dirty.
+ */
+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 %s()",
+						"pg_buffercache_mark_dirty")));
+
+	if (buf < 1 || buf > NBuffers)
+		elog(ERROR, "bad buffer ID: %d", buf);
+
+	PG_RETURN_BOOL(MarkUnpinnedBufferDirty(buf));
+}
+
+/*
+ * Try to mark all the shared buffers as dirty.
+ */
+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 %s()",
+						"pg_buffercache_mark_dirty_all")));
+
+	for (int buf = 1; buf <= NBuffers; buf++)
+	{
+		if (MarkUnpinnedBufferDirty(buf))
+			buffers_dirtied++;
+	}
+
+	PG_RETURN_INT32(buffers_dirtied);
+}
-- 
2.47.2

From 99f8af2cfca5319e2c9d9b9f9518e5c9d2d04e84 Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavu...@gmail.com>
Date: Fri, 4 Apr 2025 13:40:29 +0300
Subject: [PATCH v6 4/4] Extend pg_buffercache extension's tests

Prior commits extended pg_buffercache extension with
pg_buffercache_evict_relation(), pg_buffercache_evict_all(),
pg_buffercache_mark_dirty() and pg_buffercache_mark_dirty_all()
functions. Add test for these.

Also, there was no test for pg_buffercache_evict(), add test for this
too.

Author: Nazir Bilal Yavuz <byavu...@gmail.com>
Suggested-by: Aidar Imamov <a.ima...@postgrespro.ru>
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com
---
 .../expected/pg_buffercache.out               | 89 +++++++++++++++++++
 contrib/pg_buffercache/sql/pg_buffercache.sql | 51 +++++++++++
 2 files changed, 140 insertions(+)

diff --git a/contrib/pg_buffercache/expected/pg_buffercache.out b/contrib/pg_buffercache/expected/pg_buffercache.out
index b745dc69eae..5d1a6f8aefa 100644
--- a/contrib/pg_buffercache/expected/pg_buffercache.out
+++ b/contrib/pg_buffercache/expected/pg_buffercache.out
@@ -55,3 +55,92 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
  t
 (1 row)
 
+RESET role;
+------
+---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
+------
+CREATE ROLE regress_buffercache_normal;
+SET ROLE regress_buffercache_normal;
+-- These should fail because these are STRICT functions
+SELECT * FROM pg_buffercache_evict();
+ERROR:  function pg_buffercache_evict() does not exist
+LINE 1: SELECT * FROM pg_buffercache_evict();
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT * FROM pg_buffercache_evict_relation();
+ERROR:  function pg_buffercache_evict_relation() does not exist
+LINE 1: SELECT * FROM pg_buffercache_evict_relation();
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT * FROM pg_buffercache_mark_dirty();
+ERROR:  function pg_buffercache_mark_dirty() does not exist
+LINE 1: SELECT * FROM pg_buffercache_mark_dirty();
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+-- These should fail because they need to be called as SUPERUSER
+SELECT * FROM pg_buffercache_evict(1);
+ERROR:  must be superuser to use pg_buffercache_evict()
+SELECT * FROM pg_buffercache_evict_relation(1);
+ERROR:  must be superuser to use pg_buffercache_evict_relation()
+SELECT * FROM pg_buffercache_evict_all();
+ERROR:  must be superuser to use pg_buffercache_evict_all()
+SELECT * FROM pg_buffercache_mark_dirty(1);
+ERROR:  must be superuser to use pg_buffercache_mark_dirty()
+SELECT * FROM pg_buffercache_mark_dirty_all();
+ERROR:  must be superuser to use pg_buffercache_mark_dirty_all()
+RESET ROLE;
+CREATE ROLE regress_buffercache_superuser SUPERUSER;
+SET ROLE regress_buffercache_superuser;
+-- These should fail because they are not called by valid range of buffers
+-- Number of the shared buffers are limited by max integer
+SELECT 2147483647 max_buffers \gset
+SELECT * FROM pg_buffercache_evict(0);
+ERROR:  bad buffer ID: 0
+SELECT * FROM pg_buffercache_evict(:max_buffers);
+ERROR:  bad buffer ID: 2147483647
+SELECT * FROM pg_buffercache_mark_dirty(0);
+ERROR:  bad buffer ID: 0
+SELECT * FROM pg_buffercache_mark_dirty(:max_buffers);
+ERROR:  bad buffer ID: 2147483647
+-- This should fail because pg_buffercache_evict_relation() doesn't accept
+-- local relations
+CREATE TEMP TABLE temp_pg_buffercache();
+SELECT * FROM pg_buffercache_evict_relation('temp_pg_buffercache');
+ERROR:  relation uses local buffers, pg_buffercache_evict_relation() is intended to be used for shared buffers only
+DROP TABLE temp_pg_buffercache;
+-- These shouldn't fail
+SELECT COUNT(*) > 0 FROM pg_buffercache_evict(1);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT COUNT(*) > 0 FROM pg_buffercache_evict_all();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT COUNT(*) > 0 FROM pg_buffercache_mark_dirty(1);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT COUNT(*) > 0 FROM pg_buffercache_mark_dirty_all();
+ ?column? 
+----------
+ t
+(1 row)
+
+CREATE TABLE shared_pg_buffercache();
+SELECT COUNT(*) > 0 FROM pg_buffercache_evict_relation('shared_pg_buffercache');
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE shared_pg_buffercache;
+RESET ROLE;
+DROP ROLE regress_buffercache_normal;
+DROP ROLE regress_buffercache_superuser;
diff --git a/contrib/pg_buffercache/sql/pg_buffercache.sql b/contrib/pg_buffercache/sql/pg_buffercache.sql
index 944fbb1beae..f859af15220 100644
--- a/contrib/pg_buffercache/sql/pg_buffercache.sql
+++ b/contrib/pg_buffercache/sql/pg_buffercache.sql
@@ -26,3 +26,54 @@ SET ROLE pg_monitor;
 SELECT count(*) > 0 FROM pg_buffercache;
 SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary();
 SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
+RESET role;
+
+------
+---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
+------
+
+CREATE ROLE regress_buffercache_normal;
+SET ROLE regress_buffercache_normal;
+
+-- These should fail because these are STRICT functions
+SELECT * FROM pg_buffercache_evict();
+SELECT * FROM pg_buffercache_evict_relation();
+SELECT * FROM pg_buffercache_mark_dirty();
+
+-- These should fail because they need to be called as SUPERUSER
+SELECT * FROM pg_buffercache_evict(1);
+SELECT * FROM pg_buffercache_evict_relation(1);
+SELECT * FROM pg_buffercache_evict_all();
+SELECT * FROM pg_buffercache_mark_dirty(1);
+SELECT * FROM pg_buffercache_mark_dirty_all();
+
+RESET ROLE;
+CREATE ROLE regress_buffercache_superuser SUPERUSER;
+SET ROLE regress_buffercache_superuser;
+
+-- These should fail because they are not called by valid range of buffers
+-- Number of the shared buffers are limited by max integer
+SELECT 2147483647 max_buffers \gset
+SELECT * FROM pg_buffercache_evict(0);
+SELECT * FROM pg_buffercache_evict(:max_buffers);
+SELECT * FROM pg_buffercache_mark_dirty(0);
+SELECT * FROM pg_buffercache_mark_dirty(:max_buffers);
+
+-- This should fail because pg_buffercache_evict_relation() doesn't accept
+-- local relations
+CREATE TEMP TABLE temp_pg_buffercache();
+SELECT * FROM pg_buffercache_evict_relation('temp_pg_buffercache');
+DROP TABLE temp_pg_buffercache;
+
+-- These shouldn't fail
+SELECT COUNT(*) > 0 FROM pg_buffercache_evict(1);
+SELECT COUNT(*) > 0 FROM pg_buffercache_evict_all();
+SELECT COUNT(*) > 0 FROM pg_buffercache_mark_dirty(1);
+SELECT COUNT(*) > 0 FROM pg_buffercache_mark_dirty_all();
+CREATE TABLE shared_pg_buffercache();
+SELECT COUNT(*) > 0 FROM pg_buffercache_evict_relation('shared_pg_buffercache');
+DROP TABLE shared_pg_buffercache;
+
+RESET ROLE;
+DROP ROLE regress_buffercache_normal;
+DROP ROLE regress_buffercache_superuser;
-- 
2.47.2

Reply via email to