diff options
author | Andres Freund <andres@anarazel.de> | 2025-04-08 02:16:51 -0400 |
---|---|---|
committer | Andres Freund <andres@anarazel.de> | 2025-04-08 02:19:32 -0400 |
commit | dcf7e1697ba75ce7883ad7c6bc26ba24422eb892 (patch) | |
tree | 6aeadaaafac3658ce47f4cdb88df31b0f9add807 /src | |
parent | d69d45a5a956e930dc91b3ca09a0188bf9fe2176 (diff) | |
download | postgresql-dcf7e1697ba75ce7883ad7c6bc26ba24422eb892.tar.gz postgresql-dcf7e1697ba75ce7883ad7c6bc26ba24422eb892.zip |
Add pg_buffercache_evict_{relation,all} functions
In addition to the added functions, the pg_buffercache_evict() function now
shows whether the buffer was flushed.
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., ~477ms vs. ~2576ms for
16GB of fully populated shared buffers).
These functions are intended for developer testing and debugging
purposes and are available to superusers only.
Minimal tests for the new functions are included. Also, there was no test for
pg_buffercache_evict(), test for this added too.
No new extension version is needed, as it was already increased this release
by ba2a3c2302f.
Author: Nazir Bilal Yavuz <byavuz81@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Aidar Imamov <a.imamov@postgrespro.ru>
Reviewed-by: Joseph Koshakow <koshy44@gmail.com>
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/storage/buffer/bufmgr.c | 175 | ||||
-rw-r--r-- | src/include/storage/bufmgr.h | 9 | ||||
-rw-r--r-- | src/test/modules/test_aio/test_aio.c | 6 |
3 files changed, 163 insertions, 27 deletions
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 941d7fa3d94..db8f2b1754e 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -6510,37 +6510,20 @@ ResOwnerPrintBufferPin(Datum 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. + * Helper function to evict unpinned buffer whose buffer header lock is + * already acquired. */ -bool -EvictUnpinnedBuffer(Buffer buf) +static bool +EvictUnpinnedBufferInternal(BufferDesc *desc, bool *buffer_flushed) { - BufferDesc *desc; uint32 buf_state; bool result; - /* Make sure we can pin the buffer. */ - ResourceOwnerEnlarge(CurrentResourceOwner); - ReservePrivateRefCountEntry(); + *buffer_flushed = false; - Assert(!BufferIsLocal(buf)); - desc = GetBufferDescriptor(buf - 1); + buf_state = pg_atomic_read_u32(&(desc->state)); + Assert(buf_state & BM_LOCKED); - /* Lock the header and check if it's valid. */ - buf_state = LockBufHdr(desc); if ((buf_state & BM_VALID) == 0) { UnlockBufHdr(desc, buf_state); @@ -6561,6 +6544,7 @@ EvictUnpinnedBuffer(Buffer buf) { LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_SHARED); FlushBuffer(desc, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL); + *buffer_flushed = true; LWLockRelease(BufferDescriptorGetContentLock(desc)); } @@ -6573,6 +6557,149 @@ EvictUnpinnedBuffer(Buffer buf) } /* + * 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. + * + * *buffer_flushed is set to true if the buffer was dirty and has been + * flushed, false otherwise. However, *buffer_flushed=true 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, bool *buffer_flushed) +{ + BufferDesc *desc; + + Assert(BufferIsValid(buf) && !BufferIsLocal(buf)); + + /* Make sure we can pin the buffer. */ + ResourceOwnerEnlarge(CurrentResourceOwner); + ReservePrivateRefCountEntry(); + + desc = GetBufferDescriptor(buf - 1); + LockBufHdr(desc); + + return EvictUnpinnedBufferInternal(desc, buffer_flushed); +} + +/* + * Try to evict all the shared buffers. + * + * This function is intended for testing/development use only! See + * EvictUnpinnedBuffer(). + * + * The buffers_* parameters are mandatory and indicate the total count of + * buffers that: + * - buffers_evicted - were evicted + * - buffers_flushed - were flushed + * - buffers_skipped - could not be evicted + */ +void +EvictAllUnpinnedBuffers(int32 *buffers_evicted, int32 *buffers_flushed, + int32 *buffers_skipped) +{ + *buffers_evicted = 0; + *buffers_skipped = 0; + *buffers_flushed = 0; + + for (int buf = 1; buf <= NBuffers; buf++) + { + BufferDesc *desc = GetBufferDescriptor(buf - 1); + uint32 buf_state; + bool buffer_flushed; + + buf_state = pg_atomic_read_u32(&desc->state); + if (!(buf_state & BM_VALID)) + continue; + + ResourceOwnerEnlarge(CurrentResourceOwner); + ReservePrivateRefCountEntry(); + + LockBufHdr(desc); + + if (EvictUnpinnedBufferInternal(desc, &buffer_flushed)) + (*buffers_evicted)++; + else + (*buffers_skipped)++; + + if (buffer_flushed) + (*buffers_flushed)++; + } +} + +/* + * Try to evict all the shared buffers containing provided relation's pages. + * + * This function is intended for testing/development use only! See + * EvictUnpinnedBuffer(). + * + * The caller must hold at least AccessShareLock on the relation to prevent + * the relation from being dropped. + * + * The buffers_* parameters are mandatory and indicate the total count of + * buffers that: + * - buffers_evicted - were evicted + * - buffers_flushed - were flushed + * - buffers_skipped - could not be evicted + */ +void +EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted, + int32 *buffers_flushed, int32 *buffers_skipped) +{ + Assert(!RelationUsesLocalBuffers(rel)); + + *buffers_skipped = 0; + *buffers_evicted = 0; + *buffers_flushed = 0; + + for (int buf = 1; buf <= NBuffers; buf++) + { + BufferDesc *desc = GetBufferDescriptor(buf - 1); + uint32 buf_state = pg_atomic_read_u32(&(desc->state)); + bool buffer_flushed; + + /* An unlocked precheck should be safe and saves some cycles. */ + if ((buf_state & BM_VALID) == 0 || + !BufTagMatchesRelFileLocator(&desc->tag, &rel->rd_locator)) + continue; + + /* Make sure we can pin the buffer. */ + ResourceOwnerEnlarge(CurrentResourceOwner); + ReservePrivateRefCountEntry(); + + buf_state = LockBufHdr(desc); + + /* recheck, could have changed without the lock */ + if ((buf_state & BM_VALID) == 0 || + !BufTagMatchesRelFileLocator(&desc->tag, &rel->rd_locator)) + { + UnlockBufHdr(desc, buf_state); + continue; + } + + if (EvictUnpinnedBufferInternal(desc, &buffer_flushed)) + (*buffers_evicted)++; + else + (*buffers_skipped)++; + + if (buffer_flushed) + (*buffers_flushed)++; + } +} + +/* * Generic implementation of the AIO handle staging callback for readv/writev * on local/shared buffers. * diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index f2192ceb271..96150a6cfe9 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -304,7 +304,14 @@ 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 *buffer_flushed); +extern void EvictAllUnpinnedBuffers(int32 *buffers_evicted, + int32 *buffers_flushed, + int32 *buffers_skipped); +extern void EvictRelUnpinnedBuffers(Relation rel, + int32 *buffers_evicted, + int32 *buffers_flushed, + int32 *buffers_skipped); /* in buf_init.c */ extern void BufferManagerShmemInit(void); diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c index bef0ecd9007..1d776010ef4 100644 --- a/src/test/modules/test_aio/test_aio.c +++ b/src/test/modules/test_aio/test_aio.c @@ -203,6 +203,7 @@ modify_rel_block(PG_FUNCTION_ARGS) bool corrupt_header = PG_GETARG_BOOL(3); bool corrupt_checksum = PG_GETARG_BOOL(4); Page page = palloc_aligned(BLCKSZ, PG_IO_ALIGN_SIZE, 0); + bool flushed; Relation rel; Buffer buf; PageHeader ph; @@ -237,7 +238,7 @@ modify_rel_block(PG_FUNCTION_ARGS) if (BufferIsLocal(buf)) InvalidateLocalBuffer(GetLocalBufferDescriptor(-buf - 1), true); else - EvictUnpinnedBuffer(buf); + EvictUnpinnedBuffer(buf, &flushed); /* * Now modify the page as asked for by the caller. @@ -478,6 +479,7 @@ invalidate_rel_block(PG_FUNCTION_ARGS) BufferDesc *buf_hdr = BufferIsLocal(buf) ? GetLocalBufferDescriptor(-buf - 1) : GetBufferDescriptor(buf - 1); + bool flushed; LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); @@ -493,7 +495,7 @@ invalidate_rel_block(PG_FUNCTION_ARGS) if (BufferIsLocal(buf)) InvalidateLocalBuffer(GetLocalBufferDescriptor(-buf - 1), true); - else if (!EvictUnpinnedBuffer(buf)) + else if (!EvictUnpinnedBuffer(buf, &flushed)) elog(ERROR, "couldn't evict"); } } |