Skip to content

Commit ce349cf

Browse files
author
Amit Kapila
committed
WAL log unchanged toasted replica identity key attributes.
Currently, during UPDATE, the unchanged replica identity key attributes are not logged separately because they are getting logged as part of the new tuple. But if they are stored externally then the untoasted values are not getting logged as part of the new tuple and logical replication won't be able to replicate such UPDATEs. So we need to log such attributes as part of the old_key_tuple during UPDATE. Reported-by: Haiying Tang Author: Dilip Kumar and Amit Kapila Reviewed-by: Alvaro Herrera, Haiying Tang, Andres Freund Backpatch-through: 10 Discussion: https://postgr.es/m/OS0PR01MB611342D0A92D4F4BF26C0F47FB229@OS0PR01MB6113.jpnprd01.prod.outlook.com
1 parent 7a12a9e commit ce349cf

File tree

3 files changed

+123
-69
lines changed

3 files changed

+123
-69
lines changed

contrib/test_decoding/expected/toast.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot',
7777
table public.toasted_key: INSERT: id[integer]:1 toasted_key[text]:'1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
7878
COMMIT
7979
BEGIN
80-
table public.toasted_key: UPDATE: id[integer]:1 toasted_key[text]:unchanged-toast-datum toasted_col1[text]:unchanged-toast-datum toasted_col2[text]:'987654321098765432109876543210987654321098765432109
80+
table public.toasted_key: UPDATE: old-key: toasted_key[text]:'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
8181
COMMIT
8282
BEGIN
8383
table public.toasted_key: UPDATE: old-key: toasted_key[text]:'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678

doc/src/sgml/ref/alter_table.sgml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -798,10 +798,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
798798
<listitem>
799799
<para>
800800
This form changes the information which is written to the write-ahead log
801-
to identify rows which are updated or deleted. This option has no effect
802-
except when logical replication is in use.
803-
In all cases, no old values are logged unless at least one of the columns
804-
that would be logged differs between the old and new versions of the row.
801+
to identify rows which are updated or deleted.
802+
In most cases, the old value of each column is only logged if it differs
803+
from the new value; however, if the old value is stored externally, it is
804+
always logged regardless of whether it changed.
805+
This option has no effect except when logical replication is in use.
805806
<variablelist>
806807
<varlistentry>
807808
<term><literal>DEFAULT</literal></term>

src/backend/access/heap/heapam.c

Lines changed: 117 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
7676
Buffer newbuf, HeapTuple oldtup,
7777
HeapTuple newtup, HeapTuple old_key_tup,
7878
bool all_visible_cleared, bool new_all_visible_cleared);
79-
static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
80-
Bitmapset *interesting_cols,
81-
HeapTuple oldtup, HeapTuple newtup);
79+
static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
80+
Bitmapset *interesting_cols,
81+
Bitmapset *external_cols,
82+
HeapTuple oldtup, HeapTuple newtup,
83+
bool *has_external);
8284
static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
8385
LockTupleMode mode, LockWaitPolicy wait_policy,
8486
bool *have_tuple_lock);
@@ -102,7 +104,7 @@ static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
102104
static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
103105
uint16 infomask, Relation rel, int *remaining);
104106
static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
105-
static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
107+
static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required,
106108
bool *copy);
107109

108110

@@ -2941,6 +2943,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
29412943
bool all_visible_cleared_new = false;
29422944
bool checked_lockers;
29432945
bool locker_remains;
2946+
bool id_has_external = false;
29442947
TransactionId xmax_new_tuple,
29452948
xmax_old_tuple;
29462949
uint16 infomask_old_tuple,
@@ -3025,7 +3028,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
30253028
Assert(ItemIdIsNormal(lp));
30263029

30273030
/*
3028-
* Fill in enough data in oldtup for HeapDetermineModifiedColumns to work
3031+
* Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
30293032
* properly.
30303033
*/
30313034
oldtup.t_tableOid = RelationGetRelid(relation);
@@ -3036,9 +3039,17 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
30363039
/* the new tuple is ready, except for this: */
30373040
newtup->t_tableOid = RelationGetRelid(relation);
30383041

3039-
/* Determine columns modified by the update. */
3040-
modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
3041-
&oldtup, newtup);
3042+
/*
3043+
* Determine columns modified by the update. Additionally, identify
3044+
* whether any of the unmodified replica identity key attributes in the
3045+
* old tuple is externally stored or not. This is required because for
3046+
* such attributes the flattened value won't be WAL logged as part of the
3047+
* new tuple so we must include it as part of the old_key_tuple. See
3048+
* ExtractReplicaIdentity.
3049+
*/
3050+
modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
3051+
id_attrs, &oldtup,
3052+
newtup, &id_has_external);
30423053

30433054
/*
30443055
* If we're not updating any "key" column, we can grab a weaker lock type.
@@ -3639,10 +3650,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
36393650
* Compute replica identity tuple before entering the critical section so
36403651
* we don't PANIC upon a memory allocation failure.
36413652
* ExtractReplicaIdentity() will return NULL if nothing needs to be
3642-
* logged.
3653+
* logged. Pass old key required as true only if the replica identity key
3654+
* columns are modified or it has external data.
36433655
*/
36443656
old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
3645-
bms_overlap(modified_attrs, id_attrs),
3657+
bms_overlap(modified_attrs, id_attrs) ||
3658+
id_has_external,
36463659
&old_key_copied);
36473660

36483661
/* NO EREPORT(ERROR) from here till changes are logged */
@@ -3798,47 +3811,15 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
37983811
}
37993812

38003813
/*
3801-
* Check if the specified attribute's value is same in both given tuples.
3802-
* Subroutine for HeapDetermineModifiedColumns.
3814+
* Check if the specified attribute's values are the same. Subroutine for
3815+
* HeapDetermineColumnsInfo.
38033816
*/
38043817
static bool
3805-
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
3806-
HeapTuple tup1, HeapTuple tup2)
3818+
heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
3819+
bool isnull1, bool isnull2)
38073820
{
3808-
Datum value1,
3809-
value2;
3810-
bool isnull1,
3811-
isnull2;
38123821
Form_pg_attribute att;
38133822

3814-
/*
3815-
* If it's a whole-tuple reference, say "not equal". It's not really
3816-
* worth supporting this case, since it could only succeed after a no-op
3817-
* update, which is hardly a case worth optimizing for.
3818-
*/
3819-
if (attrnum == 0)
3820-
return false;
3821-
3822-
/*
3823-
* Likewise, automatically say "not equal" for any system attribute other
3824-
* than tableOID; we cannot expect these to be consistent in a HOT chain,
3825-
* or even to be set correctly yet in the new tuple.
3826-
*/
3827-
if (attrnum < 0)
3828-
{
3829-
if (attrnum != TableOidAttributeNumber)
3830-
return false;
3831-
}
3832-
3833-
/*
3834-
* Extract the corresponding values. XXX this is pretty inefficient if
3835-
* there are many indexed columns. Should HeapDetermineModifiedColumns do
3836-
* a single heap_deform_tuple call on each tuple, instead? But that
3837-
* doesn't work for system columns ...
3838-
*/
3839-
value1 = heap_getattr(tup1, attrnum, tupdesc, &isnull1);
3840-
value2 = heap_getattr(tup2, attrnum, tupdesc, &isnull2);
3841-
38423823
/*
38433824
* If one value is NULL and other is not, then they are certainly not
38443825
* equal
@@ -3880,24 +3861,96 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
38803861
* Given an updated tuple, determine (and return into the output bitmapset),
38813862
* from those listed as interesting, the set of columns that changed.
38823863
*
3883-
* The input bitmapset is destructively modified; that is OK since this is
3884-
* invoked at most once in heap_update.
3864+
* has_external indicates if any of the unmodified attributes (from those
3865+
* listed as interesting) of the old tuple is a member of external_cols and is
3866+
* stored externally.
3867+
*
3868+
* The input interesting_cols bitmapset is destructively modified; that is OK
3869+
* since this is invoked at most once in heap_update.
38853870
*/
38863871
static Bitmapset *
3887-
HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols,
3888-
HeapTuple oldtup, HeapTuple newtup)
3872+
HeapDetermineColumnsInfo(Relation relation,
3873+
Bitmapset *interesting_cols,
3874+
Bitmapset *external_cols,
3875+
HeapTuple oldtup, HeapTuple newtup,
3876+
bool *has_external)
38893877
{
3890-
int attnum;
3878+
int attrnum;
38913879
Bitmapset *modified = NULL;
3880+
TupleDesc tupdesc = RelationGetDescr(relation);
38923881

3893-
while ((attnum = bms_first_member(interesting_cols)) >= 0)
3882+
while ((attrnum = bms_first_member(interesting_cols)) >= 0)
38943883
{
3895-
attnum += FirstLowInvalidHeapAttributeNumber;
3884+
Datum value1,
3885+
value2;
3886+
bool isnull1,
3887+
isnull2;
3888+
3889+
attrnum += FirstLowInvalidHeapAttributeNumber;
38963890

3897-
if (!heap_tuple_attr_equals(RelationGetDescr(relation),
3898-
attnum, oldtup, newtup))
3891+
/*
3892+
* If it's a whole-tuple reference, say "not equal". It's not really
3893+
* worth supporting this case, since it could only succeed after a
3894+
* no-op update, which is hardly a case worth optimizing for.
3895+
*/
3896+
if (attrnum == 0)
3897+
{
38993898
modified = bms_add_member(modified,
3900-
attnum - FirstLowInvalidHeapAttributeNumber);
3899+
attrnum -
3900+
FirstLowInvalidHeapAttributeNumber);
3901+
continue;
3902+
}
3903+
3904+
/*
3905+
* Likewise, automatically say "not equal" for any system attribute
3906+
* other than tableOID; we cannot expect these to be consistent in a
3907+
* HOT chain, or even to be set correctly yet in the new tuple.
3908+
*/
3909+
if (attrnum < 0)
3910+
{
3911+
if (attrnum != TableOidAttributeNumber)
3912+
{
3913+
modified = bms_add_member(modified,
3914+
attrnum -
3915+
FirstLowInvalidHeapAttributeNumber);
3916+
continue;
3917+
}
3918+
}
3919+
3920+
/*
3921+
* Extract the corresponding values. XXX this is pretty inefficient
3922+
* if there are many indexed columns. Should we do a single
3923+
* heap_deform_tuple call on each tuple, instead? But that doesn't
3924+
* work for system columns ...
3925+
*/
3926+
value1 = heap_getattr(oldtup, attrnum, tupdesc, &isnull1);
3927+
value2 = heap_getattr(newtup, attrnum, tupdesc, &isnull2);
3928+
3929+
if (!heap_attr_equals(tupdesc, attrnum, value1,
3930+
value2, isnull1, isnull2))
3931+
{
3932+
modified = bms_add_member(modified,
3933+
attrnum -
3934+
FirstLowInvalidHeapAttributeNumber);
3935+
continue;
3936+
}
3937+
3938+
/*
3939+
* No need to check attributes that can't be stored externally. Note
3940+
* that system attributes can't be stored externally.
3941+
*/
3942+
if (attrnum < 0 || isnull1 ||
3943+
TupleDescAttr(tupdesc, attrnum - 1)->attlen != -1)
3944+
continue;
3945+
3946+
/*
3947+
* Check if the old tuple's attribute is stored externally and is a
3948+
* member of external_cols.
3949+
*/
3950+
if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value1)) &&
3951+
bms_is_member(attrnum - FirstLowInvalidHeapAttributeNumber,
3952+
external_cols))
3953+
*has_external = true;
39013954
}
39023955

39033956
return modified;
@@ -7661,14 +7714,14 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
76617714
* Returns NULL if there's no need to log an identity or if there's no suitable
76627715
* key defined.
76637716
*
7664-
* key_changed should be false if caller knows that no replica identity
7665-
* columns changed value. It's always true in the DELETE case.
7717+
* Pass key_required true if any replica identity columns changed value, or if
7718+
* any of them have any external data. Delete must always pass true.
76667719
*
76677720
* *copy is set to true if the returned tuple is a modified copy rather than
76687721
* the same tuple that was passed in.
76697722
*/
76707723
static HeapTuple
7671-
ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed,
7724+
ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
76727725
bool *copy)
76737726
{
76747727
TupleDesc desc = RelationGetDescr(relation);
@@ -7700,19 +7753,19 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed,
77007753
return tp;
77017754
}
77027755

7703-
/* if the key hasn't changed and we're only logging the key, we're done */
7704-
if (!key_changed)
7756+
/* if the key isn't required and we're only logging the key, we're done */
7757+
if (!key_required)
77057758
return NULL;
77067759

77077760
/* find out the replica identity columns */
77087761
idattrs = RelationGetIndexAttrBitmap(relation,
77097762
INDEX_ATTR_BITMAP_IDENTITY_KEY);
77107763

77117764
/*
7712-
* If there's no defined replica identity columns, treat as !key_changed.
7765+
* If there's no defined replica identity columns, treat as !key_required.
77137766
* (This case should not be reachable from heap_update, since that should
7714-
* calculate key_changed accurately. But heap_delete just passes constant
7715-
* true for key_changed, so we can hit this case in deletes.)
7767+
* calculate key_required accurately. But heap_delete just passes
7768+
* constant true for key_required, so we can hit this case in deletes.)
77167769
*/
77177770
if (bms_is_empty(idattrs))
77187771
return NULL;

0 commit comments

Comments
 (0)