1# Minimal test testing synchronous replication sync_state transition
2use strict;
3use warnings;
4use PostgresNode;
5use TestLib;
6use Test::More tests => 11;
7
8# Query checking sync_priority and sync_state of each standby
9my $check_sql =
10"SELECT application_name, sync_priority, sync_state FROM pg_stat_replication ORDER BY application_name;";
11
12# Check that sync_state of each standby is expected (waiting till it is).
13# If $setting is given, synchronous_standby_names is set to it and
14# the configuration file is reloaded before the test.
15sub test_sync_state
16{
17	my ($self, $expected, $msg, $setting) = @_;
18
19	if (defined($setting))
20	{
21		$self->psql('postgres',
22			"ALTER SYSTEM SET synchronous_standby_names = '$setting';");
23		$self->reload;
24	}
25
26	ok($self->poll_query_until('postgres', $check_sql, $expected), $msg);
27}
28
29# Start a standby and check that it is registered within the WAL sender
30# array of the given primary.  This polls the primary's pg_stat_replication
31# until the standby is confirmed as registered.
32sub start_standby_and_wait
33{
34	my ($master, $standby) = @_;
35	my $master_name  = $master->name;
36	my $standby_name = $standby->name;
37	my $query =
38	  "SELECT count(1) = 1 FROM pg_stat_replication WHERE application_name = '$standby_name'";
39
40	$standby->start;
41
42	print("### Waiting for standby \"$standby_name\" on \"$master_name\"\n");
43	$master->poll_query_until('postgres', $query);
44	return;
45}
46
47# Initialize master node
48my $node_master = get_new_node('master');
49$node_master->init(allows_streaming => 1);
50$node_master->start;
51my $backup_name = 'master_backup';
52
53# Take backup
54$node_master->backup($backup_name);
55
56# Create all the standbys.  Their status on the primary is checked to ensure
57# the ordering of each one of them in the WAL sender array of the primary.
58
59# Create standby1 linking to master
60my $node_standby_1 = get_new_node('standby1');
61$node_standby_1->init_from_backup($node_master, $backup_name,
62	has_streaming => 1);
63start_standby_and_wait($node_master, $node_standby_1);
64
65# Create standby2 linking to master
66my $node_standby_2 = get_new_node('standby2');
67$node_standby_2->init_from_backup($node_master, $backup_name,
68	has_streaming => 1);
69start_standby_and_wait($node_master, $node_standby_2);
70
71# Create standby3 linking to master
72my $node_standby_3 = get_new_node('standby3');
73$node_standby_3->init_from_backup($node_master, $backup_name,
74	has_streaming => 1);
75start_standby_and_wait($node_master, $node_standby_3);
76
77# Check that sync_state is determined correctly when
78# synchronous_standby_names is specified in old syntax.
79test_sync_state(
80	$node_master, qq(standby1|1|sync
81standby2|2|potential
82standby3|0|async),
83	'old syntax of synchronous_standby_names',
84	'standby1,standby2');
85
86# Check that all the standbys are considered as either sync or
87# potential when * is specified in synchronous_standby_names.
88# Note that standby1 is chosen as sync standby because
89# it's stored in the head of WalSnd array which manages
90# all the standbys though they have the same priority.
91test_sync_state(
92	$node_master, qq(standby1|1|sync
93standby2|1|potential
94standby3|1|potential),
95	'asterisk in synchronous_standby_names',
96	'*');
97
98# Stop and start standbys to rearrange the order of standbys
99# in WalSnd array. Now, if standbys have the same priority,
100# standby2 is selected preferentially and standby3 is next.
101$node_standby_1->stop;
102$node_standby_2->stop;
103$node_standby_3->stop;
104
105# Make sure that each standby reports back to the primary in the wanted
106# order.
107start_standby_and_wait($node_master, $node_standby_2);
108start_standby_and_wait($node_master, $node_standby_3);
109
110# Specify 2 as the number of sync standbys.
111# Check that two standbys are in 'sync' state.
112test_sync_state(
113	$node_master, qq(standby2|2|sync
114standby3|3|sync),
115	'2 synchronous standbys',
116	'2(standby1,standby2,standby3)');
117
118# Start standby1
119start_standby_and_wait($node_master, $node_standby_1);
120
121# Create standby4 linking to master
122my $node_standby_4 = get_new_node('standby4');
123$node_standby_4->init_from_backup($node_master, $backup_name,
124	has_streaming => 1);
125$node_standby_4->start;
126
127# Check that standby1 and standby2 whose names appear earlier in
128# synchronous_standby_names are considered as sync. Also check that
129# standby3 appearing later represents potential, and standby4 is
130# in 'async' state because it's not in the list.
131test_sync_state(
132	$node_master, qq(standby1|1|sync
133standby2|2|sync
134standby3|3|potential
135standby4|0|async),
136	'2 sync, 1 potential, and 1 async');
137
138# Check that sync_state of each standby is determined correctly
139# when num_sync exceeds the number of names of potential sync standbys
140# specified in synchronous_standby_names.
141test_sync_state(
142	$node_master, qq(standby1|0|async
143standby2|4|sync
144standby3|3|sync
145standby4|1|sync),
146	'num_sync exceeds the num of potential sync standbys',
147	'6(standby4,standby0,standby3,standby2)');
148
149# The setting that * comes before another standby name is acceptable
150# but does not make sense in most cases. Check that sync_state is
151# chosen properly even in case of that setting. standby1 is selected
152# as synchronous as it has the highest priority, and is followed by a
153# second standby listed first in the WAL sender array, which is
154# standby2 in this case.
155test_sync_state(
156	$node_master, qq(standby1|1|sync
157standby2|2|sync
158standby3|2|potential
159standby4|2|potential),
160	'asterisk before another standby name',
161	'2(standby1,*,standby2)');
162
163# Check that the setting of '2(*)' chooses standby2 and standby3 that are stored
164# earlier in WalSnd array as sync standbys.
165test_sync_state(
166	$node_master, qq(standby1|1|potential
167standby2|1|sync
168standby3|1|sync
169standby4|1|potential),
170	'multiple standbys having the same priority are chosen as sync',
171	'2(*)');
172
173# Stop Standby3 which is considered in 'sync' state.
174$node_standby_3->stop;
175
176# Check that the state of standby1 stored earlier in WalSnd array than
177# standby4 is transited from potential to sync.
178test_sync_state(
179	$node_master, qq(standby1|1|sync
180standby2|1|sync
181standby4|1|potential),
182	'potential standby found earlier in array is promoted to sync');
183
184# Check that standby1 and standby2 are chosen as sync standbys
185# based on their priorities.
186test_sync_state(
187	$node_master, qq(standby1|1|sync
188standby2|2|sync
189standby4|0|async),
190	'priority-based sync replication specified by FIRST keyword',
191	'FIRST 2(standby1, standby2)');
192
193# Check that all the listed standbys are considered as candidates
194# for sync standbys in a quorum-based sync replication.
195test_sync_state(
196	$node_master, qq(standby1|1|quorum
197standby2|1|quorum
198standby4|0|async),
199	'2 quorum and 1 async',
200	'ANY 2(standby1, standby2)');
201
202# Start Standby3 which will be considered in 'quorum' state.
203$node_standby_3->start;
204
205# Check that the setting of 'ANY 2(*)' chooses all standbys as
206# candidates for quorum sync standbys.
207test_sync_state(
208	$node_master, qq(standby1|1|quorum
209standby2|1|quorum
210standby3|1|quorum
211standby4|1|quorum),
212	'all standbys are considered as candidates for quorum sync standbys',
213	'ANY 2(*)');
214