Skip to content

Commit 80a0a07

Browse files
soumyadeep2007Ashwin Agrawal
authored andcommitted
Fix visimap consults for unique checks during UPDATEs
This fixes #17183. When consulting the visimap during an UPDATE for the purposes of uniqueness checks, we used to refer to the visimap from the delete half of the update. This is the wrong structure to look at as this structure is not meant to be consulted while deletes are in flight (we haven't reached end-of-delete where visibility info from the visimapDelete structure flows into the catalog). Instead, we should be consulting the visimapDelete structure attached to the deleteDesc. This structure can handle visimap queries for tuples that have visimap changes that haven't yet been persisted to the catalog table. The effect of not using this structure meant running into issues such as: "attempted to update invisible tuple" when we would attempt to persist a dirty visimap entry in AppendOnlyVisimap_IsVisible() with a call to AppendOnlyVisimap_Store(). The dirty entry is one which was introduced by the delete half of the update. Our existing test did not trip this issue because the update did not need a swap-out of the current entry. With enough data, however, the issue reproduces, as evidenced in #17183. Co-authored-by: Ashwin Agrawal <aashwin@vmware.com> Reviewed-by: Haolin Wang <whaolin@vmware.com>
1 parent d755353 commit 80a0a07

File tree

8 files changed

+164
-39
lines changed

8 files changed

+164
-39
lines changed

src/backend/access/aocs/aocsam_handler.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ aoco_dml_finish(Relation relation, CmdType operation)
326326
state->uniqueCheckDesc->blockDirectory = NULL;
327327

328328
/*
329-
* If this fetch is a part of an update, then we have been reusing the
330-
* visimap used by the delete half of the update, which would have
329+
* If this fetch is a part of an UPDATE, then we have been reusing the
330+
* visimapDelete used by the delete half of the UPDATE, which would have
331331
* already been cleaned up above. Clean up otherwise.
332332
*/
333333
if (!had_delete_desc)
@@ -336,6 +336,7 @@ aoco_dml_finish(Relation relation, CmdType operation)
336336
pfree(state->uniqueCheckDesc->visimap);
337337
}
338338
state->uniqueCheckDesc->visimap = NULL;
339+
state->uniqueCheckDesc->visiMapDelete = NULL;
339340

340341
pfree(state->uniqueCheckDesc);
341342
state->uniqueCheckDesc = NULL;
@@ -499,18 +500,29 @@ get_or_create_unique_check_desc(Relation relation, Snapshot snapshot)
499500
relation,
500501
relation->rd_att->natts, /* numColGroups */
501502
snapshot);
503+
502504
/*
503-
* If this is part of an update, we need to reuse the visimap used by
504-
* the delete half of the update. This is to avoid spurious conflicts
505-
* when the key's previous and new value are identical. Using the
506-
* visimap from the delete half ensures that the visimap can recognize
507-
* any tuples deleted by us prior to this insert, within this command.
505+
* If this is part of an UPDATE, we need to reuse the visimapDelete
506+
* support structure from the delete half of the update. This is to
507+
* avoid spurious conflicts when the key's previous and new value are
508+
* identical. Using it ensures that we can recognize any tuples deleted
509+
* by us prior to this insert, within this command.
510+
*
511+
* Note: It is important that we reuse the visimapDelete structure and
512+
* not the visimap structure. This is because, when a uniqueness check
513+
* is performed as part of an UPDATE, visimap changes aren't persisted
514+
* yet (they are persisted at dml_finish() time, see
515+
* AppendOnlyVisimapDelete_Finish()). So, if we use the visimap
516+
* structure, we would not necessarily see all the changes.
508517
*/
509518
if (state->deleteDesc)
510-
uniqueCheckDesc->visimap = &state->deleteDesc->visibilityMap;
519+
{
520+
uniqueCheckDesc->visiMapDelete = &state->deleteDesc->visiMapDelete;
521+
uniqueCheckDesc->visimap = NULL;
522+
}
511523
else
512524
{
513-
/* Initialize the visimap */
525+
/* COPY/INSERT: Initialize the visimap */
514526
uniqueCheckDesc->visimap = palloc0(sizeof(AppendOnlyVisimap));
515527
AppendOnlyVisimap_Init_forUniqueCheck(uniqueCheckDesc->visimap,
516528
relation,
@@ -994,8 +1006,9 @@ aoco_index_unique_check(Relation rel,
9941006
if (TransactionIdIsValid(snapshot->xmin) || TransactionIdIsValid(snapshot->xmax))
9951007
return true;
9961008

997-
/* Now, consult the visimap */
998-
visible = AppendOnlyVisimap_UniqueCheck(uniqueCheckDesc->visimap,
1009+
/* Now, perform a visibility check against the visimap infrastructure */
1010+
visible = AppendOnlyVisimap_UniqueCheck(uniqueCheckDesc->visiMapDelete,
1011+
uniqueCheckDesc->visimap,
9991012
aoTupleId,
10001013
snapshot);
10011014

src/backend/access/appendonly/appendonly_visimap.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,36 @@ AppendOnlyVisimapDelete_WriteBackStashedEntries(AppendOnlyVisimapDelete *visiMap
828828
}
829829
}
830830

831+
/*
832+
* Checks if the given tuple id is visible according to the visimapDelete
833+
* support structure.
834+
* A positive result is a necessary but not sufficient condition for
835+
* a tuple to be visible to the user.
836+
*
837+
* Loads the entry for the tuple id before checking the bit.
838+
*/
839+
bool
840+
AppendOnlyVisimapDelete_IsVisible(AppendOnlyVisimapDelete *visiMapDelete,
841+
AOTupleId *aoTupleId)
842+
{
843+
AppendOnlyVisimap *visiMap;
844+
845+
Assert(visiMapDelete);
846+
Assert(aoTupleId);
847+
848+
elogif(Debug_appendonly_print_visimap, LOG,
849+
"Append-only visi map delete: IsVisible check "
850+
"(tupleId) = %s",
851+
AOTupleIdToString(aoTupleId));
852+
853+
visiMap = visiMapDelete->visiMap;
854+
Assert(visiMap);
855+
856+
AppendOnlyVisimapDelete_LoadTuple(visiMapDelete, aoTupleId);
857+
858+
return AppendOnlyVisimapEntry_IsVisible(&visiMap->visimapEntry, aoTupleId);
859+
}
860+
831861

832862
/*
833863
* Finishes the delete operation.
@@ -928,8 +958,8 @@ AppendOnlyVisimap_Finish_forUniquenessChecks(
928958
{
929959
AppendOnlyVisimapStore *visimapStore = &visiMap->visimapStore;
930960
/*
931-
* The snapshot was either reset to NULL in between calls or already cleaned
932-
* up (if this was part of an update command)
961+
* The snapshot was never set or reset to NULL in between calls to
962+
* AppendOnlyVisimap_UniqueCheck().
933963
*/
934964
Assert(visimapStore->snapshot == InvalidSnapshot);
935965

src/backend/access/appendonly/appendonlyam_handler.c

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ appendonly_dml_finish(Relation relation, CmdType operation)
297297

298298
/*
299299
* If this fetch is a part of an update, then we have been reusing the
300-
* visimap used by the delete half of the update, which would have
300+
* visimapDelete used by the delete half of the update, which would have
301301
* already been cleaned up above. Clean up otherwise.
302302
*/
303303
if (!had_delete_desc)
@@ -306,6 +306,7 @@ appendonly_dml_finish(Relation relation, CmdType operation)
306306
pfree(state->uniqueCheckDesc->visimap);
307307
}
308308
state->uniqueCheckDesc->visimap = NULL;
309+
state->uniqueCheckDesc->visiMapDelete = NULL;
309310

310311
pfree(state->uniqueCheckDesc);
311312
state->uniqueCheckDesc = NULL;
@@ -465,17 +466,27 @@ get_or_create_unique_check_desc(Relation relation, Snapshot snapshot)
465466
snapshot);
466467

467468
/*
468-
* If this is part of an update, we need to reuse the visimap used by
469-
* the delete half of the update. This is to avoid spurious conflicts
470-
* when the key's previous and new value are identical. Using the
471-
* visimap from the delete half ensures that the visimap can recognize
472-
* any tuples deleted by us prior to this insert, within this command.
469+
* If this is part of an UPDATE, we need to reuse the visimapDelete
470+
* support structure from the delete half of the update. This is to
471+
* avoid spurious conflicts when the key's previous and new value are
472+
* identical. Using it ensures that we can recognize any tuples deleted
473+
* by us prior to this insert, within this command.
474+
*
475+
* Note: It is important that we reuse the visimapDelete structure and
476+
* not the visimap structure. This is because, when a uniqueness check
477+
* is performed as part of an UPDATE, visimap changes aren't persisted
478+
* yet (they are persisted at dml_finish() time, see
479+
* AppendOnlyVisimapDelete_Finish()). So, if we use the visimap
480+
* structure, we would not necessarily see all the changes.
473481
*/
474482
if (state->deleteDesc)
475-
uniqueCheckDesc->visimap = &state->deleteDesc->visibilityMap;
483+
{
484+
uniqueCheckDesc->visiMapDelete = &state->deleteDesc->visiMapDelete;
485+
uniqueCheckDesc->visimap = NULL;
486+
}
476487
else
477488
{
478-
/* Initialize the visimap */
489+
/* COPY/INSERT: Initialize the visimap */
479490
uniqueCheckDesc->visimap = palloc0(sizeof(AppendOnlyVisimap));
480491
AppendOnlyVisimap_Init_forUniqueCheck(uniqueCheckDesc->visimap,
481492
relation,
@@ -761,8 +772,9 @@ appendonly_index_unique_check(Relation rel,
761772
if (TransactionIdIsValid(snapshot->xmin) || TransactionIdIsValid(snapshot->xmax))
762773
return true;
763774

764-
/* Now, consult the visimap */
765-
visible = AppendOnlyVisimap_UniqueCheck(uniqueCheckDesc->visimap,
775+
/* Now, perform a visibility check against the visimap infrastructure */
776+
visible = AppendOnlyVisimap_UniqueCheck(uniqueCheckDesc->visiMapDelete,
777+
uniqueCheckDesc->visimap,
766778
aoTupleId,
767779
snapshot);
768780

src/include/access/appendonly_visimap.h

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ void AppendOnlyVisimapDelete_Finish(
160160

161161
void
162162
AppendOnlyVisimapDelete_LoadTuple(AppendOnlyVisimapDelete *visiMapDelete,
163+
AOTupleId *aoTupleId);
164+
165+
bool
166+
AppendOnlyVisimapDelete_IsVisible(AppendOnlyVisimapDelete *visiMapDelete,
163167
AOTupleId *aoTupleId);
164168

165169
/*
@@ -168,27 +172,59 @@ AppendOnlyVisimapDelete_LoadTuple(AppendOnlyVisimapDelete *visiMapDelete,
168172
* During a uniqueness check, look up the visimap to see if a tuple was deleted
169173
* by a *committed* transaction.
170174
*
171-
* Note: We need to use the passed in per-tuple snapshot to perform the block
172-
* directory lookup. See AppendOnlyVisimap_Init_forUniqueCheck() for details on
173-
* why we can't set up the metadata snapshot at init time.
174-
* If this is part of an update, we are reusing the visimap from the delete half
175-
* of the update, so better restore its snapshot once we are done.
175+
* If this uniqueness check is part of an UPDATE, we consult the visiMapDelete
176+
* structure. Otherwise, we consult the visiMap structure. Only one of these
177+
* arguments should be supplied.
176178
*/
177-
static inline bool AppendOnlyVisimap_UniqueCheck(
178-
AppendOnlyVisimap *visiMap,
179-
AOTupleId *aoTupleId,
180-
Snapshot appendOnlyMetaDataSnapshot)
179+
static inline bool AppendOnlyVisimap_UniqueCheck(AppendOnlyVisimapDelete *visiMapDelete,
180+
AppendOnlyVisimap *visiMap,
181+
AOTupleId *aoTupleId,
182+
Snapshot appendOnlyMetaDataSnapshot)
181183
{
182-
Snapshot save_snapshot;
183-
bool visible;
184+
Snapshot save_snapshot;
185+
bool visible;
184186

185187
Assert(appendOnlyMetaDataSnapshot->snapshot_type == SNAPSHOT_DIRTY ||
186-
appendOnlyMetaDataSnapshot->snapshot_type == SNAPSHOT_SELF);
188+
appendOnlyMetaDataSnapshot->snapshot_type == SNAPSHOT_SELF);
189+
190+
if (visiMapDelete)
191+
{
192+
/* part of an UPDATE */
193+
Assert(!visiMap);
194+
Assert(visiMapDelete->visiMap);
195+
196+
/* Save the snapshot used for the delete half of the UPDATE */
197+
save_snapshot = visiMapDelete->visiMap->visimapStore.snapshot;
198+
199+
/*
200+
* Replace with per-tuple snapshot meant for uniqueness checks. See
201+
* AppendOnlyVisimap_Init_forUniqueCheck() for details on why we can't
202+
* set up the metadata snapshot at init time.
203+
*/
204+
visiMapDelete->visiMap->visimapStore.snapshot = appendOnlyMetaDataSnapshot;
205+
206+
visible = AppendOnlyVisimapDelete_IsVisible(visiMapDelete, aoTupleId);
207+
208+
/* Restore the snapshot used for the delete half of the UPDATE */
209+
visiMapDelete->visiMap->visimapStore.snapshot = save_snapshot;
210+
}
211+
else
212+
{
213+
/* part of a COPY/INSERT */
214+
Assert(visiMap);
215+
216+
/*
217+
* Set up per-tuple snapshot meant for uniqueness checks. See
218+
* AppendOnlyVisimap_Init_forUniqueCheck() for details on why we can't
219+
* set up the metadata snapshot at init time.
220+
*/
221+
visiMap->visimapStore.snapshot = appendOnlyMetaDataSnapshot;
222+
223+
visible = AppendOnlyVisimap_IsVisible(visiMap, aoTupleId);
224+
225+
visiMap->visimapStore.snapshot = NULL; /* be a good citizen */
226+
}
187227

188-
save_snapshot = visiMap->visimapStore.snapshot;
189-
visiMap->visimapStore.snapshot = appendOnlyMetaDataSnapshot;
190-
visible = AppendOnlyVisimap_IsVisible(visiMap, aoTupleId);
191-
visiMap->visimapStore.snapshot = save_snapshot;
192228
return visible;
193229
}
194230

src/include/cdb/cdbaocsam.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ typedef struct AOCSDeleteDescData *AOCSDeleteDesc;
288288
typedef struct AOCSUniqueCheckDescData
289289
{
290290
AppendOnlyBlockDirectory *blockDirectory;
291+
/* visimap to check for deleted tuples as part of INSERT/COPY */
291292
AppendOnlyVisimap *visimap;
293+
/* visimap support structure to check for deleted tuples as part of UPDATE */
294+
AppendOnlyVisimapDelete *visiMapDelete;
292295
} AOCSUniqueCheckDescData;
293296

294297
typedef struct AOCSUniqueCheckDescData *AOCSUniqueCheckDesc;

src/include/cdb/cdbappendonlyam.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,10 @@ typedef struct AppendOnlyDeleteDescData *AppendOnlyDeleteDesc;
402402
typedef struct AppendOnlyUniqueCheckDescData
403403
{
404404
AppendOnlyBlockDirectory *blockDirectory;
405+
/* visimap to check for deleted tuples as part of INSERT/COPY */
405406
AppendOnlyVisimap *visimap;
407+
/* visimap support structure to check for deleted tuples as part of UPDATE */
408+
AppendOnlyVisimapDelete *visiMapDelete;
406409
} AppendOnlyUniqueCheckDescData;
407410

408411
typedef struct AppendOnlyUniqueCheckDescData *AppendOnlyUniqueCheckDesc;

src/test/regress/input/uao_dml/uao_dml_unique_index_update.source

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ SELECT * FROM uao_unique_index_update;
9797

9898
DROP TABLE uao_unique_index_update;
9999

100+
-- Case 8_1: Update where pre-update key = post-update key
101+
-- but updates span multiple visimap rows---------------------------------------
102+
CREATE TABLE uao_unique_index_update_1 (a INT unique, b INT) DISTRIBUTED REPLICATED;
103+
INSERT INTO uao_unique_index_update_1 SELECT a, 1 FROM generate_series (1, 32768) a;
104+
UPDATE uao_unique_index_update_1 SET b = 2;
105+
-- If we were to select the contents of the visimap relation on any of the
106+
-- segments we would see 2 entries at this point, one with first_row_no = 0 and
107+
-- the other with first_row_no = 32768.
108+
UPDATE uao_unique_index_update_1 SET b = 3;
109+
SELECT DISTINCT b FROM uao_unique_index_update_1;
110+
DROP TABLE uao_unique_index_update_1;
111+
100112
-- Case 9: Updating tx inserts a key that already exists------------------------
101113
CREATE TABLE uao_unique_index_update (a INT unique);
102114
INSERT INTO uao_unique_index_update VALUES (1), (2);

src/test/regress/output/uao_dml/uao_dml_unique_index_update.source

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ SELECT * FROM uao_unique_index_update;
134134
(1 row)
135135

136136
DROP TABLE uao_unique_index_update;
137+
-- Case 8_1: Update where pre-update key = post-update key
138+
-- but updates span multiple visimap rows---------------------------------------
139+
CREATE TABLE uao_unique_index_update_1 (a INT unique, b INT) DISTRIBUTED REPLICATED;
140+
INSERT INTO uao_unique_index_update_1 SELECT a, 1 FROM generate_series (1, 32768) a;
141+
UPDATE uao_unique_index_update_1 SET b = 2;
142+
-- If we were to select the contents of the visimap relation on any of the
143+
-- segments we would see 2 entries at this point, one with first_row_no = 0 and
144+
-- the other with first_row_no = 32768.
145+
UPDATE uao_unique_index_update_1 SET b = 3;
146+
SELECT DISTINCT b FROM uao_unique_index_update_1;
147+
b
148+
---
149+
3
150+
(1 row)
151+
152+
DROP TABLE uao_unique_index_update_1;
137153
-- Case 9: Updating tx inserts a key that already exists------------------------
138154
CREATE TABLE uao_unique_index_update (a INT unique);
139155
INSERT INTO uao_unique_index_update VALUES (1), (2);

0 commit comments

Comments
 (0)