Skip to content

Commit e7eea52

Browse files
author
Amit Kapila
committed
Fix Logical Replication of Truncate in synchronous commit mode.
The Truncate operation acquires an exclusive lock on the target relation and indexes. It then waits for logical replication of the operation to finish at commit. Now because we are acquiring the shared lock on the target index to get index attributes in pgoutput while sending the changes for the Truncate operation, it leads to a deadlock. Actually, we don't need to acquire a lock on the target index as we build the cache entry using a historic snapshot and all the later changes are absorbed while decoding WAL. So, we wrote a special purpose function for logical replication to get a bitmap of replica identity attribute numbers where we get that information without locking the target index. We decided not to backpatch this as there doesn't seem to be any field complaint about this issue since it was introduced in commit 5dfd1e5 in v11. Reported-by: Haiying Tang Author: Takamichi Osumi, test case by Li Japin Reviewed-by: Amit Kapila, Ajin Cherian Discussion: https://postgr.es/m/OS0PR01MB6113C2499C7DC70EE55ADB82FB759@OS0PR01MB6113.jpnprd01.prod.outlook.com
1 parent 6dd1042 commit e7eea52

File tree

4 files changed

+104
-3
lines changed

4 files changed

+104
-3
lines changed

src/backend/replication/logical/proto.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,8 +668,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel)
668668
/* fetch bitmap of REPLICATION IDENTITY attributes */
669669
replidentfull = (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL);
670670
if (!replidentfull)
671-
idattrs = RelationGetIndexAttrBitmap(rel,
672-
INDEX_ATTR_BITMAP_IDENTITY_KEY);
671+
idattrs = RelationGetIdentityKeyBitmap(rel);
673672

674673
/* send the attributes */
675674
for (i = 0; i < desc->natts; i++)

src/backend/utils/cache/relcache.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5206,6 +5206,81 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
52065206
}
52075207
}
52085208

5209+
/*
5210+
* RelationGetIdentityKeyBitmap -- get a bitmap of replica identity attribute
5211+
* numbers
5212+
*
5213+
* A bitmap of index attribute numbers for the configured replica identity
5214+
* index is returned.
5215+
*
5216+
* See also comments of RelationGetIndexAttrBitmap().
5217+
*
5218+
* This is a special purpose function used during logical replication. Here,
5219+
* unlike RelationGetIndexAttrBitmap(), we don't acquire a lock on the required
5220+
* index as we build the cache entry using a historic snapshot and all the
5221+
* later changes are absorbed while decoding WAL. Due to this reason, we don't
5222+
* need to retry here in case of a change in the set of indexes.
5223+
*/
5224+
Bitmapset *
5225+
RelationGetIdentityKeyBitmap(Relation relation)
5226+
{
5227+
Bitmapset *idindexattrs = NULL; /* columns in the replica identity */
5228+
List *indexoidlist;
5229+
Relation indexDesc;
5230+
int i;
5231+
MemoryContext oldcxt;
5232+
5233+
/* Quick exit if we already computed the result */
5234+
if (relation->rd_idattr != NULL)
5235+
return bms_copy(relation->rd_idattr);
5236+
5237+
/* Fast path if definitely no indexes */
5238+
if (!RelationGetForm(relation)->relhasindex)
5239+
return NULL;
5240+
5241+
/* Historic snapshot must be set. */
5242+
Assert(HistoricSnapshotActive());
5243+
5244+
indexoidlist = RelationGetIndexList(relation);
5245+
5246+
/* Fall out if no indexes (but relhasindex was set) */
5247+
if (indexoidlist == NIL)
5248+
return NULL;
5249+
5250+
/* Add referenced attributes to idindexattrs */
5251+
indexDesc = RelationIdGetRelation(relation->rd_replidindex);
5252+
for (i = 0; i < indexDesc->rd_index->indnatts; i++)
5253+
{
5254+
int attrnum = indexDesc->rd_index->indkey.values[i];
5255+
5256+
/*
5257+
* We don't include non-key columns into idindexattrs bitmaps. See
5258+
* RelationGetIndexAttrBitmap.
5259+
*/
5260+
if (attrnum != 0)
5261+
{
5262+
if (i < indexDesc->rd_index->indnkeyatts)
5263+
idindexattrs = bms_add_member(idindexattrs,
5264+
attrnum - FirstLowInvalidHeapAttributeNumber);
5265+
}
5266+
}
5267+
5268+
RelationClose(indexDesc);
5269+
list_free(indexoidlist);
5270+
5271+
/* Don't leak the old values of these bitmaps, if any */
5272+
bms_free(relation->rd_idattr);
5273+
relation->rd_idattr = NULL;
5274+
5275+
/* Now save copy of the bitmap in the relcache entry */
5276+
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
5277+
relation->rd_idattr = bms_copy(idindexattrs);
5278+
MemoryContextSwitchTo(oldcxt);
5279+
5280+
/* We return our original working copy for caller to play with */
5281+
return idindexattrs;
5282+
}
5283+
52095284
/*
52105285
* RelationGetExclusionInfo -- get info about index's exclusion constraint
52115286
*

src/include/utils/relcache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ typedef enum IndexAttrBitmapKind
6565
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
6666
IndexAttrBitmapKind attrKind);
6767

68+
extern Bitmapset *RelationGetIdentityKeyBitmap(Relation relation);
69+
6870
extern void RelationGetExclusionInfo(Relation indexRelation,
6971
Oid **operators,
7072
Oid **procs,

src/test/subscription/t/010_truncate.pl

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use warnings;
44
use PostgresNode;
55
use TestLib;
6-
use Test::More tests => 9;
6+
use Test::More tests => 11;
77

88
# setup
99

@@ -158,3 +158,28 @@
158158
$result = $node_subscriber->safe_psql('postgres',
159159
"SELECT count(*), min(a), max(a) FROM tab2");
160160
is($result, qq(3|1|3), 'truncate of multiple tables some not published');
161+
162+
# Test that truncate works for synchronous logical replication
163+
164+
$node_publisher->safe_psql('postgres',
165+
"ALTER SYSTEM SET synchronous_standby_names TO 'sub1'");
166+
$node_publisher->safe_psql('postgres', "SELECT pg_reload_conf()");
167+
168+
# insert data to truncate
169+
170+
$node_publisher->safe_psql('postgres',
171+
"INSERT INTO tab1 VALUES (1), (2), (3)");
172+
173+
$node_publisher->wait_for_catchup('sub1');
174+
175+
$result = $node_subscriber->safe_psql('postgres',
176+
"SELECT count(*), min(a), max(a) FROM tab1");
177+
is($result, qq(3|1|3), 'check synchronous logical replication');
178+
179+
$node_publisher->safe_psql('postgres', "TRUNCATE tab1");
180+
181+
$node_publisher->wait_for_catchup('sub1');
182+
183+
$result = $node_subscriber->safe_psql('postgres',
184+
"SELECT count(*), min(a), max(a) FROM tab1");
185+
is($result, qq(0||), 'truncate replicated in synchronous logical replication');

0 commit comments

Comments
 (0)