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