1
+ # Copyright (c) 2024, PostgreSQL Global Development Group
2
+
3
+ # Test REINDEX CONCURRENTLY with concurrent modifications and HOT updates
4
+ use strict;
5
+ use warnings FATAL => ' all' ;
6
+
7
+ use PostgreSQL::Test::Cluster;
8
+ use PostgreSQL::Test::Utils;
9
+ use Test::More;
10
+
11
+ Test::More-> builder-> todo_start(' filesystem bug' )
12
+ if PostgreSQL::Test::Utils::has_wal_read_bug;
13
+
14
+ my ($node , $result );
15
+
16
+ #
17
+ # Test set-up
18
+ #
19
+ $node = PostgreSQL::Test::Cluster-> new(' RC_test' );
20
+ $node -> init;
21
+ $node -> append_conf(' postgresql.conf' ,
22
+ ' lock_timeout = ' . (1000 * $PostgreSQL::Test::Utils::timeout_default ));
23
+ $node -> append_conf(' postgresql.conf' , ' fsync = off' );
24
+ $node -> start;
25
+ $node -> safe_psql(' postgres' , q( CREATE EXTENSION amcheck) );
26
+ $node -> safe_psql(' postgres' , q( CREATE TABLE tbl(i int primary key,
27
+ c1 money default 0, c2 money default 0,
28
+ c3 money default 0, updated_at timestamp,
29
+ ia int4[], p point)) );
30
+ $node -> safe_psql(' postgres' , q( CREATE INDEX CONCURRENTLY idx ON tbl(i, updated_at);) );
31
+ # create sequence
32
+ $node -> safe_psql(' postgres' , q( CREATE UNLOGGED SEQUENCE in_row_rebuild START 1 INCREMENT 1;) );
33
+ $node -> safe_psql(' postgres' , q( SELECT nextval('in_row_rebuild');) );
34
+
35
+ # Create helper functions for predicate tests
36
+ $node -> safe_psql(' postgres' , q(
37
+ CREATE FUNCTION predicate_stable() RETURNS bool IMMUTABLE
38
+ LANGUAGE plpgsql AS $$
39
+ BEGIN
40
+ EXECUTE 'SELECT txid_current()';
41
+ RETURN true;
42
+ END; $$;
43
+ ) );
44
+
45
+ $node -> safe_psql(' postgres' , q(
46
+ CREATE FUNCTION predicate_const(integer) RETURNS bool IMMUTABLE
47
+ LANGUAGE plpgsql AS $$
48
+ BEGIN
49
+ RETURN MOD($1, 2) = 0;
50
+ END; $$;
51
+ ) );
52
+
53
+ # Run CIC/RIC in different options concurrently with upserts
54
+ $node -> pgbench(
55
+ ' --no-vacuum --client=30 --jobs=4 --exit-on-abort --transactions=2500' ,
56
+ 0,
57
+ [qr { actually processed} ],
58
+ [qr { ^$} ],
59
+ ' concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY' ,
60
+ {
61
+ ' concurrent_ops' => q(
62
+ SET debug_parallel_query = off; -- this is because predicate_stable implementation
63
+ SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset
64
+ \if :gotlock
65
+ SELECT nextval('in_row_rebuild') AS last_value \gset
66
+ \set variant random(0, 5)
67
+ \set parallels random(0, 4)
68
+ \if :last_value < 3
69
+ ALTER TABLE tbl SET (parallel_workers=:parallels);
70
+ \if :variant = 0
71
+ CREATE INDEX CONCURRENTLY new_idx ON tbl(i, updated_at);
72
+ \elif :variant = 1
73
+ CREATE INDEX CONCURRENTLY new_idx ON tbl(i, updated_at) WHERE predicate_stable();
74
+ \elif :variant = 2
75
+ CREATE INDEX CONCURRENTLY new_idx ON tbl(i, updated_at) WHERE MOD(i, 2) = 0;
76
+ \elif :variant = 3
77
+ CREATE INDEX CONCURRENTLY new_idx ON tbl(i, updated_at) WHERE predicate_const(i);
78
+ \elif :variant = 4
79
+ CREATE INDEX CONCURRENTLY new_idx ON tbl(predicate_const(i));
80
+ \elif :variant = 5
81
+ CREATE INDEX CONCURRENTLY new_idx ON tbl(i, predicate_const(i), updated_at) WHERE predicate_const(i);
82
+ \endif
83
+ \sleep 10 ms
84
+ SELECT bt_index_check('new_idx', heapallindexed => true, checkunique => true);
85
+ REINDEX INDEX CONCURRENTLY new_idx;
86
+ \sleep 10 ms
87
+ SELECT bt_index_check('new_idx', heapallindexed => true, checkunique => true);
88
+ DROP INDEX CONCURRENTLY new_idx;
89
+ \endif
90
+ SELECT pg_advisory_unlock(42);
91
+ \else
92
+ \set num random(1000, 100000)
93
+ BEGIN;
94
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
95
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
96
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
97
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
98
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
99
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
100
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
101
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
102
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
103
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
104
+ SELECT setval('in_row_rebuild', 1);
105
+ COMMIT;
106
+ \endif
107
+ )
108
+ });
109
+
110
+ $node -> safe_psql(' postgres' , q( TRUNCATE TABLE tbl;) );
111
+
112
+ # Run CIC/RIC for unique index concurrently with upserts
113
+ $node -> pgbench(
114
+ ' --no-vacuum --client=30 --jobs=4 --exit-on-abort --transactions=2500' ,
115
+ 0,
116
+ [qr { actually processed} ],
117
+ [qr { ^$} ],
118
+ ' concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY for unique BTREE' ,
119
+ {
120
+ ' concurrent_ops_unique_idx' => q(
121
+ SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset
122
+ \if :gotlock
123
+ SELECT nextval('in_row_rebuild') AS last_value \gset
124
+ \set parallels random(0, 4)
125
+ \if :last_value < 3
126
+ ALTER TABLE tbl SET (parallel_workers=:parallels);
127
+ CREATE UNIQUE INDEX CONCURRENTLY new_idx ON tbl(i);
128
+ \sleep 10 ms
129
+ SELECT bt_index_check('new_idx', heapallindexed => true, checkunique => true);
130
+ REINDEX INDEX CONCURRENTLY new_idx;
131
+ \sleep 10 ms
132
+ SELECT bt_index_check('new_idx', heapallindexed => true, checkunique => true);
133
+ DROP INDEX CONCURRENTLY new_idx;
134
+ \endif
135
+ SELECT pg_advisory_unlock(42);
136
+ \else
137
+ \set num random(1, power(10, random(1, 5)))
138
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
139
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
140
+ SELECT setval('in_row_rebuild', 1);
141
+ \endif
142
+ )
143
+ });
144
+
145
+ $node -> safe_psql(' postgres' , q( TRUNCATE TABLE tbl;) );
146
+
147
+ # Run CIC/RIC for GIN with upserts
148
+ $node -> pgbench(
149
+ ' --no-vacuum --client=30 --jobs=4 --exit-on-abort --transactions=2500' ,
150
+ 0,
151
+ [qr { actually processed} ],
152
+ [qr { ^$} ],
153
+ ' concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY for GIN/GIST/BRIN/HASH/SPGIST' ,
154
+ {
155
+ ' concurrent_ops_gin_idx' => q(
156
+ SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset
157
+ \if :gotlock
158
+ SELECT nextval('in_row_rebuild') AS last_value \gset
159
+ \set parallels random(0, 4)
160
+ \if :last_value < 3
161
+ ALTER TABLE tbl SET (parallel_workers=:parallels);
162
+ CREATE INDEX CONCURRENTLY new_idx ON tbl USING GIN (ia);
163
+ \sleep 10 ms
164
+ SELECT gin_index_check('new_idx');
165
+ REINDEX INDEX CONCURRENTLY new_idx;
166
+ \sleep 10 ms
167
+ SELECT gin_index_check('new_idx');
168
+ DROP INDEX CONCURRENTLY new_idx;
169
+ \endif
170
+ SELECT pg_advisory_unlock(42);
171
+ \else
172
+ \set num random(1, power(10, random(1, 5)))
173
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
174
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
175
+ SELECT setval('in_row_rebuild', 1);
176
+ \endif
177
+ )
178
+ });
179
+
180
+ $node -> safe_psql(' postgres' , q( TRUNCATE TABLE tbl;) );
181
+
182
+ # Run CIC/RIC for GIST/BRIN/HASH/SPGIST index concurrently with upserts
183
+ $node -> pgbench(
184
+ ' --no-vacuum --client=30 --jobs=4 --exit-on-abort --transactions=2500' ,
185
+ 0,
186
+ [qr { actually processed} ],
187
+ [qr { ^$} ],
188
+ ' concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY for GIN/GIST/BRIN/HASH/SPGIST' ,
189
+ {
190
+ ' concurrent_ops_other_idx' => q(
191
+ SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset
192
+ \if :gotlock
193
+ SELECT nextval('in_row_rebuild') AS last_value \gset
194
+ \set parallels random(0, 4)
195
+ \if :last_value < 3
196
+ ALTER TABLE tbl SET (parallel_workers=:parallels);
197
+ \set variant random(0, 3)
198
+ \if :variant = 0
199
+ CREATE INDEX CONCURRENTLY new_idx ON tbl USING GIST (p);
200
+ \elif :variant = 1
201
+ CREATE INDEX CONCURRENTLY new_idx ON tbl USING BRIN (updated_at);
202
+ \elif :variant = 2
203
+ CREATE INDEX CONCURRENTLY new_idx ON tbl USING HASH (updated_at);
204
+ \elif :variant = 3
205
+ CREATE INDEX CONCURRENTLY new_idx ON tbl USING SPGIST (p);
206
+ \endif
207
+ \sleep 10 ms
208
+ REINDEX INDEX CONCURRENTLY new_idx;
209
+ \sleep 10 ms
210
+ DROP INDEX CONCURRENTLY new_idx;
211
+ \endif
212
+ SELECT pg_advisory_unlock(42);
213
+ \else
214
+ \set num random(1, power(10, random(1, 5)))
215
+ INSERT INTO tbl VALUES(floor(random()*:num),0,0,0,now())
216
+ ON CONFLICT(i) DO UPDATE SET updated_at = now();
217
+ SELECT setval('in_row_rebuild', 1);
218
+ \endif
219
+ )
220
+ });
221
+
222
+ $node -> stop;
223
+ done_testing();
0 commit comments