Fix catalog lookup with the wrong snapshot during logical decoding.
Previously, we relied on HEAP2_NEW_CID records and XACT_INVALIDATION
records to know if the transaction has modified the catalog, and that
information is not serialized to snapshot. Therefore, after the restart,
if the logical decoding decodes only the commit record of the transaction
that has actually modified a catalog, we will miss adding its XID to the
snapshot. Thus, we will end up looking at catalogs with the wrong
snapshot.
To fix this problem, this changes the snapshot builder so that it
remembers the last-running-xacts list of the decoded RUNNING_XACTS record
after restoring the previously serialized snapshot. Then, we mark the
transaction as containing catalog changes if it's in the list of initial
running transactions and its commit record has XACT_XINFO_HAS_INVALS. To
avoid ABI breakage, we store the array of the initial running transactions
in the static variables InitialRunningXacts and NInitialRunningXacts,
instead of storing those in SnapBuild or ReorderBuffer.
This approach has a false positive; we could end up adding the transaction
that didn't change catalog to the snapshot since we cannot distinguish
whether the transaction has catalog changes only by checking the COMMIT
record. It doesn't have the information on which (sub) transaction has
catalog changes, and XACT_XINFO_HAS_INVALS doesn't necessarily indicate
that the transaction has catalog change. But that won't be a problem since
we use snapshot built during decoding only to read system catalogs.
On the master branch, we took a more future-proof approach by writing
catalog modifying transactions to the serialized snapshot which avoids the
above false positive. But we cannot backpatch it because of a change in
the SnapBuild.
Reported-by: Mike Oh
Author: Masahiko Sawada
Reviewed-by: Amit Kapila, Shi yu, Takamichi Osumi, Kyotaro Horiguchi, Bertrand Drouvot, Ahsan Hadi
Backpatch-through: 10
Discussion: https://postgr.es/m/81D0D8B0-E7C4-4999-B616-1E5004DBDCD2%40amazon.com
2022-08-11 06:26:49 +02:00
|
|
|
Parsed test spec with 2 sessions
|
|
|
|
|
|
|
|
starting permutation: s0_init s0_begin s0_savepoint s0_truncate s1_checkpoint s1_get_changes s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s0_commit s1_get_changes
|
|
|
|
step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
init
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_savepoint: SAVEPOINT sp1;
|
|
|
|
step s0_truncate: TRUNCATE tbl1;
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
----
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_insert: INSERT INTO tbl1 VALUES (1);
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
---------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: TRUNCATE: (no-flags)
|
|
|
|
COMMIT
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
-------------------------------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null
|
|
|
|
COMMIT
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
stop
|
|
|
|
(1 row)
|
|
|
|
|
2022-10-20 06:13:59 +02:00
|
|
|
|
|
|
|
starting permutation: s0_init s0_begin s0_savepoint s0_insert s1_checkpoint s1_get_changes s0_insert2 s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s0_commit s1_get_changes
|
|
|
|
step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
init
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_savepoint: SAVEPOINT sp1;
|
|
|
|
step s0_insert: INSERT INTO tbl1 VALUES (1);
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
----
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
step s0_insert2: INSERT INTO user_cat VALUES (1);
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_insert: INSERT INTO tbl1 VALUES (1);
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
-------------------------------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null
|
|
|
|
table public.user_cat: INSERT: val1[integer]:1
|
|
|
|
COMMIT
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
-------------------------------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null
|
|
|
|
COMMIT
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
stop
|
|
|
|
(1 row)
|
|
|
|
|
2022-10-21 06:33:35 +02:00
|
|
|
|
|
|
|
starting permutation: s0_init s0_begin s0_savepoint s0_insert s1_checkpoint s1_get_changes s0_truncate s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s0_commit s1_get_changes
|
|
|
|
step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
init
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_savepoint: SAVEPOINT sp1;
|
|
|
|
step s0_insert: INSERT INTO tbl1 VALUES (1);
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
----
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
step s0_truncate: TRUNCATE tbl1;
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_insert: INSERT INTO tbl1 VALUES (1);
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
-------------------------------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null
|
|
|
|
table public.tbl1: TRUNCATE: (no-flags)
|
|
|
|
COMMIT
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
-------------------------------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null
|
|
|
|
COMMIT
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
stop
|
|
|
|
(1 row)
|
|
|
|
|
2024-01-29 06:12:41 +01:00
|
|
|
|
|
|
|
starting permutation: s0_init s0_begin s0_savepoint s0_create_part1 s0_savepoint_release s1_checkpoint s0_create_part2 s0_commit s0_begin s0_truncate s1_checkpoint s1_get_changes s0_insert_part s1_get_changes s0_commit s1_get_changes
|
|
|
|
step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
init
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_savepoint: SAVEPOINT sp1;
|
|
|
|
step s0_create_part1: CREATE TABLE tbl1_part_p1 PARTITION OF tbl1_part FOR VALUES FROM (0) TO (10);
|
|
|
|
step s0_savepoint_release: RELEASE SAVEPOINT sp1;
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s0_create_part2: CREATE TABLE tbl1_part_p2 PARTITION OF tbl1_part FOR VALUES FROM (10) TO (20);
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s0_begin: BEGIN;
|
|
|
|
step s0_truncate: TRUNCATE tbl1;
|
|
|
|
step s1_checkpoint: CHECKPOINT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
----
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
step s0_insert_part: INSERT INTO tbl1_part VALUES (1);
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
----
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
step s0_commit: COMMIT;
|
|
|
|
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
|
|
|
|
data
|
|
|
|
--------------------------------------------------
|
|
|
|
BEGIN
|
|
|
|
table public.tbl1: TRUNCATE: (no-flags)
|
|
|
|
table public.tbl1_part_p1: INSERT: val1[integer]:1
|
|
|
|
COMMIT
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
?column?
|
|
|
|
--------
|
|
|
|
stop
|
|
|
|
(1 row)
|
|
|
|
|