From 40fb634b1a7be5cf27e0d6c391a6f8f0eb4de412 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Tue, 16 Nov 2021 09:25:04 +0530 Subject: [PATCH] Invalidate relcache when changing REPLICA IDENTITY index. When changing REPLICA IDENTITY INDEX to another one, the target table's relcache was not being invalidated. This leads to skipping update/delete operations during apply on the subscriber side as the columns required to search corresponding rows won't get logged. Author: Tang Haiying, Hou Zhijie Reviewed-by: Euler Taveira, Amit Kapila Backpatch-through: 10 Discussion: https://postgr.es/m/OS0PR01MB61133CA11630DAE45BC6AD95FB939@OS0PR01MB6113.jpnprd01.prod.outlook.com --- src/backend/commands/tablecmds.c | 6 ++ src/test/subscription/t/100_bugs.pl | 86 ++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 62de9ba5eb..49835f2814 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -13398,6 +13398,12 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid, CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple); InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0, InvalidOid, is_internal); + /* + * Invalidate the relcache for the table, so that after we commit + * all sessions will refresh the table's replica identity index + * before attempting any UPDATE or DELETE on the table. + */ + CacheInvalidateRelcache(rel); } heap_freetuple(pg_index_tuple); } diff --git a/src/test/subscription/t/100_bugs.pl b/src/test/subscription/t/100_bugs.pl index 9ef42c368f..bd04fa5f01 100644 --- a/src/test/subscription/t/100_bugs.pl +++ b/src/test/subscription/t/100_bugs.pl @@ -3,7 +3,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 3; +use Test::More tests => 5; # Bug #15114 @@ -98,3 +98,87 @@ is( $node_publisher->psql( ); $node_publisher->stop('fast'); + +# https://postgr.es/m/OS0PR01MB61133CA11630DAE45BC6AD95FB939%40OS0PR01MB6113.jpnprd01.prod.outlook.com + +# The bug was that when changing the REPLICA IDENTITY INDEX to another one, the +# target table's relcache was not being invalidated. This leads to skipping +# UPDATE/DELETE operations during apply on the subscriber side as the columns +# required to search corresponding rows won't get logged. +$node_publisher = get_new_node('publisher3'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->start; + +$node_subscriber = get_new_node('subscriber3'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +$node_publisher->safe_psql('postgres', + "CREATE TABLE tab_replidentity_index(a int not null, b int not null)"); +$node_publisher->safe_psql('postgres', + "CREATE UNIQUE INDEX idx_replidentity_index_a ON tab_replidentity_index(a)" +); +$node_publisher->safe_psql('postgres', + "CREATE UNIQUE INDEX idx_replidentity_index_b ON tab_replidentity_index(b)" +); + +# use index idx_replidentity_index_a as REPLICA IDENTITY on publisher. +$node_publisher->safe_psql('postgres', + "ALTER TABLE tab_replidentity_index REPLICA IDENTITY USING INDEX idx_replidentity_index_a" +); + +$node_publisher->safe_psql('postgres', + "INSERT INTO tab_replidentity_index VALUES(1, 1),(2, 2)"); + +$node_subscriber->safe_psql('postgres', + "CREATE TABLE tab_replidentity_index(a int not null, b int not null)"); +$node_subscriber->safe_psql('postgres', + "CREATE UNIQUE INDEX idx_replidentity_index_a ON tab_replidentity_index(a)" +); +$node_subscriber->safe_psql('postgres', + "CREATE UNIQUE INDEX idx_replidentity_index_b ON tab_replidentity_index(b)" +); +# use index idx_replidentity_index_b as REPLICA IDENTITY on subscriber because +# it reflects the future scenario we are testing: changing REPLICA IDENTITY +# INDEX. +$node_subscriber->safe_psql('postgres', + "ALTER TABLE tab_replidentity_index REPLICA IDENTITY USING INDEX idx_replidentity_index_b" +); + +$publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', + "CREATE PUBLICATION tap_pub FOR TABLE tab_replidentity_index"); +$node_subscriber->safe_psql('postgres', + "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=tap_sub' PUBLICATION tap_pub" +); + +$node_publisher->wait_for_catchup('tap_sub'); + +# Also wait for initial table sync to finish +my $synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +is( $node_subscriber->safe_psql( + 'postgres', "SELECT * FROM tab_replidentity_index"), + qq(1|1 +2|2), + "check initial data on subscriber"); + +# Set REPLICA IDENTITY to idx_replidentity_index_b on publisher, then run UPDATE and DELETE. +$node_publisher->safe_psql( + 'postgres', qq[ + ALTER TABLE tab_replidentity_index REPLICA IDENTITY USING INDEX idx_replidentity_index_b; + UPDATE tab_replidentity_index SET a = -a WHERE a = 1; + DELETE FROM tab_replidentity_index WHERE a = 2; +]); + +$node_publisher->wait_for_catchup('tap_sub'); +is( $node_subscriber->safe_psql( + 'postgres', "SELECT * FROM tab_replidentity_index"), + qq(-1|1), + "update works with REPLICA IDENTITY"); + +$node_publisher->stop('fast'); +$node_subscriber->stop('fast');