Skip to content

Commit 18d355b

Browse files
ashutosh-bapatCommitfest Bot
authored andcommitted
WIP test shared buffers resizing and checkpoint
A new test triggers an injection point in the BufferSync() after it has collected buffers to flushed. Simultaneously it starts buffer shrinking. The expectation is that the checkpointer would crash accessing a buffer (descriptor) outside the new range of shared buffers. But that does not happen because of a bug in synchronization. The checkpointer does not reload configuration when checkpoint is going on. It does not load the new value of the configuration. When the resizing is triggered by the PM, checkpointer receives the proc signal barrier but it does not start it doesn't enter the barrier mechanism and doesn't alter its address maps or memory sizes. Hence the test does not crash. But of course it means that it won't consider the correct size of buffers next time it performs a checkpoint. The test was at least useful to detect this anomaly. Once we fix the synchronization issue we should see the crash and then fix the crash. Author: Ashutosh Bapat Notes to reviewers ------------------ 1. pg_buffercache used a query on pg_settings to fetch the value of the number of buffers. That doesn't work anymore because of change in the SHOW shared_buffers. Modified the test to convert the setting value to the number of shared buffers, save it in a variable and use the variable in queries which need the number of shared buffers. We could instead fix ShowGUCOption() to pass use_units flag to show_hook and let it output the number of shared buffers instead. But that seems a larger change. There aren't other GUCs whose show_hook outputs their values with units. So this local fix might be better.
1 parent b7678e1 commit 18d355b

File tree

5 files changed

+130
-24
lines changed

5 files changed

+130
-24
lines changed

contrib/pg_buffercache/expected/pg_buffercache.out

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
CREATE EXTENSION pg_buffercache;
2-
select count(*) = (select setting::bigint
3-
from pg_settings
4-
where name = 'shared_buffers')
5-
from pg_buffercache;
2+
select pg_size_bytes(setting)/(select setting::bigint from pg_settings where name = 'block_size') AS nbuffers
3+
from pg_settings
4+
where name = 'shared_buffers'
5+
\gset
6+
select count(*) = :nbuffers from pg_buffercache;
67
?column?
78
----------
89
t
@@ -24,20 +25,14 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0;
2425
(1 row)
2526

2627
-- Test the buffer lookup table function and count is <= shared_buffers
27-
select count(*) <= (select setting::bigint
28-
from pg_settings
29-
where name = 'shared_buffers')
30-
from pg_buffercache_lookup_table_entries();
28+
select count(*) <= :nbuffers from pg_buffercache_lookup_table_entries();
3129
?column?
3230
----------
3331
t
3432
(1 row)
3533

3634
-- Check that pg_buffercache_lookup_table view works and count is <= shared_buffers
37-
select count(*) <= (select setting::bigint
38-
from pg_settings
39-
where name = 'shared_buffers')
40-
from pg_buffercache_lookup_table;
35+
select count(*) <= :nbuffers from pg_buffercache_lookup_table;
4136
?column?
4237
----------
4338
t

contrib/pg_buffercache/sql/pg_buffercache.sql

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
CREATE EXTENSION pg_buffercache;
22

3-
select count(*) = (select setting::bigint
4-
from pg_settings
5-
where name = 'shared_buffers')
6-
from pg_buffercache;
3+
select pg_size_bytes(setting)/(select setting::bigint from pg_settings where name = 'block_size') AS nbuffers
4+
from pg_settings
5+
where name = 'shared_buffers'
6+
\gset
7+
select count(*) = :nbuffers from pg_buffercache;
78

89
select buffers_used + buffers_unused > 0,
910
buffers_dirty <= buffers_used,
@@ -13,16 +14,10 @@ from pg_buffercache_summary();
1314
SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0;
1415

1516
-- Test the buffer lookup table function and count is <= shared_buffers
16-
select count(*) <= (select setting::bigint
17-
from pg_settings
18-
where name = 'shared_buffers')
19-
from pg_buffercache_lookup_table_entries();
17+
select count(*) <= :nbuffers from pg_buffercache_lookup_table_entries();
2018

2119
-- Check that pg_buffercache_lookup_table view works and count is <= shared_buffers
22-
select count(*) <= (select setting::bigint
23-
from pg_settings
24-
where name = 'shared_buffers')
25-
from pg_buffercache_lookup_table;
20+
select count(*) <= :nbuffers from pg_buffercache_lookup_table;
2621

2722
-- Check that the functions / views can't be accessed by default. To avoid
2823
-- having to create a dedicated user, use the pg_database_owner pseudo-role.

src/backend/storage/buffer/bufmgr.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#include "utils/rel.h"
6868
#include "utils/resowner.h"
6969
#include "utils/timestamp.h"
70+
#include "utils/injection_point.h"
7071

7172

7273
/* Note: these two macros only work on shared buffers, not local ones! */
@@ -3416,6 +3417,9 @@ BufferSync(int flags)
34163417
ProcessProcSignalBarrier();
34173418
}
34183419

3420+
/* Injection point after scanning all buffers for dirty pages */
3421+
INJECTION_POINT("buffer-sync-dirty-buffer-scan", NULL);
3422+
34193423
if (num_to_scan == 0)
34203424
return; /* nothing to do */
34213425

src/test/buffermgr/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ tests += {
1616
},
1717
'tests': [
1818
't/001_resize_buffer.pl',
19+
't/002_checkpoint_buffer_resize.pl',
1920
't/003_parallel_resize_buffer.pl',
2021
't/004_client_join_buffer_resize.pl',
2122
],
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright (c) 2025-2025, PostgreSQL Global Development Group
2+
#
3+
# Test shared_buffer resizing coordination with checkpoint using injection points
4+
5+
use strict;
6+
use warnings;
7+
use IPC::Run;
8+
use PostgreSQL::Test::Cluster;
9+
use PostgreSQL::Test::Utils;
10+
use Test::More;
11+
12+
# Skip this test if injection points are not supported
13+
if ($ENV{enable_injection_points} ne 'yes')
14+
{
15+
plan skip_all => 'Injection points not supported by this build';
16+
}
17+
18+
# Initialize cluster with injection points enabled
19+
my $node = PostgreSQL::Test::Cluster->new('main');
20+
$node->init;
21+
$node->append_conf('postgresql.conf', 'shared_preload_libraries = injection_points');
22+
$node->append_conf('postgresql.conf', 'shared_buffers = 256kB');
23+
# Disable background writer to prevent interference with dirty buffers
24+
$node->append_conf('postgresql.conf', 'bgwriter_lru_maxpages = 0');
25+
$node->start;
26+
27+
# Load the injection points extension
28+
$node->safe_psql('postgres', "CREATE EXTENSION injection_points");
29+
30+
# Create some data to make checkpoint meaningful and ensure many dirty buffers
31+
$node->safe_psql('postgres', "CREATE TABLE test_data (id int, data text)");
32+
# Insert enough data to fill more than 16 buffers (each row ~1KB, so 20+ rows per page)
33+
$node->safe_psql('postgres', "INSERT INTO test_data SELECT i, repeat('x', 1000) FROM generate_series(1, 5000) i");
34+
35+
# Create additional tables to ensure we have plenty of dirty buffers
36+
$node->safe_psql('postgres', "CREATE TABLE test_data2 AS SELECT * FROM test_data WHERE id <= 2500");
37+
$node->safe_psql('postgres', "CREATE TABLE test_data3 AS SELECT * FROM test_data WHERE id > 2500");
38+
39+
# Update data to create more dirty buffers
40+
$node->safe_psql('postgres', "UPDATE test_data SET data = repeat('y', 1000) WHERE id % 3 = 0");
41+
$node->safe_psql('postgres', "UPDATE test_data2 SET data = repeat('z', 1000) WHERE id % 2 = 0");
42+
43+
# Prepare the new shared_buffers configuration before starting checkpoint
44+
$node->safe_psql('postgres', "ALTER SYSTEM SET shared_buffers = '128kB'");
45+
$node->safe_psql('postgres', "SELECT pg_reload_conf()");
46+
47+
# Set up the injection point to make checkpoint wait
48+
$node->safe_psql('postgres', "SELECT injection_points_attach('buffer-sync-dirty-buffer-scan', 'wait')");
49+
50+
# Start a checkpoint in the background that will trigger the injection point
51+
my $checkpoint_session = $node->background_psql('postgres');
52+
$checkpoint_session->query_until(
53+
qr/starting_checkpoint/,
54+
q(
55+
\echo starting_checkpoint
56+
CHECKPOINT;
57+
\q
58+
)
59+
);
60+
61+
# Wait until checkpointer actually reaches the injection point
62+
$node->wait_for_event('checkpointer', 'buffer-sync-dirty-buffer-scan');
63+
64+
# Verify checkpoint is waiting by checking if it hasn't completed
65+
my $checkpoint_running = $node->safe_psql('postgres',
66+
"SELECT COUNT(*) FROM pg_stat_activity WHERE backend_type = 'checkpointer' AND wait_event = 'buffer-sync-dirty-buffer-scan'");
67+
is($checkpoint_running, '1', 'Checkpoint is waiting at injection point');
68+
69+
# Start the resize operation in the background (don't wait for completion)
70+
my $resize_session = $node->background_psql('postgres');
71+
$resize_session->query_until(
72+
qr/starting_resize/,
73+
q(
74+
\echo starting_resize
75+
SELECT pg_resize_shared_buffers();
76+
)
77+
);
78+
79+
# Continue the checkpoint and wait for its completion
80+
my $log_offset = -s $node->logfile;
81+
$node->safe_psql('postgres', "SELECT injection_points_wakeup('buffer-sync-dirty-buffer-scan')");
82+
83+
# Wait for both checkpoint and resize to complete
84+
$node->wait_for_log(qr/checkpoint complete/, $log_offset);
85+
86+
# Wait for the resize operation to complete using the proper method
87+
$resize_session->query(q(\echo 'resize_complete'));
88+
89+
pass('Checkpoint and buffer resize both completed after injection point was released');
90+
91+
# Verify the resize actually worked
92+
is($node->safe_psql('postgres', "SHOW shared_buffers"), '128kB',
93+
'Buffer resize completed successfully after checkpoint coordination');
94+
95+
# Cleanup the background session
96+
$resize_session->quit;
97+
98+
# Clean up the injection point
99+
$node->safe_psql('postgres', "SELECT injection_points_detach('buffer-sync-dirty-buffer-scan')");
100+
101+
# Verify system remains stable after coordinated operations
102+
103+
# Perform a normal checkpoint to ensure everything is working
104+
$node->safe_psql('postgres', "CHECKPOINT");
105+
106+
pass('System remains stable after injection point testing');
107+
108+
# Cleanup
109+
$node->safe_psql('postgres', "DROP TABLE test_data, test_data2, test_data3");
110+
111+
done_testing();

0 commit comments

Comments
 (0)