Skip to content

Commit 0b4b6d0

Browse files
nbyavuzCommitfest Bot
authored and
Commitfest Bot
committed
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 <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
1 parent f8c115a commit 0b4b6d0

File tree

10 files changed

+216
-7
lines changed

10 files changed

+216
-7
lines changed

contrib/pg_buffercache/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ EXTENSION = pg_buffercache
99
DATA = pg_buffercache--1.2.sql pg_buffercache--1.2--1.3.sql \
1010
pg_buffercache--1.1--1.2.sql pg_buffercache--1.0--1.1.sql \
1111
pg_buffercache--1.3--1.4.sql pg_buffercache--1.4--1.5.sql \
12-
pg_buffercache--1.5--1.6.sql
12+
pg_buffercache--1.5--1.6.sql pg_buffercache--1.6--1.7.sql
1313
PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"
1414

1515
REGRESS = pg_buffercache pg_buffercache_numa

contrib/pg_buffercache/expected/pg_buffercache.out

+29-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
5757

5858
RESET role;
5959
------
60-
---- Test pg_buffercache_evict* functions
60+
---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
6161
------
6262
CREATE ROLE regress_buffercache_normal;
6363
SET ROLE regress_buffercache_normal;
@@ -68,6 +68,10 @@ SELECT * FROM pg_buffercache_evict_relation(1);
6868
ERROR: must be superuser to use pg_buffercache_evict_relation()
6969
SELECT * FROM pg_buffercache_evict_all();
7070
ERROR: must be superuser to use pg_buffercache_evict_all()
71+
SELECT * FROM pg_buffercache_mark_dirty(1);
72+
ERROR: must be superuser to use pg_buffercache_mark_dirty()
73+
SELECT * FROM pg_buffercache_mark_dirty_all();
74+
ERROR: must be superuser to use pg_buffercache_mark_dirty_all()
7175
RESET ROLE;
7276
-- These should return nothing, because these are STRICT functions
7377
SELECT * FROM pg_buffercache_evict(NULL);
@@ -82,6 +86,12 @@ SELECT * FROM pg_buffercache_evict_relation(NULL);
8286
| |
8387
(1 row)
8488

89+
SELECT * FROM pg_buffercache_mark_dirty(NULL);
90+
pg_buffercache_mark_dirty
91+
---------------------------
92+
93+
(1 row)
94+
8595
-- These should fail because they are not called by valid range of buffers
8696
-- Number of the shared buffers are limited by max integer
8797
SELECT 2147483647 max_buffers \gset
@@ -91,6 +101,12 @@ SELECT * FROM pg_buffercache_evict(0);
91101
ERROR: bad buffer ID: 0
92102
SELECT * FROM pg_buffercache_evict(:max_buffers);
93103
ERROR: bad buffer ID: 2147483647
104+
SELECT * FROM pg_buffercache_mark_dirty(-1);
105+
ERROR: bad buffer ID: -1
106+
SELECT * FROM pg_buffercache_mark_dirty(0);
107+
ERROR: bad buffer ID: 0
108+
SELECT * FROM pg_buffercache_mark_dirty(:max_buffers);
109+
ERROR: bad buffer ID: 2147483647
94110
-- This should fail because pg_buffercache_evict_relation() doesn't accept
95111
-- local relations
96112
CREATE TEMP TABLE temp_pg_buffercache();
@@ -118,4 +134,16 @@ SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg
118134
(1 row)
119135

120136
DROP TABLE shared_pg_buffercache;
137+
SELECT pg_buffercache_mark_dirty(1) IS NOT NULL;
138+
?column?
139+
----------
140+
t
141+
(1 row)
142+
143+
SELECT pg_buffercache_mark_dirty_all() IS NOT NULL;
144+
?column?
145+
----------
146+
t
147+
(1 row)
148+
121149
DROP ROLE regress_buffercache_normal;

contrib/pg_buffercache/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ install_data(
2424
'pg_buffercache--1.3--1.4.sql',
2525
'pg_buffercache--1.4--1.5.sql',
2626
'pg_buffercache--1.5--1.6.sql',
27+
'pg_buffercache--1.6--1.7.sql',
2728
'pg_buffercache.control',
2829
kwargs: contrib_data_args,
2930
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* contrib/pg_buffercache/pg_buffercache--1.6--1.7.sql */
2+
3+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
4+
\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.7'" to load this file. \quit
5+
6+
CREATE FUNCTION pg_buffercache_mark_dirty(IN int)
7+
RETURNS bool
8+
AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty'
9+
LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
10+
11+
CREATE FUNCTION pg_buffercache_mark_dirty_all()
12+
RETURNS INT4
13+
AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty_all'
14+
LANGUAGE C PARALLEL SAFE VOLATILE;
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pg_buffercache extension
22
comment = 'examine the shared buffer cache'
3-
default_version = '1.6'
3+
default_version = '1.7'
44
module_pathname = '$libdir/pg_buffercache'
55
relocatable = true

contrib/pg_buffercache/pg_buffercache_pages.c

+33
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
100100
PG_FUNCTION_INFO_V1(pg_buffercache_evict);
101101
PG_FUNCTION_INFO_V1(pg_buffercache_evict_relation);
102102
PG_FUNCTION_INFO_V1(pg_buffercache_evict_all);
103+
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty);
104+
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_all);
103105

104106

105107
/* Only need to touch memory once per backend process lifetime */
@@ -772,3 +774,34 @@ pg_buffercache_evict_all(PG_FUNCTION_ARGS)
772774

773775
PG_RETURN_DATUM(result);
774776
}
777+
778+
/*
779+
* Try to mark a shared buffer as dirty.
780+
*/
781+
Datum
782+
pg_buffercache_mark_dirty(PG_FUNCTION_ARGS)
783+
{
784+
Buffer buf = PG_GETARG_INT32(0);
785+
786+
pg_buffercache_superuser_check("pg_buffercache_mark_dirty");
787+
788+
if (buf < 1 || buf > NBuffers)
789+
elog(ERROR, "bad buffer ID: %d", buf);
790+
791+
PG_RETURN_BOOL(MarkUnpinnedBufferDirty(buf));
792+
}
793+
794+
/*
795+
* Try to mark all the shared buffers as dirty.
796+
*/
797+
Datum
798+
pg_buffercache_mark_dirty_all(PG_FUNCTION_ARGS)
799+
{
800+
int32 buffers_dirtied = 0;
801+
802+
pg_buffercache_superuser_check("pg_buffercache_mark_dirty_all");
803+
804+
MarkAllUnpinnedBuffersDirty(&buffers_dirtied);
805+
806+
PG_RETURN_INT32(buffers_dirtied);
807+
}

contrib/pg_buffercache/sql/pg_buffercache.sql

+9-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ RESET role;
3030

3131

3232
------
33-
---- Test pg_buffercache_evict* functions
33+
---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
3434
------
3535

3636
CREATE ROLE regress_buffercache_normal;
@@ -40,19 +40,25 @@ SET ROLE regress_buffercache_normal;
4040
SELECT * FROM pg_buffercache_evict(1);
4141
SELECT * FROM pg_buffercache_evict_relation(1);
4242
SELECT * FROM pg_buffercache_evict_all();
43+
SELECT * FROM pg_buffercache_mark_dirty(1);
44+
SELECT * FROM pg_buffercache_mark_dirty_all();
4345

4446
RESET ROLE;
4547

4648
-- These should return nothing, because these are STRICT functions
4749
SELECT * FROM pg_buffercache_evict(NULL);
4850
SELECT * FROM pg_buffercache_evict_relation(NULL);
51+
SELECT * FROM pg_buffercache_mark_dirty(NULL);
4952

5053
-- These should fail because they are not called by valid range of buffers
5154
-- Number of the shared buffers are limited by max integer
5255
SELECT 2147483647 max_buffers \gset
5356
SELECT * FROM pg_buffercache_evict(-1);
5457
SELECT * FROM pg_buffercache_evict(0);
5558
SELECT * FROM pg_buffercache_evict(:max_buffers);
59+
SELECT * FROM pg_buffercache_mark_dirty(-1);
60+
SELECT * FROM pg_buffercache_mark_dirty(0);
61+
SELECT * FROM pg_buffercache_mark_dirty(:max_buffers);
5662

5763
-- This should fail because pg_buffercache_evict_relation() doesn't accept
5864
-- local relations
@@ -66,5 +72,7 @@ SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_all();
6672
CREATE TABLE shared_pg_buffercache();
6773
SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg_buffercache');
6874
DROP TABLE shared_pg_buffercache;
75+
SELECT pg_buffercache_mark_dirty(1) IS NOT NULL;
76+
SELECT pg_buffercache_mark_dirty_all() IS NOT NULL;
6977

7078
DROP ROLE regress_buffercache_normal;

doc/src/sgml/pgbuffercache.sgml

+51-3
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,26 @@
3535
<primary>pg_buffercache_evict_all</primary>
3636
</indexterm>
3737

38+
<indexterm>
39+
<primary>pg_buffercache_mark_dirty</primary>
40+
</indexterm>
41+
42+
<indexterm>
43+
<primary>pg_buffercache_mark_dirty_all</primary>
44+
</indexterm>
45+
3846
<para>
3947
This module provides the <function>pg_buffercache_pages()</function>
4048
function (wrapped in the <structname>pg_buffercache</structname> view),
4149
<function>pg_buffercache_numa_pages()</function> function (wrapped in the
4250
<structname>pg_buffercache_numa</structname> view), the
4351
<function>pg_buffercache_summary()</function> function, the
4452
<function>pg_buffercache_usage_counts()</function> function, the
45-
<function>pg_buffercache_evict()</function>, the
46-
<function>pg_buffercache_evict_relation()</function> function and the
47-
<function>pg_buffercache_evict_all()</function> function.
53+
<function>pg_buffercache_evict()</function> function, the
54+
<function>pg_buffercache_evict_relation()</function> function, the
55+
<function>pg_buffercache_evict_all()</function> function, the
56+
<function>pg_buffercache_mark_dirty()</function> function and the
57+
<function>pg_buffercache_mark_dirty_all()</function> function.
4858
</para>
4959

5060
<para>
@@ -99,6 +109,18 @@
99109
function is restricted to superusers only.
100110
</para>
101111

112+
<para>
113+
The <function>pg_buffercache_mark_dirty()</function> function allows a block
114+
to be marked as dirty from the buffer pool given a buffer identifier. Use of
115+
this function is restricted to superusers only.
116+
</para>
117+
118+
<para>
119+
The <function>pg_buffercache_mark_dirty_all()</function> function tries to
120+
mark all buffers dirty in the buffer pool. Use of this function is
121+
restricted to superusers only.
122+
</para>
123+
102124
<sect2 id="pgbuffercache-pg-buffercache">
103125
<title>The <structname>pg_buffercache</structname> View</title>
104126

@@ -522,6 +544,32 @@
522544
</para>
523545
</sect2>
524546

547+
<sect2 id="pgbuffercache-pg-buffercache-mark-dirty">
548+
<title>The <structname>pg_buffercache_mark_dirty</structname> Function</title>
549+
<para>
550+
The <function>pg_buffercache_mark_dirty()</function> function takes a
551+
buffer identifier, as shown in the <structfield>bufferid</structfield>
552+
column of the <structname>pg_buffercache</structname> view. It returns
553+
true on success, and false if the buffer wasn't valid or if it couldn't be
554+
marked as dirty because it was pinned. The result is immediately out of
555+
date upon return, as the buffer might become valid again at any time due to
556+
concurrent activity. The function is intended for developer testing only.
557+
</para>
558+
</sect2>
559+
560+
<sect2 id="pgbuffercache-pg-buffercache-mark-dirty-all">
561+
<title>The <structname>pg_buffercache_mark_dirty_all</structname> Function</title>
562+
<para>
563+
The <function>pg_buffercache_mark_dirty_all()</function> function is very
564+
similar to the <function>pg_buffercache_mark_dirty()</function> function.
565+
The difference is, the <function>pg_buffercache_mark_dirty_all()</function>
566+
function does not take an argument; instead it tries to mark all buffers
567+
dirty in the buffer pool. The result is immediately out of date upon
568+
return, as the buffer might become valid again at any time due to
569+
concurrent activity. The function is intended for developer testing only.
570+
</para>
571+
</sect2>
572+
525573
<sect2 id="pgbuffercache-sample-output">
526574
<title>Sample Output</title>
527575

src/backend/storage/buffer/bufmgr.c

+75
Original file line numberDiff line numberDiff line change
@@ -6769,6 +6769,81 @@ EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted,
67696769
}
67706770
}
67716771

6772+
/*
6773+
* Try to mark the provided shared buffer as dirty.
6774+
*
6775+
* This function is intended for testing/development use only!
6776+
*
6777+
* Same as EvictUnpinnedBuffer() but with MarkBufferDirty() call inside.
6778+
*
6779+
* Returns true if the buffer was already dirty or it has successfully been
6780+
* marked as dirty.
6781+
*/
6782+
bool
6783+
MarkUnpinnedBufferDirty(Buffer buf)
6784+
{
6785+
BufferDesc *desc;
6786+
uint32 buf_state;
6787+
6788+
Assert(!BufferIsLocal(buf));
6789+
6790+
/* Make sure we can pin the buffer. */
6791+
ResourceOwnerEnlarge(CurrentResourceOwner);
6792+
ReservePrivateRefCountEntry();
6793+
6794+
desc = GetBufferDescriptor(buf - 1);
6795+
6796+
/* Lock the header and check if it's valid. */
6797+
buf_state = LockBufHdr(desc);
6798+
if ((buf_state & BM_VALID) == 0)
6799+
{
6800+
UnlockBufHdr(desc, buf_state);
6801+
return false;
6802+
}
6803+
6804+
/* Check that it's not pinned already. */
6805+
if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
6806+
{
6807+
UnlockBufHdr(desc, buf_state);
6808+
return false;
6809+
}
6810+
6811+
PinBuffer_Locked(desc); /* releases spinlock */
6812+
6813+
/* If it was not already dirty, mark it as dirty. */
6814+
if (!(buf_state & BM_DIRTY))
6815+
{
6816+
LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_EXCLUSIVE);
6817+
MarkBufferDirty(buf);
6818+
LWLockRelease(BufferDescriptorGetContentLock(desc));
6819+
}
6820+
6821+
UnpinBuffer(desc);
6822+
6823+
return true;
6824+
}
6825+
6826+
/*
6827+
* Try to mark all the shared buffers as dirty.
6828+
*
6829+
* This function is intended for testing/development use only! See
6830+
* MarkUnpinnedBufferDirty().
6831+
*
6832+
* The buffers_dirtied parameter is mandatory and indicate the total count of
6833+
* buffers were dirtied.
6834+
*/
6835+
void
6836+
MarkAllUnpinnedBuffersDirty(int32 *buffers_dirtied)
6837+
{
6838+
*buffers_dirtied = 0;
6839+
6840+
for (int buf = 1; buf <= NBuffers; buf++)
6841+
{
6842+
if (MarkUnpinnedBufferDirty(buf))
6843+
(*buffers_dirtied)++;
6844+
}
6845+
}
6846+
67726847
/*
67736848
* Generic implementation of the AIO handle staging callback for readv/writev
67746849
* on local/shared buffers.

src/include/storage/bufmgr.h

+2
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ extern void EvictRelUnpinnedBuffers(Relation rel,
315315
int32 *buffers_evicted,
316316
int32 *buffers_flushed,
317317
int32 *buffers_skipped);
318+
extern bool MarkUnpinnedBufferDirty(Buffer buf);
319+
extern void MarkAllUnpinnedBuffersDirty(int32 *buffers_dirtied);
318320

319321
/* in buf_init.c */
320322
extern void BufferManagerShmemInit(void);

0 commit comments

Comments
 (0)