From 22f90970ddd79ec9dd48b1ab3083e17e3d18f0cb Mon Sep 17 00:00:00 2001 From: Nazir Bilal Yavuz Date: Wed, 25 Dec 2024 15:46:10 +0300 Subject: [PATCH v4 3/3] 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 Reviewed-by: Andres Freund Reviewed-by: Aidar Imamov Reviewed-by: Joseph Koshakow Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com --- .../pg_buffercache--1.5--1.6.sql | 10 ++++ contrib/pg_buffercache/pg_buffercache_pages.c | 43 +++++++++++++++ doc/src/sgml/pgbuffercache.sgml | 54 +++++++++++++++++-- src/backend/storage/buffer/bufmgr.c | 54 +++++++++++++++++++ src/include/storage/bufmgr.h | 1 + 5 files changed, 159 insertions(+), 3 deletions(-) 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 15881f5b8fe..9e7c3fb6a5f 100644 --- a/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql +++ b/contrib/pg_buffercache/pg_buffercache--1.5--1.6.sql @@ -23,3 +23,13 @@ CREATE FUNCTION pg_buffercache_evict_all( RETURNS record 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 a76625307a7..a02067ec162 100644 --- a/contrib/pg_buffercache/pg_buffercache_pages.c +++ b/contrib/pg_buffercache/pg_buffercache_pages.c @@ -75,6 +75,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 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 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 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 pg_buffercache_mark_dirty_all function"))); + + for (int buf = 1; buf <= NBuffers; buf++) + { + if (MarkUnpinnedBufferDirty(buf)) + buffers_dirtied++; + } + + PG_RETURN_INT32(buffers_dirtied); +} diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml index 681c74251d4..baf64e07b4e 100644 --- a/doc/src/sgml/pgbuffercache.sgml +++ b/doc/src/sgml/pgbuffercache.sgml @@ -35,14 +35,24 @@ pg_buffercache_evict_all + + pg_buffercache_mark_dirty + + + + pg_buffercache_mark_dirty_all + + This module provides the pg_buffercache_pages() function (wrapped in the pg_buffercache view), the pg_buffercache_summary() function, the pg_buffercache_usage_counts() function, the - pg_buffercache_evict(), the - pg_buffercache_evict_relation() function and the - pg_buffercache_evict_all() function. + pg_buffercache_evict() function, the + pg_buffercache_evict_relation() function, the + pg_buffercache_evict_all() function, the + pg_buffercache_mark_dirty() function and the + pg_buffercache_mark_dirty_all() function. @@ -87,6 +97,18 @@ restricted to superusers only. + + The pg_buffercache_mark_dirty() 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. + + + + The pg_buffercache_mark_dirty_all() function tries to + mark all buffers dirty in the buffer pool. Use of this function is + restricted to superusers only. + + The <structname>pg_buffercache</structname> View @@ -433,6 +455,32 @@ + + The <structname>pg_buffercache_mark_dirty</structname> Function + + The pg_buffercache_mark_dirty() function takes a + buffer identifier, as shown in the bufferid + column of the pg_buffercache 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. + + + + + The <structname>pg_buffercache_mark_dirty_all</structname> Function + + The pg_buffercache_mark_dirty_all() function is very + similar to the pg_buffercache_mark_dirty() function. + The difference is, the pg_buffercache_mark_dirty_all() + 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. + + + Sample Output diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 6105c6d2d73..2decd8858b5 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -6208,3 +6208,57 @@ EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted, int32 *buffers_flu UnlockBufHdr(bufHdr, buf_state); } } + +/* + * 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; +} diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index 952c26c01c1..f78cda19ec6 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -301,6 +301,7 @@ extern void LimitAdditionalLocalPins(uint32 *additional_pins); extern bool EvictUnpinnedBuffer(Buffer buf, uint32 buf_state, 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); -- 2.43.0