1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-28 11:44:57 +03:00

pg_buffercache: Add pg_buffercache_mark_dirty{,_relation,_all}()

This commit introduces three new functions for marking shared buffers as
dirty by using the functions introduced in 9660906dbd:
* pg_buffercache_mark_dirty() for one shared buffer.
- pg_buffercache_mark_dirt_relation() for all the shared buffers in a
relation.
* pg_buffercache_mark_dirty_all() for all the shared buffers in pool.

The "_all" and "_relation" flavors are designed to address the
inefficiency of repeatedly calling pg_buffercache_mark_dirty() for each
individual buffer, which can be time-consuming when dealing with with
large shared buffers pool.

These functions are intended as developer tools and are available only
to superusers.  There is no need to bump the version of pg_buffercache,
4b203d499c having done this job in this release cycle.

Author: Nazir Bilal Yavuz <byavuz81@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Aidar Imamov <a.imamov@postgrespro.ru>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Joseph Koshakow <koshy44@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Yuhang Qiu <iamqyh@gmail.com>
Reviewed-by: Xuneng Zhou <xunengzhou@gmail.com>
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw@mail.gmail.com
This commit is contained in:
Michael Paquier
2025-11-28 09:04:04 +09:00
parent d167c19295
commit 9ccc049dfe
5 changed files with 296 additions and 8 deletions

View File

@@ -75,7 +75,7 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
RESET role; RESET role;
------ ------
---- Test pg_buffercache_evict* functions ---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
------ ------
CREATE ROLE regress_buffercache_normal; CREATE ROLE regress_buffercache_normal;
SET ROLE regress_buffercache_normal; SET ROLE regress_buffercache_normal;
@@ -86,6 +86,12 @@ SELECT * FROM pg_buffercache_evict_relation(1);
ERROR: must be superuser to use pg_buffercache_evict_relation() ERROR: must be superuser to use pg_buffercache_evict_relation()
SELECT * FROM pg_buffercache_evict_all(); SELECT * FROM pg_buffercache_evict_all();
ERROR: must be superuser to use 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_relation(1);
ERROR: must be superuser to use pg_buffercache_mark_dirty_relation()
SELECT * FROM pg_buffercache_mark_dirty_all();
ERROR: must be superuser to use pg_buffercache_mark_dirty_all()
RESET ROLE; RESET ROLE;
-- These should return nothing, because these are STRICT functions -- These should return nothing, because these are STRICT functions
SELECT * FROM pg_buffercache_evict(NULL); SELECT * FROM pg_buffercache_evict(NULL);
@@ -100,6 +106,18 @@ SELECT * FROM pg_buffercache_evict_relation(NULL);
| | | |
(1 row) (1 row)
SELECT * FROM pg_buffercache_mark_dirty(NULL);
buffer_dirtied | buffer_already_dirty
----------------+----------------------
|
(1 row)
SELECT * FROM pg_buffercache_mark_dirty_relation(NULL);
buffers_dirtied | buffers_already_dirty | buffers_skipped
-----------------+-----------------------+-----------------
| |
(1 row)
-- These should fail because they are not called by valid range of buffers -- These should fail because they are not called by valid range of buffers
-- Number of the shared buffers are limited by max integer -- Number of the shared buffers are limited by max integer
SELECT 2147483647 max_buffers \gset SELECT 2147483647 max_buffers \gset
@@ -109,11 +127,18 @@ SELECT * FROM pg_buffercache_evict(0);
ERROR: bad buffer ID: 0 ERROR: bad buffer ID: 0
SELECT * FROM pg_buffercache_evict(:max_buffers); SELECT * FROM pg_buffercache_evict(:max_buffers);
ERROR: bad buffer ID: 2147483647 ERROR: bad buffer ID: 2147483647
-- This should fail because pg_buffercache_evict_relation() doesn't accept SELECT * FROM pg_buffercache_mark_dirty(-1);
-- local relations ERROR: bad buffer ID: -1
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
-- These should fail because they don't accept local relations
CREATE TEMP TABLE temp_pg_buffercache(); CREATE TEMP TABLE temp_pg_buffercache();
SELECT * FROM pg_buffercache_evict_relation('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 ERROR: relation uses local buffers, pg_buffercache_evict_relation() is intended to be used for shared buffers only
SELECT * FROM pg_buffercache_mark_dirty_relation('temp_pg_buffercache');
ERROR: relation uses local buffers, pg_buffercache_mark_dirty_relation() is intended to be used for shared buffers only
DROP TABLE temp_pg_buffercache; DROP TABLE temp_pg_buffercache;
-- These shouldn't fail -- These shouldn't fail
SELECT buffer_evicted IS NOT NULL FROM pg_buffercache_evict(1); SELECT buffer_evicted IS NOT NULL FROM pg_buffercache_evict(1);
@@ -135,5 +160,23 @@ SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg
t t
(1 row) (1 row)
SELECT buffers_dirtied IS NOT NULL FROM pg_buffercache_mark_dirty_relation('shared_pg_buffercache');
?column?
----------
t
(1 row)
DROP TABLE shared_pg_buffercache; DROP TABLE shared_pg_buffercache;
SELECT pg_buffercache_mark_dirty(1) IS NOT NULL;
?column?
----------
t
(1 row)
SELECT pg_buffercache_mark_dirty_all() IS NOT NULL;
?column?
----------
t
(1 row)
DROP ROLE regress_buffercache_normal; DROP ROLE regress_buffercache_normal;

View File

@@ -31,3 +31,26 @@ REVOKE ALL ON pg_buffercache_numa FROM PUBLIC;
GRANT EXECUTE ON FUNCTION pg_buffercache_os_pages(boolean) TO pg_monitor; GRANT EXECUTE ON FUNCTION pg_buffercache_os_pages(boolean) TO pg_monitor;
GRANT SELECT ON pg_buffercache_os_pages TO pg_monitor; GRANT SELECT ON pg_buffercache_os_pages TO pg_monitor;
GRANT SELECT ON pg_buffercache_numa TO pg_monitor; GRANT SELECT ON pg_buffercache_numa TO pg_monitor;
-- Functions to mark buffers as dirty.
CREATE FUNCTION pg_buffercache_mark_dirty(
IN int,
OUT buffer_dirtied boolean,
OUT buffer_already_dirty boolean)
AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty'
LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
CREATE FUNCTION pg_buffercache_mark_dirty_relation(
IN regclass,
OUT buffers_dirtied int4,
OUT buffers_already_dirty int4,
OUT buffers_skipped int4)
AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty_relation'
LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
CREATE FUNCTION pg_buffercache_mark_dirty_all(
OUT buffers_dirtied int4,
OUT buffers_already_dirty int4,
OUT buffers_skipped int4)
AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty_all'
LANGUAGE C PARALLEL SAFE VOLATILE;

View File

@@ -25,6 +25,9 @@
#define NUM_BUFFERCACHE_EVICT_ELEM 2 #define NUM_BUFFERCACHE_EVICT_ELEM 2
#define NUM_BUFFERCACHE_EVICT_RELATION_ELEM 3 #define NUM_BUFFERCACHE_EVICT_RELATION_ELEM 3
#define NUM_BUFFERCACHE_EVICT_ALL_ELEM 3 #define NUM_BUFFERCACHE_EVICT_ALL_ELEM 3
#define NUM_BUFFERCACHE_MARK_DIRTY_ELEM 2
#define NUM_BUFFERCACHE_MARK_DIRTY_RELATION_ELEM 3
#define NUM_BUFFERCACHE_MARK_DIRTY_ALL_ELEM 3
#define NUM_BUFFERCACHE_OS_PAGES_ELEM 3 #define NUM_BUFFERCACHE_OS_PAGES_ELEM 3
@@ -101,6 +104,9 @@ PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
PG_FUNCTION_INFO_V1(pg_buffercache_evict); PG_FUNCTION_INFO_V1(pg_buffercache_evict);
PG_FUNCTION_INFO_V1(pg_buffercache_evict_relation); PG_FUNCTION_INFO_V1(pg_buffercache_evict_relation);
PG_FUNCTION_INFO_V1(pg_buffercache_evict_all); PG_FUNCTION_INFO_V1(pg_buffercache_evict_all);
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty);
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_relation);
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_all);
/* Only need to touch memory once per backend process lifetime */ /* Only need to touch memory once per backend process lifetime */
@@ -826,3 +832,119 @@ pg_buffercache_evict_all(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result); PG_RETURN_DATUM(result);
} }
/*
* Try to mark a shared buffer as dirty.
*/
Datum
pg_buffercache_mark_dirty(PG_FUNCTION_ARGS)
{
Datum result;
TupleDesc tupledesc;
HeapTuple tuple;
Datum values[NUM_BUFFERCACHE_MARK_DIRTY_ELEM];
bool nulls[NUM_BUFFERCACHE_MARK_DIRTY_ELEM] = {0};
Buffer buf = PG_GETARG_INT32(0);
bool buffer_already_dirty;
if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
pg_buffercache_superuser_check("pg_buffercache_mark_dirty");
if (buf < 1 || buf > NBuffers)
elog(ERROR, "bad buffer ID: %d", buf);
values[0] = BoolGetDatum(MarkDirtyUnpinnedBuffer(buf, &buffer_already_dirty));
values[1] = BoolGetDatum(buffer_already_dirty);
tuple = heap_form_tuple(tupledesc, values, nulls);
result = HeapTupleGetDatum(tuple);
PG_RETURN_DATUM(result);
}
/*
* Try to mark all the shared buffers of a relation as dirty.
*/
Datum
pg_buffercache_mark_dirty_relation(PG_FUNCTION_ARGS)
{
Datum result;
TupleDesc tupledesc;
HeapTuple tuple;
Datum values[NUM_BUFFERCACHE_MARK_DIRTY_RELATION_ELEM];
bool nulls[NUM_BUFFERCACHE_MARK_DIRTY_RELATION_ELEM] = {0};
Oid relOid;
Relation rel;
int32 buffers_already_dirty = 0;
int32 buffers_dirtied = 0;
int32 buffers_skipped = 0;
if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
pg_buffercache_superuser_check("pg_buffercache_mark_dirty_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_mark_dirty_relation")));
MarkDirtyRelUnpinnedBuffers(rel, &buffers_dirtied, &buffers_already_dirty,
&buffers_skipped);
relation_close(rel, AccessShareLock);
values[0] = Int32GetDatum(buffers_dirtied);
values[1] = Int32GetDatum(buffers_already_dirty);
values[2] = Int32GetDatum(buffers_skipped);
tuple = heap_form_tuple(tupledesc, values, nulls);
result = HeapTupleGetDatum(tuple);
PG_RETURN_DATUM(result);
}
/*
* Try to mark all the shared buffers as dirty.
*/
Datum
pg_buffercache_mark_dirty_all(PG_FUNCTION_ARGS)
{
Datum result;
TupleDesc tupledesc;
HeapTuple tuple;
Datum values[NUM_BUFFERCACHE_MARK_DIRTY_ALL_ELEM];
bool nulls[NUM_BUFFERCACHE_MARK_DIRTY_ALL_ELEM] = {0};
int32 buffers_already_dirty = 0;
int32 buffers_dirtied = 0;
int32 buffers_skipped = 0;
if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
pg_buffercache_superuser_check("pg_buffercache_mark_dirty_all");
MarkDirtyAllUnpinnedBuffers(&buffers_dirtied, &buffers_already_dirty,
&buffers_skipped);
values[0] = Int32GetDatum(buffers_dirtied);
values[1] = Int32GetDatum(buffers_already_dirty);
values[2] = Int32GetDatum(buffers_skipped);
tuple = heap_form_tuple(tupledesc, values, nulls);
result = HeapTupleGetDatum(tuple);
PG_RETURN_DATUM(result);
}

View File

@@ -38,7 +38,7 @@ RESET role;
------ ------
---- Test pg_buffercache_evict* functions ---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
------ ------
CREATE ROLE regress_buffercache_normal; CREATE ROLE regress_buffercache_normal;
@@ -48,12 +48,17 @@ SET ROLE regress_buffercache_normal;
SELECT * FROM pg_buffercache_evict(1); SELECT * FROM pg_buffercache_evict(1);
SELECT * FROM pg_buffercache_evict_relation(1); SELECT * FROM pg_buffercache_evict_relation(1);
SELECT * FROM pg_buffercache_evict_all(); SELECT * FROM pg_buffercache_evict_all();
SELECT * FROM pg_buffercache_mark_dirty(1);
SELECT * FROM pg_buffercache_mark_dirty_relation(1);
SELECT * FROM pg_buffercache_mark_dirty_all();
RESET ROLE; RESET ROLE;
-- These should return nothing, because these are STRICT functions -- These should return nothing, because these are STRICT functions
SELECT * FROM pg_buffercache_evict(NULL); SELECT * FROM pg_buffercache_evict(NULL);
SELECT * FROM pg_buffercache_evict_relation(NULL); SELECT * FROM pg_buffercache_evict_relation(NULL);
SELECT * FROM pg_buffercache_mark_dirty(NULL);
SELECT * FROM pg_buffercache_mark_dirty_relation(NULL);
-- These should fail because they are not called by valid range of buffers -- These should fail because they are not called by valid range of buffers
-- Number of the shared buffers are limited by max integer -- Number of the shared buffers are limited by max integer
@@ -61,11 +66,14 @@ SELECT 2147483647 max_buffers \gset
SELECT * FROM pg_buffercache_evict(-1); SELECT * FROM pg_buffercache_evict(-1);
SELECT * FROM pg_buffercache_evict(0); SELECT * FROM pg_buffercache_evict(0);
SELECT * FROM pg_buffercache_evict(:max_buffers); SELECT * FROM pg_buffercache_evict(:max_buffers);
SELECT * FROM pg_buffercache_mark_dirty(-1);
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 -- These should fail because they don't accept local relations
-- local relations
CREATE TEMP TABLE temp_pg_buffercache(); CREATE TEMP TABLE temp_pg_buffercache();
SELECT * FROM pg_buffercache_evict_relation('temp_pg_buffercache'); SELECT * FROM pg_buffercache_evict_relation('temp_pg_buffercache');
SELECT * FROM pg_buffercache_mark_dirty_relation('temp_pg_buffercache');
DROP TABLE temp_pg_buffercache; DROP TABLE temp_pg_buffercache;
-- These shouldn't fail -- These shouldn't fail
@@ -73,6 +81,9 @@ SELECT buffer_evicted IS NOT NULL FROM pg_buffercache_evict(1);
SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_all(); SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_all();
CREATE TABLE shared_pg_buffercache(); CREATE TABLE shared_pg_buffercache();
SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg_buffercache'); SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg_buffercache');
SELECT buffers_dirtied IS NOT NULL FROM pg_buffercache_mark_dirty_relation('shared_pg_buffercache');
DROP TABLE shared_pg_buffercache; DROP TABLE shared_pg_buffercache;
SELECT pg_buffercache_mark_dirty(1) IS NOT NULL;
SELECT pg_buffercache_mark_dirty_all() IS NOT NULL;
DROP ROLE regress_buffercache_normal; DROP ROLE regress_buffercache_normal;

View File

@@ -43,6 +43,18 @@
<primary>pg_buffercache_evict_all</primary> <primary>pg_buffercache_evict_all</primary>
</indexterm> </indexterm>
<indexterm>
<primary>pg_buffercache_mark_dirty</primary>
</indexterm>
<indexterm>
<primary>pg_buffercache_mark_dirty_relation</primary>
</indexterm>
<indexterm>
<primary>pg_buffercache_mark_dirty_all</primary>
</indexterm>
<para> <para>
This module provides the <function>pg_buffercache_pages()</function> This module provides the <function>pg_buffercache_pages()</function>
function (wrapped in the <structname>pg_buffercache</structname> view), the function (wrapped in the <structname>pg_buffercache</structname> view), the
@@ -52,8 +64,11 @@
<function>pg_buffercache_summary()</function> function, the <function>pg_buffercache_summary()</function> function, the
<function>pg_buffercache_usage_counts()</function> function, the <function>pg_buffercache_usage_counts()</function> function, the
<function>pg_buffercache_evict()</function> function, the <function>pg_buffercache_evict()</function> function, the
<function>pg_buffercache_evict_relation()</function> function and the <function>pg_buffercache_evict_relation()</function> function, the
<function>pg_buffercache_evict_all()</function> function. <function>pg_buffercache_evict_all()</function> function, the
<function>pg_buffercache_mark_dirty()</function> function, the
<function>pg_buffercache_mark_dirty_relation()</function> function and the
<function>pg_buffercache_mark_dirty_all()</function> function.
</para> </para>
<para> <para>
@@ -112,6 +127,25 @@
function is restricted to superusers only. function is restricted to superusers only.
</para> </para>
<para>
The <function>pg_buffercache_mark_dirty()</function> function allows a block
to be marked as dirty in the buffer pool given a buffer identifier. Use of
this function is restricted to superusers only.
</para>
<para>
The <function>pg_buffercache_mark_dirty_relation()</function> function
allows all unpinned shared buffers in the relation to be marked as dirty in
the buffer pool given a relation identifier. Use of this function is
restricted to superusers only.
</para>
<para>
The <function>pg_buffercache_mark_dirty_all()</function> function allows all
unpinned shared buffers to be marked as dirty in the buffer pool. Use of
this function is restricted to superusers only.
</para>
<sect2 id="pgbuffercache-pg-buffercache"> <sect2 id="pgbuffercache-pg-buffercache">
<title>The <structname>pg_buffercache</structname> View</title> <title>The <structname>pg_buffercache</structname> View</title>
@@ -584,6 +618,61 @@
</para> </para>
</sect2> </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
information about whether the buffer was marked as dirty.
The <structfield>buffer_dirtied</structfield> column is true on success,
and false if the buffer was already dirty if the buffer was not valid or
if it could not be marked as dirty because it was pinned.
The <structfield>buffer_already_dirty</structfield> column is true if
the buffer couldn't be marked as dirty because it was already dirty. 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-relation">
<title>The <structname>pg_buffercache_mark_dirty_relation</structname> Function</title>
<para>
The <function>pg_buffercache_mark_dirty_relation()</function> function is
very similar to the
<function>pg_buffercache_mark_dirty()</function> function.
The difference is that the
<function>pg_buffercache_mark_dirty_relation()</function> function takes a
relation identifier instead of buffer identifier. It tries to mark all
buffers dirty for all forks in that relation.
It returns the number of buffers marked as dirty, the number of buffers
already dirty and the number of buffers skipped because already pinned or
invalid.
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 that the
<function>pg_buffercache_mark_dirty_all()</function> tries to mark all
buffers dirty in the buffer pool.
It returns the number of buffers marked as dirty, the number of buffers
already dirty and the number of buffers skipped because already pinned or
invalid.
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"> <sect2 id="pgbuffercache-sample-output">
<title>Sample Output</title> <title>Sample Output</title>