|
13 | 13 | * |
14 | 14 | * |
15 | 15 | * IDENTIFICATION |
16 | | - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.296 2004/12/01 19:00:41 tgl Exp $ |
| 16 | + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.297 2004/12/02 19:28:49 tgl Exp $ |
17 | 17 | * |
18 | 18 | *------------------------------------------------------------------------- |
19 | 19 | */ |
@@ -265,6 +265,27 @@ vacuum(VacuumStmt *vacstmt) |
265 | 265 | else |
266 | 266 | in_outer_xact=IsInTransactionChain((void*)vacstmt); |
267 | 267 |
|
| 268 | +/* |
| 269 | + * Disallow the combination VACUUM FULL FREEZE; although it would mostly |
| 270 | + * work, VACUUM FULL's ability to move tuples around means that it is |
| 271 | + * injecting its own XID into tuple visibility checks. We'd have to |
| 272 | + * guarantee that every moved tuple is properly marked XMIN_COMMITTED or |
| 273 | + * XMIN_INVALID before the end of the operation. There are corner cases |
| 274 | + * where this does not happen, and getting rid of them all seems hard |
| 275 | + * (not to mention fragile to maintain). On the whole it's not worth it |
| 276 | + * compared to telling people to use two operations. See pgsql-hackers |
| 277 | + * discussion of 27-Nov-2004, and comments below for update_hint_bits(). |
| 278 | + * |
| 279 | + * Note: this is enforced here, and not in the grammar, since (a) we can |
| 280 | + * give a better error message, and (b) we might want to allow it again |
| 281 | + * someday. |
| 282 | + */ |
| 283 | +if (vacstmt->vacuum&&vacstmt->full&&vacstmt->freeze) |
| 284 | +ereport(ERROR, |
| 285 | +(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 286 | +errmsg("VACUUM FULL FREEZE is not supported"), |
| 287 | +errhint("Use VACUUM FULL, then VACUUM FREEZE."))); |
| 288 | + |
268 | 289 | /* |
269 | 290 | * Send info about dead objects to the statistics collector |
270 | 291 | */ |
@@ -1346,8 +1367,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, |
1346 | 1367 | do_shrinking= false; |
1347 | 1368 | break; |
1348 | 1369 | default: |
1349 | | -/* unexpected HeapTupleSatisfiesVacuum result */ |
1350 | | -Assert(false); |
| 1370 | +elog(ERROR,"unexpected HeapTupleSatisfiesVacuum result"); |
1351 | 1371 | break; |
1352 | 1372 | } |
1353 | 1373 |
|
@@ -1530,9 +1550,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, |
1530 | 1550 | VacPageListvacuum_pages,VacPageListfraged_pages, |
1531 | 1551 | intnindexes,Relation*Irel) |
1532 | 1552 | { |
1533 | | -#ifdefUSE_ASSERT_CHECKING |
1534 | 1553 | TransactionIdmyXID=GetCurrentTransactionId(); |
1535 | | -#endif |
1536 | 1554 | Bufferdst_buffer=InvalidBuffer; |
1537 | 1555 | BlockNumbernblocks, |
1538 | 1556 | blkno; |
@@ -1702,36 +1720,30 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, |
1702 | 1720 | */ |
1703 | 1721 | if (!(tuple.t_data->t_infomask&HEAP_XMIN_COMMITTED)) |
1704 | 1722 | { |
| 1723 | +if (tuple.t_data->t_infomask&HEAP_MOVED_IN) |
| 1724 | +elog(ERROR,"HEAP_MOVED_IN was not expected"); |
| 1725 | +if (!(tuple.t_data->t_infomask&HEAP_MOVED_OFF)) |
| 1726 | +elog(ERROR,"HEAP_MOVED_OFF was expected"); |
| 1727 | + |
1705 | 1728 | /* |
1706 | | - * There cannot be another concurrently running VACUUM. If |
1707 | | - * the tuple had been moved in by a previous VACUUM, the |
1708 | | - * visibility check would have set XMIN_COMMITTED.If the |
1709 | | - * tuple had been moved in by the currently running |
1710 | | - * VACUUM, the loop would have been terminated. We had |
1711 | | - * elog(ERROR, ...) here, but as we are testing for a |
1712 | | - * can't-happen condition, Assert() seems more |
1713 | | - * appropriate. |
| 1729 | + * MOVED_OFF by another VACUUM would have caused the |
| 1730 | + * visibility check to set XMIN_COMMITTED or XMIN_INVALID. |
1714 | 1731 | */ |
1715 | | -Assert(!(tuple.t_data->t_infomask&HEAP_MOVED_IN)); |
| 1732 | +if (HeapTupleHeaderGetXvac(tuple.t_data)!=myXID) |
| 1733 | +elog(ERROR,"invalid XVAC in tuple header"); |
1716 | 1734 |
|
1717 | 1735 | /* |
1718 | 1736 | * If this (chain) tuple is moved by me already then I |
1719 | 1737 | * have to check is it in vacpage or not - i.e. is it |
1720 | 1738 | * moved while cleaning this page or some previous one. |
1721 | 1739 | */ |
1722 | | -Assert(tuple.t_data->t_infomask&HEAP_MOVED_OFF); |
1723 | | - |
1724 | | -/* |
1725 | | - * MOVED_OFF by another VACUUM would have caused the |
1726 | | - * visibility check to set XMIN_COMMITTED or XMIN_INVALID. |
1727 | | - */ |
1728 | | -Assert(HeapTupleHeaderGetXvac(tuple.t_data)==myXID); |
1729 | 1740 |
|
1730 | 1741 | /* Can't we Assert(keep_tuples > 0) here? */ |
1731 | 1742 | if (keep_tuples==0) |
1732 | 1743 | continue; |
1733 | | -if (chain_tuple_moved)/* some chains was moved while */ |
1734 | | -{/* cleaning this page */ |
| 1744 | +if (chain_tuple_moved) |
| 1745 | +{ |
| 1746 | +/* some chains were moved while cleaning this page */ |
1735 | 1747 | Assert(vacpage->offsets_free>0); |
1736 | 1748 | for (i=0;i<vacpage->offsets_free;i++) |
1737 | 1749 | { |
@@ -2133,15 +2145,19 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, |
2133 | 2145 | continue; |
2134 | 2146 |
|
2135 | 2147 | /* |
2136 | | - **See comments in the walk-along-page loop above, why |
2137 | | - *we * have Asserts here instead of if (...) elog(ERROR). |
| 2148 | + * See comments in the walk-along-page loop above about |
| 2149 | + *why only MOVED_OFF tuples should be found here. |
2138 | 2150 | */ |
2139 | | -Assert(!(htup->t_infomask&HEAP_MOVED_IN)); |
2140 | | -Assert(htup->t_infomask&HEAP_MOVED_OFF); |
2141 | | -Assert(HeapTupleHeaderGetXvac(htup)==myXID); |
| 2151 | +if (htup->t_infomask&HEAP_MOVED_IN) |
| 2152 | +elog(ERROR,"HEAP_MOVED_IN was not expected"); |
| 2153 | +if (!(htup->t_infomask&HEAP_MOVED_OFF)) |
| 2154 | +elog(ERROR,"HEAP_MOVED_OFF was expected"); |
| 2155 | +if (HeapTupleHeaderGetXvac(htup)!=myXID) |
| 2156 | +elog(ERROR,"invalid XVAC in tuple header"); |
| 2157 | + |
2142 | 2158 | if (chain_tuple_moved) |
2143 | 2159 | { |
2144 | | -/* some chainswas moved while cleaning this page */ |
| 2160 | +/* some chainswere moved while cleaning this page */ |
2145 | 2161 | Assert(vacpage->offsets_free>0); |
2146 | 2162 | for (i=0;i<vacpage->offsets_free;i++) |
2147 | 2163 | { |
@@ -2294,7 +2310,13 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, |
2294 | 2310 | vacrelstats->rel_tuples,keep_tuples); |
2295 | 2311 | } |
2296 | 2312 |
|
2297 | | -/* clean moved tuples from last page in Nvacpagelist list */ |
| 2313 | +/* |
| 2314 | + * Clean moved-off tuples from last page in Nvacpagelist list. |
| 2315 | + * |
| 2316 | + * We need only do this in this one page, because higher-numbered |
| 2317 | + * pages are going to be truncated from the relation entirely. |
| 2318 | + * But see comments for update_hint_bits(). |
| 2319 | + */ |
2298 | 2320 | if (vacpage->blkno== (blkno-1)&& |
2299 | 2321 | vacpage->offsets_free>0) |
2300 | 2322 | { |
@@ -2324,16 +2346,18 @@ repair_frag(VRelStats *vacrelstats, Relation onerel, |
2324 | 2346 | continue; |
2325 | 2347 |
|
2326 | 2348 | /* |
2327 | | - **See comments in the walk-along-page loop above, why |
2328 | | - *we * have Asserts here instead of if (...) elog(ERROR). |
| 2349 | + * See comments in the walk-along-page loop above about |
| 2350 | + *why only MOVED_OFF tuples should be found here. |
2329 | 2351 | */ |
2330 | | -Assert(!(htup->t_infomask&HEAP_MOVED_IN)); |
2331 | | -Assert(htup->t_infomask&HEAP_MOVED_OFF); |
2332 | | -Assert(HeapTupleHeaderGetXvac(htup)==myXID); |
| 2352 | +if (htup->t_infomask&HEAP_MOVED_IN) |
| 2353 | +elog(ERROR,"HEAP_MOVED_IN was not expected"); |
| 2354 | +if (!(htup->t_infomask&HEAP_MOVED_OFF)) |
| 2355 | +elog(ERROR,"HEAP_MOVED_OFF was expected"); |
| 2356 | +if (HeapTupleHeaderGetXvac(htup)!=myXID) |
| 2357 | +elog(ERROR,"invalid XVAC in tuple header"); |
2333 | 2358 |
|
2334 | 2359 | itemid->lp_flags &= ~LP_USED; |
2335 | 2360 | num_tuples++; |
2336 | | - |
2337 | 2361 | } |
2338 | 2362 | Assert(vacpage->offsets_free==num_tuples); |
2339 | 2363 |
|
@@ -2644,20 +2668,36 @@ move_plain_tuple(Relation rel, |
2644 | 2668 | /* |
2645 | 2669 | *update_hint_bits() -- update hint bits in destination pages |
2646 | 2670 | * |
2647 | | - * Scan all the pages that we moved tuples onto and update tuple |
2648 | | - * status bits. This is not really necessary, but will save time for |
2649 | | - * future transactions examining these tuples. |
| 2671 | + * Scan all the pages that we moved tuples onto and update tuple status bits. |
| 2672 | + * This is normally not really necessary, but it will save time for future |
| 2673 | + * transactions examining these tuples. |
| 2674 | + * |
| 2675 | + * This pass guarantees that all HEAP_MOVED_IN tuples are marked as |
| 2676 | + * XMIN_COMMITTED, so that future tqual tests won't need to check their XVAC. |
| 2677 | + * |
| 2678 | + * BUT NOTICE that this code fails to clear HEAP_MOVED_OFF tuples from |
| 2679 | + * pages that were move source pages but not move dest pages. The bulk |
| 2680 | + * of the move source pages will be physically truncated from the relation, |
| 2681 | + * and the last page remaining in the rel will be fixed separately in |
| 2682 | + * repair_frag(), so the only cases where a MOVED_OFF tuple won't get its |
| 2683 | + * hint bits updated are tuples that are moved as part of a chain and were |
| 2684 | + * on pages that were not either move destinations nor at the end of the rel. |
| 2685 | + * To completely ensure that no MOVED_OFF tuples remain unmarked, we'd have |
| 2686 | + * to remember and revisit those pages too. |
2650 | 2687 | * |
2651 | | - * XXX NOTICE that this code fails to clear HEAP_MOVED_OFF tuples from |
2652 | | - * pages that were move source pages but not move dest pages. One |
2653 | | - * also wonders whether it wouldn't be better to skip this step and |
2654 | | - * let the tuple status updates happen someplace that's not holding an |
2655 | | - * exclusive lock on the relation. |
| 2688 | + * Because of this omission, VACUUM FULL FREEZE is not a safe combination; |
| 2689 | + * it's possible that the VACUUM's own XID remains exposed as something that |
| 2690 | + * tqual tests would need to check. |
| 2691 | + * |
| 2692 | + * For the non-freeze case, one wonders whether it wouldn't be better to skip |
| 2693 | + * this work entirely, and let the tuple status updates happen someplace |
| 2694 | + * that's not holding an exclusive lock on the relation. |
2656 | 2695 | */ |
2657 | 2696 | staticvoid |
2658 | 2697 | update_hint_bits(Relationrel,VacPageListfraged_pages,intnum_fraged_pages, |
2659 | 2698 | BlockNumberlast_move_dest_block,intnum_moved) |
2660 | 2699 | { |
| 2700 | +TransactionIdmyXID=GetCurrentTransactionId(); |
2661 | 2701 | intchecked_moved=0; |
2662 | 2702 | inti; |
2663 | 2703 | VacPage*curpage; |
@@ -2696,12 +2736,13 @@ update_hint_bits(Relation rel, VacPageList fraged_pages, int num_fraged_pages, |
2696 | 2736 | continue; |
2697 | 2737 |
|
2698 | 2738 | /* |
2699 | | - * See comments in the walk-along-page loop above, why we have |
2700 | | - * Asserts here instead of if (...) elog(ERROR). The |
2701 | | - * difference here is that we may see MOVED_IN. |
| 2739 | + * Here we may see either MOVED_OFF or MOVED_IN tuples. |
2702 | 2740 | */ |
2703 | | -Assert(htup->t_infomask&HEAP_MOVED); |
2704 | | -Assert(HeapTupleHeaderGetXvac(htup)==GetCurrentTransactionId()); |
| 2741 | +if (!(htup->t_infomask&HEAP_MOVED)) |
| 2742 | +elog(ERROR,"HEAP_MOVED_OFF/HEAP_MOVED_IN was expected"); |
| 2743 | +if (HeapTupleHeaderGetXvac(htup)!=myXID) |
| 2744 | +elog(ERROR,"invalid XVAC in tuple header"); |
| 2745 | + |
2705 | 2746 | if (htup->t_infomask&HEAP_MOVED_IN) |
2706 | 2747 | { |
2707 | 2748 | htup->t_infomask |=HEAP_XMIN_COMMITTED; |
|