Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a96c41f

Browse files
committed
Allow VACUUM to be run with index cleanup disabled.
This commit adds a new reloption, vacuum_index_cleanup, which controls whether index cleanup is performed for a particular relation by default. It also adds a new option to the VACUUM command, INDEX_CLEANUP, which can be used to override the reloption. If neither the reloption nor the VACUUM option is used, the default is true, as before. Masahiko Sawada, reviewed and tested by Nathan Bossart, Alvaro Herrera, Kyotaro Horiguchi, Darafei Praliaskouski, and me. The wording of the documentation is mostly due to me. Discussion: http://postgr.es/m/CAD21AoAt5R3DNUZSjOoXDUY=naYPUOuffVsRzuTYMz29yLzQCA@mail.gmail.com
1 parent 74eb217 commit a96c41f

File tree

11 files changed

+193
-25
lines changed

11 files changed

+193
-25
lines changed

doc/src/sgml/ref/create_table.sgml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,21 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
13891389
</listitem>
13901390
</varlistentry>
13911391

1392+
<varlistentry>
1393+
<term><literal>vacuum_index_cleanup</literal> (<type>boolean</type>)</term>
1394+
<listitem>
1395+
<para>
1396+
Enables or disables index cleanup when <command>VACUUM</command> is
1397+
run on this table. The default value is <literal>true</literal>.
1398+
Disabling index cleanup can speed up <command>VACUUM</command> very
1399+
significantly, but may also lead to severely bloated indexes if table
1400+
modifications are frequent. The <literal>INDEX_CLEANUP</literal>
1401+
parameter to <xref linkend="sql-vacuum"/>, if specified, overrides
1402+
the value of this option.
1403+
</para>
1404+
</listitem>
1405+
</varlistentry>
1406+
13921407
<varlistentry>
13931408
<term><literal>autovacuum_vacuum_threshold</literal>, <literal>toast.autovacuum_vacuum_threshold</literal> (<type>integer</type>)</term>
13941409
<listitem>

doc/src/sgml/ref/vacuum.sgml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
3232
ANALYZE [ <replaceable class="parameter">boolean</replaceable> ]
3333
DISABLE_PAGE_SKIPPING [ <replaceable class="parameter">boolean</replaceable> ]
3434
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
35+
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
3536

3637
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
3738

@@ -181,6 +182,28 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
181182
</listitem>
182183
</varlistentry>
183184

185+
<varlistentry>
186+
<term><literal>INDEX_CLEANUP</literal></term>
187+
<listitem>
188+
<para>
189+
Specifies that <command>VACUUM</command> should attempt to remove
190+
index entries pointing to dead tuples. This is normally the desired
191+
behavior and is the default unless the
192+
<literal>vacuum_index_cleanup</literal> option has been set to false
193+
for the table to be vacuumed. Setting this option to false may be
194+
useful when it is necessary to make vacuum run as quickly as possible,
195+
for example to avoid imminent transaction ID wraparound
196+
(see <xref linkend="vacuum-for-wraparound"/>). However, if index
197+
cleanup is not performed regularly, performance may suffer, because
198+
as the table is modified, indexes will accumulate dead tuples
199+
and the table itself will accumulate dead line pointers that cannot be
200+
removed until index cleanup is completed. This option has no effect
201+
for tables that do not have an index and is ignored if the
202+
<literal>FULL</literal> is used.
203+
</para>
204+
</listitem>
205+
</varlistentry>
206+
184207
<varlistentry>
185208
<term><replaceable class="parameter">boolean</replaceable></term>
186209
<listitem>

src/backend/access/common/reloptions.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ static relopt_bool boolRelOpts[] =
138138
},
139139
false
140140
},
141+
{
142+
{
143+
"vacuum_index_cleanup",
144+
"Enables index vacuuming and index cleanup",
145+
RELOPT_KIND_HEAP,
146+
ShareUpdateExclusiveLock
147+
},
148+
true
149+
},
141150
/* list terminator */
142151
{{NULL}}
143152
};
@@ -1388,7 +1397,9 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
13881397
{"parallel_workers", RELOPT_TYPE_INT,
13891398
offsetof(StdRdOptions, parallel_workers)},
13901399
{"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL,
1391-
offsetof(StdRdOptions, vacuum_cleanup_index_scale_factor)}
1400+
offsetof(StdRdOptions, vacuum_cleanup_index_scale_factor)},
1401+
{"vacuum_index_cleanup", RELOPT_TYPE_BOOL,
1402+
offsetof(StdRdOptions, vacuum_index_cleanup)}
13921403
};
13931404

13941405
options = parseRelOptions(reloptions, validate, kind, &numoptions);

src/backend/access/heap/vacuumlazy.c

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@
112112

113113
typedef struct LVRelStats
114114
{
115-
/* hasindex = true means two-pass strategy; false means one-pass */
116-
bool hasindex;
115+
/* useindex = true means two-pass strategy; false means one-pass */
116+
bool useindex;
117117
/* Overall statistics about rel */
118118
BlockNumber old_rel_pages; /* previous value of pg_class.relpages */
119119
BlockNumber rel_pages; /* total number of pages */
@@ -125,6 +125,8 @@ typedef struct LVRelStats
125125
double new_rel_tuples; /* new estimated total # of tuples */
126126
double new_live_tuples; /* new estimated total # of live tuples */
127127
double new_dead_tuples; /* new estimated total # of dead tuples */
128+
double nleft_dead_tuples; /* # of dead tuples we left */
129+
double nleft_dead_itemids; /* # of dead item pointers we left */
128130
BlockNumber pages_removed;
129131
double tuples_deleted;
130132
BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
@@ -150,7 +152,7 @@ static BufferAccessStrategy vac_strategy;
150152

151153

152154
/* non-export function prototypes */
153-
static void lazy_scan_heap(Relation onerel, int options,
155+
static void lazy_scan_heap(Relation onerel, VacuumParams *params,
154156
LVRelStats *vacrelstats, Relation *Irel, int nindexes,
155157
bool aggressive);
156158
static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats, BlockNumber nblocks);
@@ -209,6 +211,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
209211
MultiXactId new_min_multi;
210212

211213
Assert(params != NULL);
214+
Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
212215

213216
/* measure elapsed time iff autovacuum logging requires it */
214217
if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -275,10 +278,11 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
275278

276279
/* Open all indexes of the relation */
277280
vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel);
278-
vacrelstats->hasindex = (nindexes > 0);
281+
vacrelstats->useindex = (nindexes > 0 &&
282+
params->index_cleanup == VACOPT_TERNARY_ENABLED);
279283

280284
/* Do the vacuuming */
281-
lazy_scan_heap(onerel, params->options, vacrelstats, Irel, nindexes, aggressive);
285+
lazy_scan_heap(onerel, params, vacrelstats, Irel, nindexes, aggressive);
282286

283287
/* Done with indexes */
284288
vac_close_indexes(nindexes, Irel, NoLock);
@@ -349,7 +353,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
349353
new_rel_pages,
350354
new_live_tuples,
351355
new_rel_allvisible,
352-
vacrelstats->hasindex,
356+
nindexes > 0,
353357
new_frozen_xid,
354358
new_min_multi,
355359
false);
@@ -419,6 +423,12 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
419423
vacrelstats->new_rel_tuples,
420424
vacrelstats->new_dead_tuples,
421425
OldestXmin);
426+
if (vacrelstats->nleft_dead_tuples > 0 ||
427+
vacrelstats->nleft_dead_itemids > 0)
428+
appendStringInfo(&buf,
429+
_("%.0f tuples and %.0f item identifiers are left as dead.\n"),
430+
vacrelstats->nleft_dead_tuples,
431+
vacrelstats->nleft_dead_itemids);
422432
appendStringInfo(&buf,
423433
_("buffer usage: %d hits, %d misses, %d dirtied\n"),
424434
VacuumPageHit,
@@ -485,7 +495,7 @@ vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats)
485495
* reference them have been killed.
486496
*/
487497
static void
488-
lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
498+
lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
489499
Relation *Irel, int nindexes, bool aggressive)
490500
{
491501
BlockNumber nblocks,
@@ -501,7 +511,10 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
501511
live_tuples, /* live tuples (reltuples estimate) */
502512
tups_vacuumed, /* tuples cleaned up by vacuum */
503513
nkeep, /* dead-but-not-removable tuples */
504-
nunused; /* unused item pointers */
514+
nunused, /* unused item pointers */
515+
nleft_dead_tuples, /* tuples we left as dead */
516+
nleft_dead_itemids; /* item pointers we left as dead,
517+
* includes nleft_dead_tuples. */
505518
IndexBulkDeleteResult **indstats;
506519
int i;
507520
PGRUsage ru0;
@@ -534,6 +547,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
534547
empty_pages = vacuumed_pages = 0;
535548
next_fsm_block_to_vacuum = (BlockNumber) 0;
536549
num_tuples = live_tuples = tups_vacuumed = nkeep = nunused = 0;
550+
nleft_dead_itemids = nleft_dead_tuples = 0;
537551

538552
indstats = (IndexBulkDeleteResult **)
539553
palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
@@ -599,7 +613,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
599613
* be replayed on any hot standby, where it can be disruptive.
600614
*/
601615
next_unskippable_block = 0;
602-
if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
616+
if ((params->options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
603617
{
604618
while (next_unskippable_block < nblocks)
605619
{
@@ -654,7 +668,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
654668
{
655669
/* Time to advance next_unskippable_block */
656670
next_unskippable_block++;
657-
if ((options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
671+
if ((params->options & VACOPT_DISABLE_PAGE_SKIPPING) == 0)
658672
{
659673
while (next_unskippable_block < nblocks)
660674
{
@@ -1070,7 +1084,17 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
10701084
HeapTupleIsHeapOnly(&tuple))
10711085
nkeep += 1;
10721086
else
1087+
{
10731088
tupgone = true; /* we can delete the tuple */
1089+
1090+
/*
1091+
* Since this dead tuple will not be vacuumed and
1092+
* ignored when index cleanup is disabled we count
1093+
* count it for reporting.
1094+
*/
1095+
if (params->index_cleanup == VACOPT_TERNARY_ENABLED)
1096+
nleft_dead_tuples++;
1097+
}
10741098
all_visible = false;
10751099
break;
10761100
case HEAPTUPLE_LIVE:
@@ -1222,23 +1246,40 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
12221246
}
12231247

12241248
/*
1225-
* If there are no indexes then we can vacuum the page right now
1226-
* instead of doing a second scan.
1249+
* If there are no indexes we can vacuum the page right now instead of
1250+
* doing a second scan. Also we don't do that but forget dead tuples
1251+
* when index cleanup is disabled.
12271252
*/
1228-
if (nindexes == 0 &&
1229-
vacrelstats->num_dead_tuples > 0)
1253+
if (!vacrelstats->useindex && vacrelstats->num_dead_tuples > 0)
12301254
{
1231-
/* Remove tuples from heap */
1232-
lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer);
1233-
has_dead_tuples = false;
1255+
if (nindexes == 0)
1256+
{
1257+
/* Remove tuples from heap if the table has no index */
1258+
lazy_vacuum_page(onerel, blkno, buf, 0, vacrelstats, &vmbuffer);
1259+
vacuumed_pages++;
1260+
has_dead_tuples = false;
1261+
}
1262+
else
1263+
{
1264+
/*
1265+
* Here, we have indexes but index cleanup is disabled. Instead of
1266+
* vacuuming the dead tuples on the heap, we just forget them.
1267+
*
1268+
* Note that vacrelstats->dead_tuples could have tuples which
1269+
* became dead after HOT-pruning but are not marked dead yet.
1270+
* We do not process them because it's a very rare condition, and
1271+
* the next vacuum will process them anyway.
1272+
*/
1273+
Assert(params->index_cleanup == VACOPT_TERNARY_DISABLED);
1274+
nleft_dead_itemids += vacrelstats->num_dead_tuples;
1275+
}
12341276

12351277
/*
12361278
* Forget the now-vacuumed tuples, and press on, but be careful
12371279
* not to reset latestRemovedXid since we want that value to be
12381280
* valid.
12391281
*/
12401282
vacrelstats->num_dead_tuples = 0;
1241-
vacuumed_pages++;
12421283

12431284
/*
12441285
* Periodically do incremental FSM vacuuming to make newly-freed
@@ -1357,14 +1398,21 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
13571398
RecordPageWithFreeSpace(onerel, blkno, freespace, nblocks);
13581399
}
13591400

1401+
/* No dead tuples should be left if index cleanup is enabled */
1402+
Assert((params->index_cleanup == VACOPT_TERNARY_ENABLED &&
1403+
nleft_dead_tuples == 0 && nleft_dead_itemids == 0) ||
1404+
params->index_cleanup == VACOPT_TERNARY_DISABLED);
1405+
13601406
/* report that everything is scanned and vacuumed */
13611407
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
13621408

13631409
pfree(frozen);
13641410

13651411
/* save stats for use later */
13661412
vacrelstats->tuples_deleted = tups_vacuumed;
1367-
vacrelstats->new_dead_tuples = nkeep;
1413+
vacrelstats->new_dead_tuples = nkeep + nleft_dead_tuples;
1414+
vacrelstats->nleft_dead_tuples = nleft_dead_tuples;
1415+
vacrelstats->nleft_dead_itemids = nleft_dead_itemids;
13681416

13691417
/* now we can compute the new value for pg_class.reltuples */
13701418
vacrelstats->new_live_tuples = vac_estimate_reltuples(onerel,
@@ -1433,8 +1481,11 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
14331481
PROGRESS_VACUUM_PHASE_INDEX_CLEANUP);
14341482

14351483
/* Do post-vacuum cleanup and statistics update for each index */
1436-
for (i = 0; i < nindexes; i++)
1437-
lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
1484+
if (vacrelstats->useindex)
1485+
{
1486+
for (i = 0; i < nindexes; i++)
1487+
lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
1488+
}
14381489

14391490
/* If no indexes, make log report that lazy_vacuum_heap would've made */
14401491
if (vacuumed_pages)
@@ -1465,6 +1516,8 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
14651516
"%u pages are entirely empty.\n",
14661517
empty_pages),
14671518
empty_pages);
1519+
appendStringInfo(&buf, "%.0f tuples and %.0f item identifiers are left as dead.\n",
1520+
nleft_dead_tuples, nleft_dead_itemids);
14681521
appendStringInfo(&buf, _("%s."), pg_rusage_show(&ru0));
14691522

14701523
ereport(elevel,
@@ -2110,7 +2163,7 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
21102163
autovacuum_work_mem != -1 ?
21112164
autovacuum_work_mem : maintenance_work_mem;
21122165

2113-
if (vacrelstats->hasindex)
2166+
if (vacrelstats->useindex)
21142167
{
21152168
maxtuples = (vac_work_mem * 1024L) / sizeof(ItemPointerData);
21162169
maxtuples = Min(maxtuples, INT_MAX);

src/backend/commands/vacuum.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
7676
TransactionId lastSaneFrozenXid,
7777
MultiXactId lastSaneMinMulti);
7878
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params);
79+
static VacOptTernaryValue get_vacopt_ternary_value(DefElem *def);
7980

8081
/*
8182
* Primary entry point for manual VACUUM and ANALYZE commands
@@ -95,6 +96,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
9596
bool disable_page_skipping = false;
9697
ListCell *lc;
9798

99+
/* Set default value */
100+
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
101+
98102
/* Parse options list */
99103
foreach(lc, vacstmt->options)
100104
{
@@ -120,6 +124,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
120124
full = defGetBoolean(opt);
121125
else if (strcmp(opt->defname, "disable_page_skipping") == 0)
122126
disable_page_skipping = defGetBoolean(opt);
127+
else if (strcmp(opt->defname, "index_cleanup") == 0)
128+
params.index_cleanup = get_vacopt_ternary_value(opt);
123129
else
124130
ereport(ERROR,
125131
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1719,6 +1725,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
17191725
onerelid = onerel->rd_lockInfo.lockRelId;
17201726
LockRelationIdForSession(&onerelid, lmode);
17211727

1728+
/* Set index cleanup option based on reloptions if not yet */
1729+
if (params->index_cleanup == VACOPT_TERNARY_DEFAULT)
1730+
{
1731+
if (onerel->rd_options == NULL ||
1732+
((StdRdOptions *) onerel->rd_options)->vacuum_index_cleanup)
1733+
params->index_cleanup = VACOPT_TERNARY_ENABLED;
1734+
else
1735+
params->index_cleanup = VACOPT_TERNARY_DISABLED;
1736+
}
1737+
17221738
/*
17231739
* Remember the relation's TOAST relation for later, if the caller asked
17241740
* us to process it. In VACUUM FULL, though, the toast table is
@@ -1899,3 +1915,15 @@ vacuum_delay_point(void)
18991915
CHECK_FOR_INTERRUPTS();
19001916
}
19011917
}
1918+
1919+
/*
1920+
* A wrapper function of defGetBoolean().
1921+
*
1922+
* This function returns VACOPT_TERNARY_ENABLED and VACOPT_TERNARY_DISABLED
1923+
* instead of true and false.
1924+
*/
1925+
static VacOptTernaryValue
1926+
get_vacopt_ternary_value(DefElem *def)
1927+
{
1928+
return defGetBoolean(def) ? VACOPT_TERNARY_ENABLED : VACOPT_TERNARY_DISABLED;
1929+
}

src/backend/postmaster/autovacuum.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2886,6 +2886,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
28862886
(dovacuum ? VACOPT_VACUUM : 0) |
28872887
(doanalyze ? VACOPT_ANALYZE : 0) |
28882888
(!wraparound ? VACOPT_SKIP_LOCKED : 0);
2889+
tab->at_params.index_cleanup = VACOPT_TERNARY_DEFAULT;
28892890
tab->at_params.freeze_min_age = freeze_min_age;
28902891
tab->at_params.freeze_table_age = freeze_table_age;
28912892
tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age;

0 commit comments

Comments
 (0)