Skip to content

Commit 2f2baf9

Browse files
committed
Fix aboriginal mistake in lazy VACUUM's code for truncating away
no-longer-needed pages at the end of a table. We thought we could throw away pages containing HEAPTUPLE_DEAD tuples; but this is not so, because such tuples very likely have index entries pointing at them, and we wouldn't have removed the index entries. The problem only emerges in a somewhat unlikely race condition: the dead tuples have to have been inserted by a transaction that later aborted, and this has to have happened between VACUUM's initial scan of the page and then rechecking it for empty in count_nondeletable_pages. But that timespan will include an index-cleaning pass, so it's not all that hard to hit. This seems to explain a couple of previously unsolved bug reports.
1 parent a93840e commit 2f2baf9

File tree

1 file changed

+12
-56
lines changed

1 file changed

+12
-56
lines changed

src/backend/commands/vacuumlazy.c

+12-56
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
*
3333
* IDENTIFICATION
34-
* $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.20.2.1 2005/05/07 21:33:47 tgl Exp $
34+
* $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.20.2.2 2007/09/16 02:38:31 tgl Exp $
3535
*
3636
*-------------------------------------------------------------------------
3737
*/
@@ -780,15 +780,14 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats)
780780
}
781781

782782
/*
783-
* Rescan end pages to verify that they are (still) empty of needed tuples.
783+
* Rescan end pages to verify that they are (still) empty of tuples.
784784
*
785785
* Returns number of nondeletable pages (last nonempty page + 1).
786786
*/
787787
static BlockNumber
788788
count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
789789
{
790790
BlockNumber blkno;
791-
HeapTupleData tuple;
792791

793792
/* Strange coding of loop control is needed because blkno is unsigned */
794793
blkno = vacrelstats->rel_pages;
@@ -798,9 +797,7 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
798797
Page page;
799798
OffsetNumber offnum,
800799
maxoff;
801-
bool pgchanged,
802-
tupgone,
803-
hastup;
800+
bool hastup;
804801

805802
CHECK_FOR_INTERRUPTS();
806803

@@ -815,78 +812,37 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
815812

816813
if (PageIsNew(page) || PageIsEmpty(page))
817814
{
818-
/* PageIsNew robably shouldn't happen... */
815+
/* PageIsNew probably shouldn't happen... */
819816
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
820817
ReleaseBuffer(buf);
821818
continue;
822819
}
823820

824-
pgchanged = false;
825821
hastup = false;
826822
maxoff = PageGetMaxOffsetNumber(page);
827823
for (offnum = FirstOffsetNumber;
828824
offnum <= maxoff;
829825
offnum = OffsetNumberNext(offnum))
830826
{
831827
ItemId itemid;
832-
uint16 sv_infomask;
833828

834829
itemid = PageGetItemId(page, offnum);
835830

836-
if (!ItemIdIsUsed(itemid))
837-
continue;
838-
839-
tuple.t_datamcxt = NULL;
840-
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
841-
tuple.t_len = ItemIdGetLength(itemid);
842-
ItemPointerSet(&(tuple.t_self), blkno, offnum);
843-
844-
tupgone = false;
845-
sv_infomask = tuple.t_data->t_infomask;
846-
847-
switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
848-
{
849-
case HEAPTUPLE_DEAD:
850-
tupgone = true; /* we can delete the tuple */
851-
break;
852-
case HEAPTUPLE_LIVE:
853-
/* Shouldn't be necessary to re-freeze anything */
854-
break;
855-
case HEAPTUPLE_RECENTLY_DEAD:
856-
857-
/*
858-
* If tuple is recently deleted then we must not
859-
* remove it from relation.
860-
*/
861-
break;
862-
case HEAPTUPLE_INSERT_IN_PROGRESS:
863-
/* This is an expected case during concurrent vacuum */
864-
break;
865-
case HEAPTUPLE_DELETE_IN_PROGRESS:
866-
/* This is an expected case during concurrent vacuum */
867-
break;
868-
default:
869-
elog(ERROR, "Unexpected HeapTupleSatisfiesVacuum result");
870-
break;
871-
}
872-
873-
/* check for hint-bit update by HeapTupleSatisfiesVacuum */
874-
if (sv_infomask != tuple.t_data->t_infomask)
875-
pgchanged = true;
876-
877-
if (!tupgone)
831+
/*
832+
* Note: any non-unused item should be taken as a reason to keep
833+
* this page. We formerly thought that DEAD tuples could be
834+
* thrown away, but that's not so, because we'd not have cleaned
835+
* out their index entries.
836+
*/
837+
if (ItemIdIsUsed(itemid))
878838
{
879839
hastup = true;
880840
break; /* can stop scanning */
881841
}
882842
} /* scan along page */
883843

884844
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
885-
886-
if (pgchanged)
887-
WriteBuffer(buf);
888-
else
889-
ReleaseBuffer(buf);
845+
ReleaseBuffer(buf);
890846

891847
/* Done scanning if we found a tuple here */
892848
if (hastup)

0 commit comments

Comments
 (0)