Add pg_buffercache_evict() function for testing.
authorThomas Munro <tmunro@postgresql.org>
Sat, 6 Apr 2024 21:13:17 +0000 (09:13 +1200)
committerThomas Munro <tmunro@postgresql.org>
Mon, 8 Apr 2024 04:23:40 +0000 (16:23 +1200)
When testing buffer pool logic, it is useful to be able to evict
arbitrary blocks.  This function can be used in SQL queries over the
pg_buffercache view to set up a wide range of buffer pool states.  Of
course, buffer mappings might change concurrently so you might evict a
block other than the one you had in mind, and another session might
bring it back in at any time.  That's OK for the intended purpose of
setting up developer testing scenarios, and more complicated interlocking
schemes to give stronger guararantees about that would likely be less
flexible for actual testing work anyway.  Superuser-only.

Author: Palak Chaturvedi <chaturvedipalak1911@gmail.com>
Author: Thomas Munro <thomas.munro@gmail.com> (docs, small tweaks)
Reviewed-by: Nitin Jadhav <nitinjadhavpostgres@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Cary Huang <cary.huang@highgo.ca>
Reviewed-by: Cédric Villemain <cedric.villemain+pgsql@abcsql.com>
Reviewed-by: Jim Nasby <jim.nasby@gmail.com>
Reviewed-by: Maxim Orlov <orlovmg@gmail.com>
Reviewed-by: Thomas Munro <thomas.munro@gmail.com>
Reviewed-by: Melanie Plageman <melanieplageman@gmail.com>
Discussion: https://postgr.es/m/CALfch19pW48ZwWzUoRSpsaV9hqt0UPyaBPC4bOZ4W+c7FF566A@mail.gmail.com

contrib/pg_buffercache/Makefile
contrib/pg_buffercache/meson.build
contrib/pg_buffercache/pg_buffercache--1.4--1.5.sql [new file with mode: 0644]
contrib/pg_buffercache/pg_buffercache.control
contrib/pg_buffercache/pg_buffercache_pages.c
doc/src/sgml/pgbuffercache.sgml
src/backend/storage/buffer/bufmgr.c
src/include/storage/bufmgr.h

index d6b58d4da94ababa83dfd9870be27fd00dda3bd4..eae65ead9e5021cad062f7df0abc9160fb8575ae 100644 (file)
@@ -8,7 +8,7 @@ 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.3--1.4.sql pg_buffercache--1.4--1.5.sql
 PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"
 
 REGRESS = pg_buffercache
index c86e33cc9587a2925e1fc6d8a8e68f9f104d471d..1ca3452918bbc13e66e7a32eba448af907ae19c4 100644 (file)
@@ -22,6 +22,7 @@ install_data(
   'pg_buffercache--1.2--1.3.sql',
   'pg_buffercache--1.2.sql',
   'pg_buffercache--1.3--1.4.sql',
+  'pg_buffercache--1.4--1.5.sql',
   'pg_buffercache.control',
   kwargs: contrib_data_args,
 )
diff --git a/contrib/pg_buffercache/pg_buffercache--1.4--1.5.sql b/contrib/pg_buffercache/pg_buffercache--1.4--1.5.sql
new file mode 100644 (file)
index 0000000..0fb18ff
--- /dev/null
@@ -0,0 +1,6 @@
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.5'" to load this file. \quit
+
+CREATE FUNCTION pg_buffercache_evict(IN int)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'pg_buffercache_evict'
+LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
index a82ae5f9bb5368834428a1e975f62e8d75687595..5ee875f77dd9067ffabc4b347a15de67646f8366 100644 (file)
@@ -1,5 +1,5 @@
 # pg_buffercache extension
 comment = 'examine the shared buffer cache'
-default_version = '1.4'
+default_version = '1.5'
 module_pathname = '$libdir/pg_buffercache'
 relocatable = true
index 33167323653650739f35c0bf1c309b108fbc2dc1..3ae0a018e103cb99fefe5053ea15fc845d4c9747 100644 (file)
@@ -63,6 +63,7 @@ typedef struct
 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);
 
 Datum
 pg_buffercache_pages(PG_FUNCTION_ARGS)
@@ -347,3 +348,22 @@ pg_buffercache_usage_counts(PG_FUNCTION_ARGS)
 
    return (Datum) 0;
 }
+
+/*
+ * Try to evict a shared buffer.
+ */
+Datum
+pg_buffercache_evict(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_evict function")));
+
+   if (buf < 1 || buf > NBuffers)
+       elog(ERROR, "bad buffer ID: %d", buf);
+
+   PG_RETURN_BOOL(EvictUnpinnedBuffer(buf));
+}
index afe2d97834049e466d66bb58c0f51fd3cdc65047..4b90eefc0b078dc4b4b9df5a6ea103526f79d10e 100644 (file)
@@ -11,6 +11,8 @@
  <para>
   The <filename>pg_buffercache</filename> module provides a means for
   examining what's happening in the shared buffer cache in real time.
+  It also offers a low-level way to evict data from it, for testing
+  purposes.
  </para>
 
  <indexterm>
   <primary>pg_buffercache_summary</primary>
  </indexterm>
 
+ <indexterm>
+  <primary>pg_buffercache_evict</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, and the
-  <function>pg_buffercache_usage_counts()</function> function.
+  the <function>pg_buffercache_summary()</function> function, the
+  <function>pg_buffercache_usage_counts()</function> function and
+  the <function>pg_buffercache_evict()</function> function.
  </para>
 
  <para>
  </para>
 
  <para>
-  By default, use is restricted to superusers and roles with privileges of the
-  <literal>pg_monitor</literal> role. Access may be granted to others
-  using <command>GRANT</command>.
+  By default, use of the above functions is restricted to superusers and roles
+  with privileges of the <literal>pg_monitor</literal> role. Access may be
+  granted to others using <command>GRANT</command>.
+ </para>
+
+ <para>
+  The <function>pg_buffercache_evict()</function> function allows a block to
+  be evicted from the buffer pool given a buffer identifier.  Use of this
+  function is restricted to superusers only.
  </para>
 
  <sect2 id="pgbuffercache-pg-buffercache">
   </para>
  </sect2>
 
- <sect2 id="pgbuffercache-sample-output">
+ <sect2 id="pgbuffercache-pg-buffercache-evict">
+  <title>The <structname>pg_buffercache_evict</structname> Function</title>
+  <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.
+  </para>
+ </sect2>
+
+<sect2 id="pgbuffercache-sample-output">
   <title>Sample Output</title>
 
 <screen>
index 06e9ffd2b00d2e1fcb9abc2b7aa72ba4b6cbee1c..44836751b71a300055ae99f74b74ee22f0482c75 100644 (file)
@@ -6003,3 +6003,66 @@ ResOwnerPrintBufferPin(Datum res)
 {
    return DebugPrintBufferRefcount(DatumGetInt32(res));
 }
+
+/*
+ * Try to evict the current block in a shared buffer.
+ *
+ * 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 again by the time control is returned, potentially
+ * even by the same block.  This inherent raciness without other interlocking
+ * makes the function unsuitable for non-testing usage.
+ *
+ * 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)
+{
+   BufferDesc *desc;
+   uint32      buf_state;
+   bool        result;
+
+   /* Make sure we can pin the buffer. */
+   ResourceOwnerEnlarge(CurrentResourceOwner);
+   ReservePrivateRefCountEntry();
+
+   Assert(!BufferIsLocal(buf));
+   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 dirty, try to clean it once. */
+   if (buf_state & BM_DIRTY)
+   {
+       LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_SHARED);
+       FlushBuffer(desc, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL);
+       LWLockRelease(BufferDescriptorGetContentLock(desc));
+   }
+
+   /* This will return false if it becomes dirty or someone else pins it. */
+   result = InvalidateVictimBuffer(desc);
+
+   UnpinBuffer(desc);
+
+   return result;
+}
index 07ba1a605024eb601e3cd508062ee88d2e39497e..42211bfec4fce3bdb345c50f408472f44f8a570d 100644 (file)
@@ -305,6 +305,8 @@ extern bool BgBufferSync(struct WritebackContext *wb_context);
 extern void LimitAdditionalPins(uint32 *additional_pins);
 extern void LimitAdditionalLocalPins(uint32 *additional_pins);
 
+extern bool EvictUnpinnedBuffer(Buffer buf);
+
 /* in buf_init.c */
 extern void InitBufferPool(void);
 extern Size BufferShmemSize(void);