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 <byavu...@gmail.com> wrote: > On Mon, 17 Feb 2025 at 19:59, Andres Freund <and...@anarazel.de> 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
From 2090816c1a2d97b257f136ea7ab36c1c86dc1e2a Mon Sep 17 00:00:00 2001 From: Nazir Bilal Yavuz <byavu...@gmail.com> 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
From 56ef07473155d3e5f568dab710501bd92f8718ff Mon Sep 17 00:00:00 2001 From: Nazir Bilal Yavuz <byavu...@gmail.com> 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