Commit f8b4a2e0 authored by Tom Lane's avatar Tom Lane

Fix tuple-chain-moving tests to handle marked-for-update tuples correctly

(they are not part of a chain).  When failing to find a parent tuple in
an update chain, emit a warning and abandon repair_frag, but do not give
an error as before.  This should eliminate the infamous 'No one parent tuple
was found' failure, which we now realize is not a can't-happen condition
but a perfectly valid database state.  Per recent pghackers discussion.
parent 0affc29e
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.233 2002/08/06 02:36:34 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.234 2002/08/13 20:14:24 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1298,7 +1298,8 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, ...@@ -1298,7 +1298,8 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
usable_free_size = 0; usable_free_size = 0;
} }
if (usable_free_size > 0 && num_vtlinks > 0) /* don't bother to save vtlinks if we will not call repair_frag */
if (fraged_pages->num_pages > 0 && num_vtlinks > 0)
{ {
qsort((char *) vtlinks, num_vtlinks, sizeof(VTupleLinkData), qsort((char *) vtlinks, num_vtlinks, sizeof(VTupleLinkData),
vac_cmp_vtlinks); vac_cmp_vtlinks);
...@@ -1322,7 +1323,6 @@ Re-using: Free/Avail. Space %.0f/%.0f; EndEmpty/Avail. Pages %u/%u.\n\t%s", ...@@ -1322,7 +1323,6 @@ Re-using: Free/Avail. Space %.0f/%.0f; EndEmpty/Avail. Pages %u/%u.\n\t%s",
free_size, usable_free_size, free_size, usable_free_size,
empty_end_pages, fraged_pages->num_pages, empty_end_pages, fraged_pages->num_pages,
vac_show_rusage(&ru0)); vac_show_rusage(&ru0));
} }
...@@ -1577,42 +1577,65 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -1577,42 +1577,65 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
* If this tuple is in the chain of tuples created in updates * If this tuple is in the chain of tuples created in updates
* by "recent" transactions then we have to move all chain of * by "recent" transactions then we have to move all chain of
* tuples to another places. * tuples to another places.
*
* NOTE: this test is not 100% accurate: it is possible for
* a tuple to be an updated one with recent xmin, and yet not
* have a corresponding tuple in the vtlinks list. Presumably
* there was once a parent tuple with xmax matching the xmin,
* but it's possible that that tuple has been removed --- for
* example, if it had xmin = xmax then HeapTupleSatisfiesVacuum
* would deem it removable as soon as the xmin xact completes.
*
* To be on the safe side, we abandon the repair_frag process if
* we cannot find the parent tuple in vtlinks. This may be overly
* conservative; AFAICS it would be safe to move the chain.
*/ */
if ((tuple.t_data->t_infomask & HEAP_UPDATED && if (((tuple.t_data->t_infomask & HEAP_UPDATED) &&
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data), !TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data),
OldestXmin)) || OldestXmin)) ||
(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) && (!(tuple.t_data->t_infomask & (HEAP_XMAX_INVALID |
HEAP_MARKED_FOR_UPDATE)) &&
!(ItemPointerEquals(&(tuple.t_self), !(ItemPointerEquals(&(tuple.t_self),
&(tuple.t_data->t_ctid))))) &(tuple.t_data->t_ctid)))))
{ {
Buffer Cbuf = buf; Buffer Cbuf = buf;
bool freeCbuf = false;
bool chain_move_failed = false;
Page Cpage; Page Cpage;
ItemId Citemid; ItemId Citemid;
ItemPointerData Ctid; ItemPointerData Ctid;
HeapTupleData tp = tuple; HeapTupleData tp = tuple;
Size tlen = tuple_len; Size tlen = tuple_len;
VTupleMove vtmove = (VTupleMove) VTupleMove vtmove;
palloc(100 * sizeof(VTupleMoveData)); int num_vtmove;
int num_vtmove = 0; int free_vtmove;
int free_vtmove = 100;
VacPage to_vacpage = NULL; VacPage to_vacpage = NULL;
int to_item = 0; int to_item = 0;
bool freeCbuf = false;
int ti; int ti;
if (vacrelstats->vtlinks == NULL)
elog(ERROR, "No one parent tuple was found");
if (cur_buffer != InvalidBuffer) if (cur_buffer != InvalidBuffer)
{ {
WriteBuffer(cur_buffer); WriteBuffer(cur_buffer);
cur_buffer = InvalidBuffer; cur_buffer = InvalidBuffer;
} }
/* Quick exit if we have no vtlinks to search in */
if (vacrelstats->vtlinks == NULL)
{
elog(WARNING, "Parent item in update-chain not found - can't continue repair_frag");
break; /* out of walk-along-page loop */
}
vtmove = (VTupleMove) palloc(100 * sizeof(VTupleMoveData));
num_vtmove = 0;
free_vtmove = 100;
/* /*
* If this tuple is in the begin/middle of the chain then * If this tuple is in the begin/middle of the chain then
* we have to move to the end of chain. * we have to move to the end of chain.
*/ */
while (!(tp.t_data->t_infomask & HEAP_XMAX_INVALID) && while (!(tp.t_data->t_infomask & (HEAP_XMAX_INVALID |
HEAP_MARKED_FOR_UPDATE)) &&
!(ItemPointerEquals(&(tp.t_self), !(ItemPointerEquals(&(tp.t_self),
&(tp.t_data->t_ctid)))) &(tp.t_data->t_ctid))))
{ {
...@@ -1636,22 +1659,35 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -1636,22 +1659,35 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
* in scan_heap(), but it's not implemented at the * in scan_heap(), but it's not implemented at the
* moment and so we just stop shrinking here. * moment and so we just stop shrinking here.
*/ */
ReleaseBuffer(Cbuf);
pfree(vtmove);
vtmove = NULL;
elog(WARNING, "Child itemid in update-chain marked as unused - can't continue repair_frag"); elog(WARNING, "Child itemid in update-chain marked as unused - can't continue repair_frag");
break; chain_move_failed = true;
break; /* out of loop to move to chain end */
} }
tp.t_datamcxt = NULL; tp.t_datamcxt = NULL;
tp.t_data = (HeapTupleHeader) PageGetItem(Cpage, Citemid); tp.t_data = (HeapTupleHeader) PageGetItem(Cpage, Citemid);
tp.t_self = Ctid; tp.t_self = Ctid;
tlen = tp.t_len = ItemIdGetLength(Citemid); tlen = tp.t_len = ItemIdGetLength(Citemid);
} }
if (vtmove == NULL) if (chain_move_failed)
break; {
/* first, can chain be moved ? */ if (freeCbuf)
ReleaseBuffer(Cbuf);
pfree(vtmove);
break; /* out of walk-along-page loop */
}
/*
* Check if all items in chain can be moved
*/
for (;;) for (;;)
{ {
Buffer Pbuf;
Page Ppage;
ItemId Pitemid;
HeapTupleData Ptp;
VTupleLinkData vtld,
*vtlp;
if (to_vacpage == NULL || if (to_vacpage == NULL ||
!enough_space(to_vacpage, tlen)) !enough_space(to_vacpage, tlen))
{ {
...@@ -1664,13 +1700,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -1664,13 +1700,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
if (i == num_fraged_pages) if (i == num_fraged_pages)
{ {
/* can't move item anywhere */ /* can't move item anywhere */
for (i = 0; i < num_vtmove; i++) chain_move_failed = true;
{ break; /* out of check-all-items loop */
Assert(vtmove[i].vacpage->offsets_used > 0);
(vtmove[i].vacpage->offsets_used)--;
}
num_vtmove = 0;
break;
} }
to_item = i; to_item = i;
to_vacpage = fraged_pages->pagedesc[to_item]; to_vacpage = fraged_pages->pagedesc[to_item];
...@@ -1682,9 +1713,10 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -1682,9 +1713,10 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
if (free_vtmove == 0) if (free_vtmove == 0)
{ {
free_vtmove = 1000; free_vtmove = 1000;
vtmove = (VTupleMove) repalloc(vtmove, vtmove = (VTupleMove)
(free_vtmove + num_vtmove) * repalloc(vtmove,
sizeof(VTupleMoveData)); (free_vtmove + num_vtmove) *
sizeof(VTupleMoveData));
} }
vtmove[num_vtmove].tid = tp.t_self; vtmove[num_vtmove].tid = tp.t_self;
vtmove[num_vtmove].vacpage = to_vacpage; vtmove[num_vtmove].vacpage = to_vacpage;
...@@ -1695,113 +1727,96 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -1695,113 +1727,96 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
free_vtmove--; free_vtmove--;
num_vtmove++; num_vtmove++;
/* All done ? */ /* At beginning of chain? */
if (!(tp.t_data->t_infomask & HEAP_UPDATED) || if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
TransactionIdPrecedes(HeapTupleHeaderGetXmin(tp.t_data), TransactionIdPrecedes(HeapTupleHeaderGetXmin(tp.t_data),
OldestXmin)) OldestXmin))
break; break;
/* Well, try to find tuple with old row version */ /* No, move to tuple with prior row version */
for (;;) vtld.new_tid = tp.t_self;
vtlp = (VTupleLink)
vac_bsearch((void *) &vtld,
(void *) (vacrelstats->vtlinks),
vacrelstats->num_vtlinks,
sizeof(VTupleLinkData),
vac_cmp_vtlinks);
if (vtlp == NULL)
{ {
Buffer Pbuf; /* see discussion above */
Page Ppage; elog(WARNING, "Parent item in update-chain not found - can't continue repair_frag");
ItemId Pitemid; chain_move_failed = true;
HeapTupleData Ptp; break; /* out of check-all-items loop */
VTupleLinkData vtld, }
*vtlp; tp.t_self = vtlp->this_tid;
Pbuf = ReadBuffer(onerel,
vtld.new_tid = tp.t_self; ItemPointerGetBlockNumber(&(tp.t_self)));
vtlp = (VTupleLink) Ppage = BufferGetPage(Pbuf);
vac_bsearch((void *) &vtld, Pitemid = PageGetItemId(Ppage,
(void *) (vacrelstats->vtlinks), ItemPointerGetOffsetNumber(&(tp.t_self)));
vacrelstats->num_vtlinks, /* this can't happen since we saw tuple earlier: */
sizeof(VTupleLinkData), if (!ItemIdIsUsed(Pitemid))
vac_cmp_vtlinks); elog(ERROR, "Parent itemid marked as unused");
if (vtlp == NULL) Ptp.t_datamcxt = NULL;
elog(ERROR, "Parent tuple was not found"); Ptp.t_data = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
tp.t_self = vtlp->this_tid;
Pbuf = ReadBuffer(onerel, /* ctid should not have changed since we saved it */
ItemPointerGetBlockNumber(&(tp.t_self))); Assert(ItemPointerEquals(&(vtld.new_tid),
Ppage = BufferGetPage(Pbuf); &(Ptp.t_data->t_ctid)));
Pitemid = PageGetItemId(Ppage,
ItemPointerGetOffsetNumber(&(tp.t_self)));
if (!ItemIdIsUsed(Pitemid))
elog(ERROR, "Parent itemid marked as unused");
Ptp.t_datamcxt = NULL;
Ptp.t_data = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
Assert(ItemPointerEquals(&(vtld.new_tid),
&(Ptp.t_data->t_ctid)));
/*
* Read above about cases when
* !ItemIdIsUsed(Citemid) (child item is
* removed)... Due to the fact that at the moment
* we don't remove unuseful part of update-chain,
* it's possible to get too old parent row here.
* Like as in the case which caused this problem,
* we stop shrinking here. I could try to find
* real parent row but want not to do it because
* of real solution will be implemented anyway,
* latter, and we are too close to 6.5 release. -
* vadim 06/11/99
*/
if (!(TransactionIdEquals(HeapTupleHeaderGetXmax(Ptp.t_data),
HeapTupleHeaderGetXmin(tp.t_data))))
{
if (freeCbuf)
ReleaseBuffer(Cbuf);
freeCbuf = false;
ReleaseBuffer(Pbuf);
for (i = 0; i < num_vtmove; i++)
{
Assert(vtmove[i].vacpage->offsets_used > 0);
(vtmove[i].vacpage->offsets_used)--;
}
num_vtmove = 0;
elog(WARNING, "Too old parent tuple found - can't continue repair_frag");
break;
}
#ifdef NOT_USED /* I'm not sure that this will wotk
* properly... */
/* /*
* If this tuple is updated version of row and it * Read above about cases when
* was created by the same transaction then no one * !ItemIdIsUsed(Citemid) (child item is
* is interested in this tuple - mark it as * removed)... Due to the fact that at the moment
* removed. * we don't remove unuseful part of update-chain,
*/ * it's possible to get too old parent row here.
if (Ptp.t_data->t_infomask & HEAP_UPDATED && * Like as in the case which caused this problem,
TransactionIdEquals(HeapTupleHeaderGetXmin(Ptp.t_data), * we stop shrinking here. I could try to find
HeapTupleHeaderGetXmax(Ptp.t_data))) * real parent row but want not to do it because
{ * of real solution will be implemented anyway,
Ptp.t_data->t_infomask &= * later, and we are too close to 6.5 release. -
~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_IN); * vadim 06/11/99
Ptp.t_data->t_infomask |= HEAP_MOVED_OFF; */
HeapTupleHeaderSetXvac(Ptp.t_data, myXID); if (!(TransactionIdEquals(HeapTupleHeaderGetXmax(Ptp.t_data),
WriteBuffer(Pbuf); HeapTupleHeaderGetXmin(tp.t_data))))
continue; {
} ReleaseBuffer(Pbuf);
#endif elog(WARNING, "Too old parent tuple found - can't continue repair_frag");
tp.t_datamcxt = Ptp.t_datamcxt; chain_move_failed = true;
tp.t_data = Ptp.t_data; break; /* out of check-all-items loop */
tlen = tp.t_len = ItemIdGetLength(Pitemid);
if (freeCbuf)
ReleaseBuffer(Cbuf);
Cbuf = Pbuf;
freeCbuf = true;
break;
} }
if (num_vtmove == 0) tp.t_datamcxt = Ptp.t_datamcxt;
break; tp.t_data = Ptp.t_data;
} tlen = tp.t_len = ItemIdGetLength(Pitemid);
if (freeCbuf)
ReleaseBuffer(Cbuf);
Cbuf = Pbuf;
freeCbuf = true;
} /* end of check-all-items loop */
if (freeCbuf) if (freeCbuf)
ReleaseBuffer(Cbuf); ReleaseBuffer(Cbuf);
if (num_vtmove == 0) /* chain can't be moved */ freeCbuf = false;
if (chain_move_failed)
{ {
/*
* Undo changes to offsets_used state. We don't bother
* cleaning up the amount-free state, since we're not
* going to do any further tuple motion.
*/
for (i = 0; i < num_vtmove; i++)
{
Assert(vtmove[i].vacpage->offsets_used > 0);
(vtmove[i].vacpage->offsets_used)--;
}
pfree(vtmove); pfree(vtmove);
break; break; /* out of walk-along-page loop */
} }
/*
* Okay, move the whle tuple chain
*/
ItemPointerSetInvalid(&Ctid); ItemPointerSetInvalid(&Ctid);
for (ti = 0; ti < num_vtmove; ti++) for (ti = 0; ti < num_vtmove; ti++)
{ {
...@@ -1962,12 +1977,15 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -1962,12 +1977,15 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
WriteBuffer(cur_buffer); WriteBuffer(cur_buffer);
WriteBuffer(Cbuf); WriteBuffer(Cbuf);
} } /* end of move-the-tuple-chain loop */
cur_buffer = InvalidBuffer; cur_buffer = InvalidBuffer;
pfree(vtmove); pfree(vtmove);
chain_tuple_moved = true; chain_tuple_moved = true;
/* advance to next tuple in walk-along-page loop */
continue; continue;
} } /* end of is-tuple-in-chain test */
/* try to find new page for this tuple */ /* try to find new page for this tuple */
if (cur_buffer == InvalidBuffer || if (cur_buffer == InvalidBuffer ||
...@@ -2089,10 +2107,19 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -2089,10 +2107,19 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
} }
} /* walk along page */ } /* walk along page */
/*
* If we broke out of the walk-along-page loop early (ie, still have
* offnum <= maxoff), then we failed to move some tuple off
* this page. No point in shrinking any more, so clean up and
* exit the per-page loop.
*/
if (offnum < maxoff && keep_tuples > 0) if (offnum < maxoff && keep_tuples > 0)
{ {
OffsetNumber off; OffsetNumber off;
/*
* Fix vacpage state for any unvisited tuples remaining on page
*/
for (off = OffsetNumberNext(offnum); for (off = OffsetNumberNext(offnum);
off <= maxoff; off <= maxoff;
off = OffsetNumberNext(off)) off = OffsetNumberNext(off))
...@@ -2154,7 +2181,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, ...@@ -2154,7 +2181,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
ReleaseBuffer(buf); ReleaseBuffer(buf);
if (offnum <= maxoff) if (offnum <= maxoff)
break; /* some item(s) left */ break; /* had to quit early, see above note */
} /* walk along relation */ } /* walk along relation */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment