From bb1944d194dccf01b2ab4a28fe0c73aaf1782797 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 3 Jun 2026 18:20:26 -0400 Subject: [PATCH v15 19/19] Add bgwriter write combining test --- .../modules/test_aio/t/005_write_combining.pl | 100 ++++++++++++++++++ src/test/modules/test_aio/test_aio--1.0.sql | 12 +++ src/test/modules/test_aio/test_aio.c | 54 ++++++++++ 3 files changed, 166 insertions(+) diff --git a/src/test/modules/test_aio/t/005_write_combining.pl b/src/test/modules/test_aio/t/005_write_combining.pl index c51d14691e7..3c9b72925fc 100644 --- a/src/test/modules/test_aio/t/005_write_combining.pl +++ b/src/test/modules/test_aio/t/005_write_combining.pl @@ -34,6 +34,7 @@ my $block_size = $node->safe_psql('postgres', test_checkpointer_combines_writes($node, $block_size); test_regular_backend_combines_writes($node, $block_size); test_eager_clean_combines_writes($node, $block_size); +test_bgwriter_combines_writes($node, $block_size); test_copy_from_combines_writes($node, $block_size); $node->stop(); @@ -164,6 +165,14 @@ sub dirty_blocks "SELECT make_blocks_unused_dirty_flushed('$table', ARRAY[$blocks])"); } +sub run_bgwriter_cleaner +{ + my $psql = shift; + + $psql->query_safe('SELECT run_bgwriter_cleaner(1000)'); + $psql->query_safe('SELECT pg_stat_force_next_flush()'); +} + sub allocate_until_blocks_clean { my ($node, $psql, $table, $blocks, $filler, $label) = @_; @@ -378,3 +387,94 @@ sub test_eager_clean_combines_writes $psql->quit(); } + +sub test_bgwriter_combines_writes +{ + my ($node, $block_size) = @_; + my $psql = $node->background_psql('postgres', on_error_stop => 0); + + # Keep unrelated dirty buffers out of the client-backend write statistics. + $node->safe_psql('postgres', 'CHECKPOINT'); + + # We create a relation and then flush all of its data. We can then mark + # buffers dirty for bgwriter without requiring WAL flush and thus preventing + # opportunistic write combining. + $node->safe_psql( + 'postgres', qq( + CREATE TABLE wc_bgwriter (id int, payload text); + INSERT INTO wc_bgwriter SELECT g, repeat('y', 200) FROM generate_series(1, 1000) AS g; + SELECT flush_rel_buffers('wc_bgwriter'::regclass); + )); + + #### + # Test one big combined write + #### + + # Mark blocks 0-5 dirty + dirty_blocks($psql, 'wc_bgwriter', '0,1,2,3,4,5'); + assert_blocks_dirty($node, 'wc_bgwriter', '0,1,2,3,4,5', 't', + 'contiguous buffers are dirty before bgwriter cleaner'); + + flush_and_reset_io_stats($node, $psql); + run_bgwriter_cleaner($psql); + # Should have written one big write + assert_writes($node, 'contiguous bgwriter cleaner', 'client backend', + 'normal', 1, 6 * $block_size); + # None of those blocks should still be dirty + assert_any_blocks_dirty($node, 'wc_bgwriter', '0,1,2,3,4,5', 'f', + 'bgwriter cleaner wrote contiguous dirty buffers'); + + #### + # Test multiple single block writes when interspersed blocks are not in + # shared buffers + #### + + # Evict every other block and mark the other blocks dirty + $psql->query_safe( + "SELECT invalidate_rel_blocks('wc_bgwriter', ARRAY[1,3,5])"); + dirty_blocks($psql, 'wc_bgwriter', '0,2,4'); + flush_and_reset_io_stats($node, $psql); + + run_bgwriter_cleaner($psql); + # The three blocks that were dirty should have been written out in three + # writes. + assert_writes($node, 'nonresident gaps bgwriter cleaner', 'client backend', + 'normal', 3, + 3 * $block_size); + # And none of them should be dirty anymore + assert_any_blocks_dirty($node, 'wc_bgwriter', '0,2,4', 'f', + 'bgwriter cleaner wrote dirty buffers separated by nonresident gaps'); + + #### + # Test two combined writes split around a pinned buffer + #### + + # Make sure first six blocks are all read in and marked dirty + dirty_blocks($psql, 'wc_bgwriter', '0,1,2,3,4,5'); + flush_and_reset_io_stats($node, $psql); + + # Do this in a transaction so that we can hold the buffer pin across + # multiple SQL statements by transferring ownership to the top transaction + # resource owner. + $psql->query_safe("BEGIN"); + # Pin a block in the middle of the blocks + my $pinned_buf = $psql->query_safe( + "SELECT pin_rel_block('wc_bgwriter', 3)"); + + run_bgwriter_cleaner($psql); + # All the blocks should be in clean buffers except block 3 which was pinned + # and should still be marked dirty. + assert_any_blocks_dirty($node, 'wc_bgwriter', '0,1,2,4,5', 'f', + 'bgwriter cleaner wrote buffers around pinned gap'); + assert_blocks_dirty($node, 'wc_bgwriter', '3', 't', + 'bgwriter cleaner skipped pinned buffer'); + $psql->query_safe("SELECT release_buffer($pinned_buf)"); + $psql->query_safe("COMMIT"); + $psql->query_safe('SELECT pg_stat_force_next_flush()'); + # Should have written out blocks 0,1,2 in one write and blocks 4 and 5 in + # another, totaling two writes. + assert_writes($node, 'pinned gap bgwriter cleaner', 'client backend', + 'normal', 2, 5 * $block_size); + + $psql->quit(); +} diff --git a/src/test/modules/test_aio/test_aio--1.0.sql b/src/test/modules/test_aio/test_aio--1.0.sql index d7d5006b3d5..6454328cd19 100644 --- a/src/test/modules/test_aio/test_aio--1.0.sql +++ b/src/test/modules/test_aio/test_aio--1.0.sql @@ -45,6 +45,10 @@ CREATE FUNCTION make_blocks_unused_dirty_flushed(rel regclass, blocks int4[]) RETURNS pg_catalog.void STRICT AS 'MODULE_PATHNAME' LANGUAGE C; +CREATE FUNCTION run_bgwriter_cleaner(lru_maxpages int4) +RETURNS pg_catalog.int4 STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + CREATE FUNCTION eager_clean_rel_block(rel regclass, blockno int4) RETURNS pg_catalog.void STRICT AS 'MODULE_PATHNAME' LANGUAGE C; @@ -61,6 +65,14 @@ CREATE FUNCTION invalidate_rel_blocks(rel regclass, blocks int4[]) RETURNS pg_catalog.void STRICT AS 'MODULE_PATHNAME' LANGUAGE C; +CREATE FUNCTION pin_rel_block(rel regclass, blockno int4) +RETURNS pg_catalog.int4 STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION release_buffer(buffer int4) +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + CREATE FUNCTION buffer_create_toy(rel regclass, blockno int4) RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c index b54764dcb46..67686a98eaf 100644 --- a/src/test/modules/test_aio/test_aio.c +++ b/src/test/modules/test_aio/test_aio.c @@ -709,6 +709,29 @@ make_blocks_unused_dirty_flushed(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +PG_FUNCTION_INFO_V1(run_bgwriter_cleaner); +Datum +run_bgwriter_cleaner(PG_FUNCTION_ARGS) +{ + int lru_maxpages = PG_GETARG_INT32(0); + WritebackContext wb_context; + int next_to_clean = 0; + uint32 next_passes = 0; + int num_to_scan = NBuffers; + int reusable_buffers = 0; + bool maxwritten_clean; + int num_written; + + WritebackContextInit(&wb_context, &bgwriter_flush_after); + num_written = BgBufferSyncCleanBuffers(lru_maxpages, &wb_context, + &next_to_clean, &next_passes, + &num_to_scan, &reusable_buffers, + NBuffers, &maxwritten_clean); + IssuePendingWritebacks(&wb_context, IOCONTEXT_NORMAL); + + PG_RETURN_INT32(num_written); +} + PG_FUNCTION_INFO_V1(eager_clean_rel_block); Datum eager_clean_rel_block(PG_FUNCTION_ARGS) @@ -789,6 +812,37 @@ rel_blocks_are_dirty(PG_FUNCTION_ARGS) PG_RETURN_ARRAYTYPE_P(dirty_array); } +PG_FUNCTION_INFO_V1(pin_rel_block); +Datum +pin_rel_block(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + BlockNumber blkno = PG_GETARG_UINT32(1); + Relation rel; + Buffer buf; + + rel = relation_open(relid, AccessShareLock); + buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, NULL); + ResourceOwnerForgetBuffer(CurrentResourceOwner, buf); + ResourceOwnerRememberBuffer(TopTransactionResourceOwner, buf); + relation_close(rel, NoLock); + + PG_RETURN_INT32(buf); +} + +PG_FUNCTION_INFO_V1(release_buffer); +Datum +release_buffer(PG_FUNCTION_ARGS) +{ + Buffer buf = PG_GETARG_INT32(0); + + ResourceOwnerForgetBuffer(TopTransactionResourceOwner, buf); + ResourceOwnerRememberBuffer(CurrentResourceOwner, buf); + ReleaseBuffer(buf); + + PG_RETURN_VOID(); +} + PG_FUNCTION_INFO_V1(buffer_create_toy); Datum buffer_create_toy(PG_FUNCTION_ARGS) -- 2.43.0