Commit ede62e56 authored by Robert Haas's avatar Robert Haas

Add VACUUM (DISABLE_PAGE_SKIPPING) for emergencies.

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.
parent 20eb2731
...@@ -21,7 +21,7 @@ PostgreSQL documentation ...@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv> <refsynopsisdiv>
<synopsis> <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 ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
</synopsis> </synopsis>
...@@ -129,6 +129,25 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER"> ...@@ -129,6 +129,25 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><replaceable class="PARAMETER">table_name</replaceable></term> <term><replaceable class="PARAMETER">table_name</replaceable></term>
<listitem> <listitem>
......
...@@ -185,6 +185,15 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params, ...@@ -185,6 +185,15 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("%s cannot be executed from VACUUM or ANALYZE", errmsg("%s cannot be executed from VACUUM or ANALYZE",
stmttype))); 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 * Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself. * in autovacuum --- autovacuum.c does this for itself.
......
...@@ -137,8 +137,9 @@ static BufferAccessStrategy vac_strategy; ...@@ -137,8 +137,9 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */ /* non-export function prototypes */
static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, static void lazy_scan_heap(Relation onerel, int options,
Relation *Irel, int nindexes, bool aggressive); LVRelStats *vacrelstats, Relation *Irel, int nindexes,
bool aggressive);
static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats);
static bool lazy_check_needs_freeze(Buffer buf, bool *hastup); static bool lazy_check_needs_freeze(Buffer buf, bool *hastup);
static void lazy_vacuum_index(Relation indrel, static void lazy_vacuum_index(Relation indrel,
...@@ -223,15 +224,17 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, ...@@ -223,15 +224,17 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
&MultiXactCutoff, &mxactFullScanLimit); &MultiXactCutoff, &mxactFullScanLimit);
/* /*
* We request an aggressive scan if either the table's frozen Xid is now * We request an aggressive scan if the table's frozen Xid is now older
* older than or equal to the requested Xid full-table scan limit; or if * than or equal to the requested Xid full-table scan limit; or if the
* the table's minimum MultiXactId is older than or equal to the requested * table's minimum MultiXactId is older than or equal to the requested
* mxid full-table scan limit. * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
*/ */
aggressive = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid, aggressive = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
xidFullScanLimit); xidFullScanLimit);
aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid, aggressive |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid,
mxactFullScanLimit); mxactFullScanLimit);
if (options & VACOPT_DISABLE_PAGE_SKIPPING)
aggressive = true;
vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats)); vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
...@@ -246,7 +249,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params, ...@@ -246,7 +249,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
vacrelstats->hasindex = (nindexes > 0); vacrelstats->hasindex = (nindexes > 0);
/* Do the vacuuming */ /* Do the vacuuming */
lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, aggressive); lazy_scan_heap(onerel, options, vacrelstats, Irel, nindexes, aggressive);
/* Done with indexes */ /* Done with indexes */
vac_close_indexes(nindexes, Irel, NoLock); vac_close_indexes(nindexes, Irel, NoLock);
...@@ -441,7 +444,7 @@ vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats) ...@@ -441,7 +444,7 @@ vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats)
* reference them have been killed. * reference them have been killed.
*/ */
static void static void
lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
Relation *Irel, int nindexes, bool aggressive) Relation *Irel, int nindexes, bool aggressive)
{ {
BlockNumber nblocks, BlockNumber nblocks,
...@@ -542,25 +545,28 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ...@@ -542,25 +545,28 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* the last page. This is worth avoiding mainly because such a lock must * the last page. This is worth avoiding mainly because such a lock must
* be replayed on any hot standby, where it can be disruptive. * be replayed on any hot standby, where it can be disruptive.
*/ */
for (next_unskippable_block = 0; next_unskippable_block = 0;
next_unskippable_block < nblocks; if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
next_unskippable_block++)
{ {
uint8 vmstatus; while (next_unskippable_block < nblocks)
vmstatus = visibilitymap_get_status(onerel, next_unskippable_block,
&vmbuffer);
if (aggressive)
{ {
if ((vmstatus & VISIBILITYMAP_ALL_FROZEN) == 0) uint8 vmstatus;
break;
} vmstatus = visibilitymap_get_status(onerel, next_unskippable_block,
else &vmbuffer);
{ if (aggressive)
if ((vmstatus & VISIBILITYMAP_ALL_VISIBLE) == 0) {
break; 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) if (next_unskippable_block >= SKIP_PAGES_THRESHOLD)
...@@ -594,26 +600,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ...@@ -594,26 +600,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
if (blkno == next_unskippable_block) if (blkno == next_unskippable_block)
{ {
/* Time to advance next_unskippable_block */ /* Time to advance next_unskippable_block */
for (next_unskippable_block++; next_unskippable_block++;
next_unskippable_block < nblocks; if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
next_unskippable_block++)
{ {
uint8 vmskipflags; while (next_unskippable_block < nblocks)
vmskipflags = visibilitymap_get_status(onerel,
next_unskippable_block,
&vmbuffer);
if (aggressive)
{ {
if ((vmskipflags & VISIBILITYMAP_ALL_FROZEN) == 0) uint8 vmskipflags;
break;
} vmskipflags = visibilitymap_get_status(onerel,
else next_unskippable_block,
{ &vmbuffer);
if ((vmskipflags & VISIBILITYMAP_ALL_VISIBLE) == 0) if (aggressive)
break; {
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, ...@@ -1054,7 +1063,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
} }
else else
{ {
bool tuple_totally_frozen; bool tuple_totally_frozen;
num_tuples += 1; num_tuples += 1;
hastup = true; hastup = true;
...@@ -1064,8 +1073,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ...@@ -1064,8 +1073,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* freezing. Note we already have exclusive buffer lock. * freezing. Note we already have exclusive buffer lock.
*/ */
if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit, if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
MultiXactCutoff, &frozen[nfrozen], MultiXactCutoff, &frozen[nfrozen],
&tuple_totally_frozen)) &tuple_totally_frozen))
frozen[nfrozen++].offset = offnum; frozen[nfrozen++].offset = offnum;
if (!tuple_totally_frozen) if (!tuple_totally_frozen)
......
...@@ -9370,6 +9370,16 @@ vacuum_option_elem: ...@@ -9370,6 +9370,16 @@ vacuum_option_elem:
| VERBOSE { $$ = VACOPT_VERBOSE; } | VERBOSE { $$ = VACOPT_VERBOSE; }
| FREEZE { $$ = VACOPT_FREEZE; } | FREEZE { $$ = VACOPT_FREEZE; }
| FULL { $$ = VACOPT_FULL; } | 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: AnalyzeStmt:
......
...@@ -2822,7 +2822,8 @@ typedef enum VacuumOption ...@@ -2822,7 +2822,8 @@ typedef enum VacuumOption
VACOPT_FREEZE = 1 << 3, /* FREEZE option */ VACOPT_FREEZE = 1 << 3, /* FREEZE option */
VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */ VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */
VACOPT_NOWAIT = 1 << 5, /* don't wait to get lock (autovacuum only) */ 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; } VacuumOption;
typedef struct VacuumStmt typedef struct VacuumStmt
......
...@@ -79,5 +79,6 @@ ERROR: ANALYZE cannot be executed from VACUUM or ANALYZE ...@@ -79,5 +79,6 @@ ERROR: ANALYZE cannot be executed from VACUUM or ANALYZE
CONTEXT: SQL function "do_analyze" statement 1 CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1 SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst; VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
DROP TABLE vaccluster; DROP TABLE vaccluster;
DROP TABLE vactst; DROP TABLE vactst;
...@@ -60,5 +60,7 @@ VACUUM FULL pg_database; ...@@ -60,5 +60,7 @@ VACUUM FULL pg_database;
VACUUM FULL vaccluster; VACUUM FULL vaccluster;
VACUUM FULL vactst; VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
DROP TABLE vaccluster; DROP TABLE vaccluster;
DROP TABLE vactst; DROP TABLE vactst;
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