Add VACUUM (DISABLE_PAGE_SKIPPING) for emergencies.
authorRobert Haas <[email protected]>
Fri, 17 Jun 2016 19:48:57 +0000 (15:48 -0400)
committerRobert Haas <[email protected]>
Fri, 17 Jun 2016 19:48:57 +0000 (15:48 -0400)
If you really want to vacuum every single page in the relation,
regardless of apparent visibility status or anything else, you can use
this option.  In previous releases, this behavior could be achieved
using VACUUM (FREEZE), but because we can now recognize all-frozen
pages as not needing to be frozen again, that no longer works.  There
should be no need for routine use of this option, but maybe bugs or
disaster recovery will necessitate its use.

Patch by me, reviewed by Andres Freund.

doc/src/sgml/ref/vacuum.sgml
src/backend/commands/vacuum.c
src/backend/commands/vacuumlazy.c
src/backend/parser/gram.y
src/include/nodes/parsenodes.h
src/test/regress/expected/vacuum.out
src/test/regress/sql/vacuum.sql

index 19fd748dde0b523be578405479a2c23384f6b1f3..dee1c5bad31cb76aff5859a1c2d55e592f32a4de 100644 (file)
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
 VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
 VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
 </synopsis>
@@ -129,6 +129,25 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DISABLE_PAGE_SKIPPING</literal></term>
+    <listitem>
+     <para>
+      Normally, <command>VACUUM</> will skip pages based on the <link
+      linkend="vacuum-for-visibility-map">visibility map</>.  Pages where
+      all tuples are known to be frozen can always be skipped, and those
+      where all tuples are known to be visible to all transactions may be
+      skipped except when performing an aggressive vacuum.  Furthermore,
+      except when performing an aggressive vacuum, some pages may be skipped
+      in order to avoid waiting for other sessions to finish using them.
+      This option disables all page-skipping behavior, and is intended to
+      be used only the contents of the visibility map are thought to
+      be suspect, which should happen only if there is a hardware or software
+      issue causing database corruption.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="PARAMETER">table_name</replaceable></term>
     <listitem>
index 25a55ab0a5a8a4662a9a8809cf429782871e8094..0563e6347430d43c6cd5d4be5c8456fcc3406946 100644 (file)
@@ -185,6 +185,15 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
                 errmsg("%s cannot be executed from VACUUM or ANALYZE",
                        stmttype)));
 
+   /*
+    * Sanity check DISABLE_PAGE_SKIPPING option.
+    */
+   if ((options & VACOPT_FULL) != 0 &&
+       (options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
+
    /*
     * Send info about dead objects to the statistics collector, unless we are
     * in autovacuum --- autovacuum.c does this for itself.
index cb5777ffdf9310d626521599c5fa519edd299d2c..32b6fddc6220c63a6287f1e789241e28aaf6cf11 100644 (file)
@@ -137,8 +137,9 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
-              Relation *Irel, int nindexes, bool aggressive);
+static void lazy_scan_heap(Relation onerel, int options,
+              LVRelStats *vacrelstats, Relation *Irel, int nindexes,
+              bool aggressive);
 static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats);
 static bool lazy_check_needs_freeze(Buffer buf, bool *hastup);
 static void lazy_vacuum_index(Relation indrel,
@@ -223,15 +224,17 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
                          &MultiXactCutoff, &mxactFullScanLimit);
 
    /*
-    * We request an aggressive scan if either the table's frozen Xid is now
-    * older than or equal to the requested Xid full-table scan limit; or if
-    * the table's minimum MultiXactId is older than or equal to the requested
-    * mxid full-table scan limit.
+    * We request an aggressive scan if the table's frozen Xid is now older
+    * than or equal to the requested Xid full-table scan limit; or if the
+    * table's minimum MultiXactId is older than or equal to the requested
+    * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
     */
    aggressive = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
                                               xidFullScanLimit);
    aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid,
                                              mxactFullScanLimit);
+   if (options & VACOPT_DISABLE_PAGE_SKIPPING)
+       aggressive = true;
 
    vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
 
@@ -246,7 +249,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
    vacrelstats->hasindex = (nindexes > 0);
 
    /* Do the vacuuming */
-   lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, aggressive);
+   lazy_scan_heap(onerel, options, vacrelstats, Irel, nindexes, aggressive);
 
    /* Done with indexes */
    vac_close_indexes(nindexes, Irel, NoLock);
@@ -441,7 +444,7 @@ vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats)
  *     reference them have been killed.
  */
 static void
-lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
+lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
               Relation *Irel, int nindexes, bool aggressive)
 {
    BlockNumber nblocks,
@@ -542,25 +545,28 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
     * the last page.  This is worth avoiding mainly because such a lock must
     * be replayed on any hot standby, where it can be disruptive.
     */
-   for (next_unskippable_block = 0;
-        next_unskippable_block < nblocks;
-        next_unskippable_block++)
+   next_unskippable_block = 0;
+   if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
    {
-       uint8       vmstatus;
-
-       vmstatus = visibilitymap_get_status(onerel, next_unskippable_block,
-                                           &vmbuffer);
-       if (aggressive)
+       while (next_unskippable_block < nblocks)
        {
-           if ((vmstatus & VISIBILITYMAP_ALL_FROZEN) == 0)
-               break;
-       }
-       else
-       {
-           if ((vmstatus & VISIBILITYMAP_ALL_VISIBLE) == 0)
-               break;
+           uint8       vmstatus;
+
+           vmstatus = visibilitymap_get_status(onerel, next_unskippable_block,
+                                               &vmbuffer);
+           if (aggressive)
+           {
+               if ((vmstatus & VISIBILITYMAP_ALL_FROZEN) == 0)
+                   break;
+           }
+           else
+           {
+               if ((vmstatus & VISIBILITYMAP_ALL_VISIBLE) == 0)
+                   break;
+           }
+           vacuum_delay_point();
+           next_unskippable_block++;
        }
-       vacuum_delay_point();
    }
 
    if (next_unskippable_block >= SKIP_PAGES_THRESHOLD)
@@ -594,26 +600,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
        if (blkno == next_unskippable_block)
        {
            /* Time to advance next_unskippable_block */
-           for (next_unskippable_block++;
-                next_unskippable_block < nblocks;
-                next_unskippable_block++)
+           next_unskippable_block++;
+           if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
            {
-               uint8       vmskipflags;
-
-               vmskipflags = visibilitymap_get_status(onerel,
-                                                      next_unskippable_block,
-                                                      &vmbuffer);
-               if (aggressive)
+               while (next_unskippable_block < nblocks)
                {
-                   if ((vmskipflags & VISIBILITYMAP_ALL_FROZEN) == 0)
-                       break;
-               }
-               else
-               {
-                   if ((vmskipflags & VISIBILITYMAP_ALL_VISIBLE) == 0)
-                       break;
+                   uint8       vmskipflags;
+
+                   vmskipflags = visibilitymap_get_status(onerel,
+                                                     next_unskippable_block,
+                                                          &vmbuffer);
+                   if (aggressive)
+                   {
+                       if ((vmskipflags & VISIBILITYMAP_ALL_FROZEN) == 0)
+                           break;
+                   }
+                   else
+                   {
+                       if ((vmskipflags & VISIBILITYMAP_ALL_VISIBLE) == 0)
+                           break;
+                   }
+                   vacuum_delay_point();
+                   next_unskippable_block++;
                }
-               vacuum_delay_point();
            }
 
            /*
@@ -1054,7 +1063,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
            }
            else
            {
-               bool    tuple_totally_frozen;
+               bool        tuple_totally_frozen;
 
                num_tuples += 1;
                hastup = true;
@@ -1064,8 +1073,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
                 * freezing.  Note we already have exclusive buffer lock.
                 */
                if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
-                                         MultiXactCutoff, &frozen[nfrozen],
-                                           &tuple_totally_frozen))
+                                          MultiXactCutoff, &frozen[nfrozen],
+                                             &tuple_totally_frozen))
                    frozen[nfrozen++].offset = offnum;
 
                if (!tuple_totally_frozen)
index 2c950f937c49a1513867c590592ba412726c023f..edf4516dacd55c0f4859eae6a00ecbb23c2c2d31 100644 (file)
@@ -9370,6 +9370,16 @@ vacuum_option_elem:
            | VERBOSE           { $$ = VACOPT_VERBOSE; }
            | FREEZE            { $$ = VACOPT_FREEZE; }
            | FULL              { $$ = VACOPT_FULL; }
+           | IDENT
+               {
+                   if (strcmp($1, "disable_page_skipping") == 0)
+                       $$ = VACOPT_DISABLE_PAGE_SKIPPING;
+                   else
+                       ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("unrecognized VACUUM option \"%s\"", $1),
+                                    parser_errposition(@1)));
+               }
        ;
 
 AnalyzeStmt:
index 714cf1550fd1e158cd88786c2e8576dccd18eeb4..d36d9c6d01967ae66741367422fc641784086bcd 100644 (file)
@@ -2822,7 +2822,8 @@ typedef enum VacuumOption
    VACOPT_FREEZE = 1 << 3,     /* FREEZE option */
    VACOPT_FULL = 1 << 4,       /* FULL (non-concurrent) vacuum */
    VACOPT_NOWAIT = 1 << 5,     /* don't wait to get lock (autovacuum only) */
-   VACOPT_SKIPTOAST = 1 << 6   /* don't process the TOAST table, if any */
+   VACOPT_SKIPTOAST = 1 << 6,  /* don't process the TOAST table, if any */
+   VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7   /* don't skip any pages */
 } VacuumOption;
 
 typedef struct VacuumStmt
index d2d750382864581da834bdc2871639bfb6227fe6..9b604be4b62e1b472bdc60a31b5c3b19b67f227c 100644 (file)
@@ -79,5 +79,6 @@ ERROR:  ANALYZE cannot be executed from VACUUM or ANALYZE
 CONTEXT:  SQL function "do_analyze" statement 1
 SQL function "wrap_do_analyze" statement 1
 VACUUM FULL vactst;
+VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
 DROP TABLE vaccluster;
 DROP TABLE vactst;
index f8412016cf3fc23bad621f5760e3126ea312e0a0..7b819f654ddf51f0a53fb39de0726bc6bbc50d9f 100644 (file)
@@ -60,5 +60,7 @@ VACUUM FULL pg_database;
 VACUUM FULL vaccluster;
 VACUUM FULL vactst;
 
+VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
+
 DROP TABLE vaccluster;
 DROP TABLE vactst;