Fix corruption of toast indexes with REINDEX CONCURRENTLY

REINDEX CONCURRENTLY run on a toast index or a toast relation could
corrupt the target indexes rebuilt, as a backend running in parallel
that manipulates toast values would directly release the lock on the
toast relation when its local operation is done, rather than releasing
the lock once the transaction that manipulated the toast values
committed.

The fix done here is simple: we now hold a ROW EXCLUSIVE lock on the
toast relation when saving or deleting a toast value until the
transaction working on them is committed, so as a concurrent reindex
happening in parallel would be able to wait for any activity and see any
new rows inserted (or deleted).

An isolation test is added to check after the case fixed here, which is
a bit fancy by design as it relies on allow_system_table_mods to rename
the toast table and its index to fixed names.  This way, it is possible
to reindex them directly without any dependency on the OID of the
underlying relation.  Note that this could not use a DO block either, as
REINDEX CONCURRENTLY cannot be run in a transaction block.  The test is
backpatched down to 13, where it is possible, thanks to c4a7a39, to use
allow_system_table_mods in a test suite.

Reported-by: Alexey Ermakov
Analyzed-by: Andres Freund, Noah Misch
Author: Michael Paquier
Reviewed-by: Nathan Bossart
Discussion: https://postgr.es/m/17268-d2fb426e0895abd4@postgresql.org
Backpatch-through: 12
This commit is contained in:
Michael Paquier 2021-12-08 11:01:08 +09:00
parent edc2332550
commit f99870dd86
4 changed files with 905 additions and 6 deletions

View File

@ -359,10 +359,12 @@ toast_save_datum(Relation rel, Datum value,
}
/*
* Done - close toast relation and its indexes
* Done - close toast relation and its indexes but keep the lock until
* commit, so as a concurrent reindex done directly on the toast relation
* would be able to wait for this transaction.
*/
toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
table_close(toastrel, RowExclusiveLock);
toast_close_indexes(toastidxs, num_indexes, NoLock);
table_close(toastrel, NoLock);
/*
* Create the TOAST pointer value that we'll return
@ -439,11 +441,13 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative)
}
/*
* End scan and close relations
* End scan and close relations but keep the lock until commit, so as a
* concurrent reindex done directly on the toast relation would be able to
* wait for this transaction.
*/
systable_endscan_ordered(toastscan);
toast_close_indexes(toastidxs, num_indexes, RowExclusiveLock);
table_close(toastrel, RowExclusiveLock);
toast_close_indexes(toastidxs, num_indexes, NoLock);
table_close(toastrel, NoLock);
}
/* ----------

View File

@ -0,0 +1,775 @@
Parsed test spec with 2 sessions
starting permutation: lrex1 ins1 retab2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
3|3333333333
(3 rows)
starting permutation: lrex1 ins1 reind2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
3|3333333333
(3 rows)
starting permutation: lrex1 upd1 retab2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|4444444444
2|2222222222
(2 rows)
starting permutation: lrex1 upd1 reind2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|4444444444
2|2222222222
(2 rows)
starting permutation: lrex1 del1 retab2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
(1 row)
starting permutation: lrex1 del1 reind2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
(1 row)
starting permutation: lrex1 dro1 retab2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
ERROR: relation "pg_toast.reind_con_toast" does not exist
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lrex1 dro1 reind2 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
ERROR: relation "pg_toast.reind_con_toast_idx" does not exist
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lrex1 retab2 dro1 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
step dro1: DROP TABLE reind_con_wide;
step end1: COMMIT;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lrex1 reind2 dro1 end1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
step dro1: DROP TABLE reind_con_wide;
step end1: COMMIT;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lsha1 ins1 retab2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
3|3333333333
(3 rows)
starting permutation: lsha1 ins1 reind2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
3|3333333333
(3 rows)
starting permutation: lsha1 upd1 retab2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|4444444444
2|2222222222
(2 rows)
starting permutation: lsha1 upd1 reind2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|4444444444
2|2222222222
(2 rows)
starting permutation: lsha1 del1 retab2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
(1 row)
starting permutation: lsha1 del1 reind2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
(1 row)
starting permutation: lsha1 dro1 retab2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step dro1: DROP TABLE reind_con_wide;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
ERROR: relation "pg_toast.reind_con_toast" does not exist
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lsha1 dro1 reind2 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step dro1: DROP TABLE reind_con_wide;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
ERROR: relation "pg_toast.reind_con_toast_idx" does not exist
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lsha1 retab2 dro1 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
step dro1: DROP TABLE reind_con_wide;
step end1: COMMIT;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lsha1 reind2 dro1 end1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
step dro1: DROP TABLE reind_con_wide;
step end1: COMMIT;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lexc1 ins1 retab2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
3|3333333333
(3 rows)
starting permutation: lexc1 ins1 reind2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
3|3333333333
(3 rows)
starting permutation: lexc1 upd1 retab2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|4444444444
2|2222222222
(2 rows)
starting permutation: lexc1 upd1 reind2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|4444444444
2|2222222222
(2 rows)
starting permutation: lexc1 del1 retab2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
(1 row)
starting permutation: lexc1 del1 reind2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
(1 row)
starting permutation: lexc1 dro1 retab2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step end1: COMMIT;
step retab2: <... completed>
ERROR: relation "pg_toast.reind_con_toast" does not exist
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lexc1 dro1 reind2 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step end1: COMMIT;
step reind2: <... completed>
ERROR: relation "pg_toast.reind_con_toast_idx" does not exist
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lexc1 retab2 dro1 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
step dro1: DROP TABLE reind_con_wide;
step end1: COMMIT;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lexc1 reind2 dro1 end1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
step dro1: DROP TABLE reind_con_wide;
step end1: COMMIT;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
ERROR: relation "reind_con_wide" does not exist
starting permutation: lrex1 ins1 retab2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 ins1 reind2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 upd1 retab2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 upd1 reind2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 del1 retab2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 del1 reind2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 dro1 retab2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 dro1 reind2 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 retab2 dro1 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
step dro1: DROP TABLE reind_con_wide;
step rol1: ROLLBACK;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lrex1 reind2 dro1 rol1 sel2
step lrex1: lock TABLE reind_con_wide in ROW EXCLUSIVE MODE;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
step dro1: DROP TABLE reind_con_wide;
step rol1: ROLLBACK;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 ins1 retab2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 ins1 reind2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 upd1 retab2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 upd1 reind2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 del1 retab2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 del1 reind2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 dro1 retab2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step dro1: DROP TABLE reind_con_wide;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 dro1 reind2 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step dro1: DROP TABLE reind_con_wide;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 retab2 dro1 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
step dro1: DROP TABLE reind_con_wide;
step rol1: ROLLBACK;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lsha1 reind2 dro1 rol1 sel2
step lsha1: lock TABLE reind_con_wide in SHARE MODE;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
step dro1: DROP TABLE reind_con_wide;
step rol1: ROLLBACK;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 ins1 retab2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 ins1 reind2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step ins1: INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 upd1 retab2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 upd1 reind2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step upd1: UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 del1 retab2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 del1 reind2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step del1: DELETE FROM reind_con_wide WHERE id = 2;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 dro1 retab2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; <waiting ...>
step rol1: ROLLBACK;
step retab2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 dro1 reind2 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step dro1: DROP TABLE reind_con_wide;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; <waiting ...>
step rol1: ROLLBACK;
step reind2: <... completed>
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 retab2 dro1 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step retab2: REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast;
step dro1: DROP TABLE reind_con_wide;
step rol1: ROLLBACK;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)
starting permutation: lexc1 reind2 dro1 rol1 sel2
step lexc1: lock TABLE reind_con_wide in EXCLUSIVE MODE;
step reind2: REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx;
step dro1: DROP TABLE reind_con_wide;
step rol1: ROLLBACK;
step sel2: SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id;
id| substr
--+----------
1|1111111111
2|2222222222
(2 rows)

View File

@ -53,6 +53,7 @@ test: lock-committed-update
test: lock-committed-keyupdate
test: update-locked-tuple
test: reindex-concurrently
test: reindex-concurrently-toast
test: reindex-schema
test: propagate-lock-delete
test: tuplelock-conflict

View File

@ -0,0 +1,119 @@
# REINDEX CONCURRENTLY with toast relations
#
# Ensure that concurrent operations work correctly when a REINDEX is performed
# concurrently on toast relations. Toast relation names are not deterministic,
# so this abuses of allow_system_table_mods to change the names of toast
# tables and its indexes so as they can be executed with REINDEX CONCURRENTLY,
# which cannot be launched in a transaction context.
# Create a table, with deterministic names for its toast relation and indexes.
# Fortunately ALTER TABLE is transactional, making the renaming of toast
# relations possible with allow_system_table_mods.
setup
{
CREATE TABLE reind_con_wide(id int primary key, data text);
INSERT INTO reind_con_wide
SELECT 1, repeat('1', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
INSERT INTO reind_con_wide
SELECT 2, repeat('2', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i);
SET allow_system_table_mods TO true;
DO $$DECLARE r record;
BEGIN
SELECT INTO r reltoastrelid::regclass::text AS table_name FROM pg_class
WHERE oid = 'reind_con_wide'::regclass;
EXECUTE 'ALTER TABLE ' || r.table_name || ' RENAME TO reind_con_toast;';
SELECT INTO r indexrelid::regclass::text AS index_name FROM pg_index
WHERE indrelid = (SELECT oid FROM pg_class where relname = 'reind_con_toast');
EXECUTE 'ALTER INDEX ' || r.index_name || ' RENAME TO reind_con_toast_idx;';
END$$;
}
teardown
{
DROP TABLE IF EXISTS reind_con_wide;
}
session s1
setup { BEGIN; }
step lrex1 { lock TABLE reind_con_wide in ROW EXCLUSIVE MODE; }
step lsha1 { lock TABLE reind_con_wide in SHARE MODE; }
step lexc1 { lock TABLE reind_con_wide in EXCLUSIVE MODE; }
step ins1 { INSERT INTO reind_con_wide SELECT 3, repeat('3', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i); }
step upd1 { UPDATE reind_con_wide SET data = (SELECT repeat('4', 11) || string_agg(g.i::text || random()::text, '') FROM generate_series(1, 500) g(i)) WHERE id = 1; }
step del1 { DELETE FROM reind_con_wide WHERE id = 2; }
step dro1 { DROP TABLE reind_con_wide; }
step end1 { COMMIT; }
step rol1 { ROLLBACK; }
session s2
step retab2 { REINDEX TABLE CONCURRENTLY pg_toast.reind_con_toast; }
step reind2 { REINDEX INDEX CONCURRENTLY pg_toast.reind_con_toast_idx; }
step sel2 { SELECT id, substr(data, 1, 10) FROM reind_con_wide ORDER BY id; }
# Transaction commit with ROW EXCLUSIVE MODE
permutation lrex1 ins1 retab2 end1 sel2
permutation lrex1 ins1 reind2 end1 sel2
permutation lrex1 upd1 retab2 end1 sel2
permutation lrex1 upd1 reind2 end1 sel2
permutation lrex1 del1 retab2 end1 sel2
permutation lrex1 del1 reind2 end1 sel2
permutation lrex1 dro1 retab2 end1 sel2
permutation lrex1 dro1 reind2 end1 sel2
permutation lrex1 retab2 dro1 end1 sel2
permutation lrex1 reind2 dro1 end1 sel2
# Transaction commit with SHARE MODE
permutation lsha1 ins1 retab2 end1 sel2
permutation lsha1 ins1 reind2 end1 sel2
permutation lsha1 upd1 retab2 end1 sel2
permutation lsha1 upd1 reind2 end1 sel2
permutation lsha1 del1 retab2 end1 sel2
permutation lsha1 del1 reind2 end1 sel2
permutation lsha1 dro1 retab2 end1 sel2
permutation lsha1 dro1 reind2 end1 sel2
permutation lsha1 retab2 dro1 end1 sel2
permutation lsha1 reind2 dro1 end1 sel2
# Transaction commit with EXCLUSIVE MODE
permutation lexc1 ins1 retab2 end1 sel2
permutation lexc1 ins1 reind2 end1 sel2
permutation lexc1 upd1 retab2 end1 sel2
permutation lexc1 upd1 reind2 end1 sel2
permutation lexc1 del1 retab2 end1 sel2
permutation lexc1 del1 reind2 end1 sel2
permutation lexc1 dro1 retab2 end1 sel2
permutation lexc1 dro1 reind2 end1 sel2
permutation lexc1 retab2 dro1 end1 sel2
permutation lexc1 reind2 dro1 end1 sel2
# Transaction rollback with ROW EXCLUSIVE MODE
permutation lrex1 ins1 retab2 rol1 sel2
permutation lrex1 ins1 reind2 rol1 sel2
permutation lrex1 upd1 retab2 rol1 sel2
permutation lrex1 upd1 reind2 rol1 sel2
permutation lrex1 del1 retab2 rol1 sel2
permutation lrex1 del1 reind2 rol1 sel2
permutation lrex1 dro1 retab2 rol1 sel2
permutation lrex1 dro1 reind2 rol1 sel2
permutation lrex1 retab2 dro1 rol1 sel2
permutation lrex1 reind2 dro1 rol1 sel2
# Transaction rollback with SHARE MODE
permutation lsha1 ins1 retab2 rol1 sel2
permutation lsha1 ins1 reind2 rol1 sel2
permutation lsha1 upd1 retab2 rol1 sel2
permutation lsha1 upd1 reind2 rol1 sel2
permutation lsha1 del1 retab2 rol1 sel2
permutation lsha1 del1 reind2 rol1 sel2
permutation lsha1 dro1 retab2 rol1 sel2
permutation lsha1 dro1 reind2 rol1 sel2
permutation lsha1 retab2 dro1 rol1 sel2
permutation lsha1 reind2 dro1 rol1 sel2
# Transaction rollback with EXCLUSIVE MODE
permutation lexc1 ins1 retab2 rol1 sel2
permutation lexc1 ins1 reind2 rol1 sel2
permutation lexc1 upd1 retab2 rol1 sel2
permutation lexc1 upd1 reind2 rol1 sel2
permutation lexc1 del1 retab2 rol1 sel2
permutation lexc1 del1 reind2 rol1 sel2
permutation lexc1 dro1 retab2 rol1 sel2
permutation lexc1 dro1 reind2 rol1 sel2
permutation lexc1 retab2 dro1 rol1 sel2
permutation lexc1 reind2 dro1 rol1 sel2