diff options
Diffstat (limited to 'src/backend/commands/vacuum.c')
-rw-r--r-- | src/backend/commands/vacuum.c | 2260 |
1 files changed, 2260 insertions, 0 deletions
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c new file mode 100644 index 0000000..5c4bc15 --- /dev/null +++ b/src/backend/commands/vacuum.c @@ -0,0 +1,2260 @@ +/*------------------------------------------------------------------------- + * + * vacuum.c + * The postgres vacuum cleaner. + * + * This file now includes only control and dispatch code for VACUUM and + * ANALYZE commands. Regular VACUUM is implemented in vacuumlazy.c, + * ANALYZE in analyze.c, and VACUUM FULL is a variant of CLUSTER, handled + * in cluster.c. + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/vacuum.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/clog.h" +#include "access/commit_ts.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/tableam.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/namespace.h" +#include "catalog/pg_database.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_namespace.h" +#include "commands/cluster.h" +#include "commands/defrem.h" +#include "commands/vacuum.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/bgworker_internals.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "utils/acl.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + + +/* + * GUC parameters + */ +int vacuum_freeze_min_age; +int vacuum_freeze_table_age; +int vacuum_multixact_freeze_min_age; +int vacuum_multixact_freeze_table_age; +int vacuum_failsafe_age; +int vacuum_multixact_failsafe_age; + + +/* A few variables that don't seem worth passing around as parameters */ +static MemoryContext vac_context = NULL; +static BufferAccessStrategy vac_strategy; + + +/* + * Variables for cost-based parallel vacuum. See comments atop + * compute_parallel_delay to understand how it works. + */ +pg_atomic_uint32 *VacuumSharedCostBalance = NULL; +pg_atomic_uint32 *VacuumActiveNWorkers = NULL; +int VacuumCostBalanceLocal = 0; + +/* non-export function prototypes */ +static List *expand_vacuum_rel(VacuumRelation *vrel, int options); +static List *get_all_vacuum_rels(int options); +static void vac_truncate_clog(TransactionId frozenXID, + MultiXactId minMulti, + TransactionId lastSaneFrozenXid, + MultiXactId lastSaneMinMulti); +static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params); +static double compute_parallel_delay(void); +static VacOptValue get_vacoptval_from_boolean(DefElem *def); + +/* + * Primary entry point for manual VACUUM and ANALYZE commands + * + * This is mainly a preparation wrapper for the real operations that will + * happen in vacuum(). + */ +void +ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) +{ + VacuumParams params; + bool verbose = false; + bool skip_locked = false; + bool analyze = false; + bool freeze = false; + bool full = false; + bool disable_page_skipping = false; + bool process_toast = true; + ListCell *lc; + + /* index_cleanup and truncate values unspecified for now */ + params.index_cleanup = VACOPTVALUE_UNSPECIFIED; + params.truncate = VACOPTVALUE_UNSPECIFIED; + + /* By default parallel vacuum is enabled */ + params.nworkers = 0; + + /* Parse options list */ + foreach(lc, vacstmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + /* Parse common options for VACUUM and ANALYZE */ + if (strcmp(opt->defname, "verbose") == 0) + verbose = defGetBoolean(opt); + else if (strcmp(opt->defname, "skip_locked") == 0) + skip_locked = defGetBoolean(opt); + else if (!vacstmt->is_vacuumcmd) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized ANALYZE option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location))); + + /* Parse options available on VACUUM */ + else if (strcmp(opt->defname, "analyze") == 0) + analyze = defGetBoolean(opt); + else if (strcmp(opt->defname, "freeze") == 0) + freeze = defGetBoolean(opt); + else if (strcmp(opt->defname, "full") == 0) + full = defGetBoolean(opt); + else if (strcmp(opt->defname, "disable_page_skipping") == 0) + disable_page_skipping = defGetBoolean(opt); + else if (strcmp(opt->defname, "index_cleanup") == 0) + { + /* Interpret no string as the default, which is 'auto' */ + if (!opt->arg) + params.index_cleanup = VACOPTVALUE_AUTO; + else + { + char *sval = defGetString(opt); + + /* Try matching on 'auto' string, or fall back on boolean */ + if (pg_strcasecmp(sval, "auto") == 0) + params.index_cleanup = VACOPTVALUE_AUTO; + else + params.index_cleanup = get_vacoptval_from_boolean(opt); + } + } + else if (strcmp(opt->defname, "process_toast") == 0) + process_toast = defGetBoolean(opt); + else if (strcmp(opt->defname, "truncate") == 0) + params.truncate = get_vacoptval_from_boolean(opt); + else if (strcmp(opt->defname, "parallel") == 0) + { + if (opt->arg == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("parallel option requires a value between 0 and %d", + MAX_PARALLEL_WORKER_LIMIT), + parser_errposition(pstate, opt->location))); + } + else + { + int nworkers; + + nworkers = defGetInt32(opt); + if (nworkers < 0 || nworkers > MAX_PARALLEL_WORKER_LIMIT) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("parallel workers for vacuum must be between 0 and %d", + MAX_PARALLEL_WORKER_LIMIT), + parser_errposition(pstate, opt->location))); + + /* + * Disable parallel vacuum, if user has specified parallel + * degree as zero. + */ + if (nworkers == 0) + params.nworkers = -1; + else + params.nworkers = nworkers; + } + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized VACUUM option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location))); + } + + /* Set vacuum options */ + params.options = + (vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE) | + (verbose ? VACOPT_VERBOSE : 0) | + (skip_locked ? VACOPT_SKIP_LOCKED : 0) | + (analyze ? VACOPT_ANALYZE : 0) | + (freeze ? VACOPT_FREEZE : 0) | + (full ? VACOPT_FULL : 0) | + (disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0) | + (process_toast ? VACOPT_PROCESS_TOAST : 0); + + /* sanity checks on options */ + Assert(params.options & (VACOPT_VACUUM | VACOPT_ANALYZE)); + Assert((params.options & VACOPT_VACUUM) || + !(params.options & (VACOPT_FULL | VACOPT_FREEZE))); + + if ((params.options & VACOPT_FULL) && params.nworkers > 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("VACUUM FULL cannot be performed in parallel"))); + + /* + * Make sure VACOPT_ANALYZE is specified if any column lists are present. + */ + if (!(params.options & VACOPT_ANALYZE)) + { + ListCell *lc; + + foreach(lc, vacstmt->rels) + { + VacuumRelation *vrel = lfirst_node(VacuumRelation, lc); + + if (vrel->va_cols != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ANALYZE option must be specified when a column list is provided"))); + } + } + + /* + * All freeze ages are zero if the FREEZE option is given; otherwise pass + * them as -1 which means to use the default values. + */ + if (params.options & VACOPT_FREEZE) + { + params.freeze_min_age = 0; + params.freeze_table_age = 0; + params.multixact_freeze_min_age = 0; + params.multixact_freeze_table_age = 0; + } + else + { + params.freeze_min_age = -1; + params.freeze_table_age = -1; + params.multixact_freeze_min_age = -1; + params.multixact_freeze_table_age = -1; + } + + /* user-invoked vacuum is never "for wraparound" */ + params.is_wraparound = false; + + /* user-invoked vacuum never uses this parameter */ + params.log_min_duration = -1; + + /* Now go through the common routine */ + vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel); +} + +/* + * Internal entry point for VACUUM and ANALYZE commands. + * + * relations, if not NIL, is a list of VacuumRelation to process; otherwise, + * we process all relevant tables in the database. For each VacuumRelation, + * if a valid OID is supplied, the table with that OID is what to process; + * otherwise, the VacuumRelation's RangeVar indicates what to process. + * + * params contains a set of parameters that can be used to customize the + * behavior. + * + * bstrategy is normally given as NULL, but in autovacuum it can be passed + * in to use the same buffer strategy object across multiple vacuum() calls. + * + * isTopLevel should be passed down from ProcessUtility. + * + * It is the caller's responsibility that all parameters are allocated in a + * memory context that will not disappear at transaction commit. + */ +void +vacuum(List *relations, VacuumParams *params, + BufferAccessStrategy bstrategy, bool isTopLevel) +{ + static bool in_vacuum = false; + + const char *stmttype; + volatile bool in_outer_xact, + use_own_xacts; + + Assert(params != NULL); + + stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE"; + + /* + * We cannot run VACUUM inside a user transaction block; if we were inside + * a transaction, then our commit- and start-transaction-command calls + * would not have the intended effect! There are numerous other subtle + * dependencies on this, too. + * + * ANALYZE (without VACUUM) can run either way. + */ + if (params->options & VACOPT_VACUUM) + { + PreventInTransactionBlock(isTopLevel, stmttype); + in_outer_xact = false; + } + else + in_outer_xact = IsInTransactionBlock(isTopLevel); + + /* + * Due to static variables vac_context, anl_context and vac_strategy, + * vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE + * calls a hostile index expression that itself calls ANALYZE. + */ + if (in_vacuum) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s cannot be executed from VACUUM or ANALYZE", + stmttype))); + + /* + * Sanity check DISABLE_PAGE_SKIPPING option. + */ + if ((params->options & VACOPT_FULL) != 0 && + (params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL"))); + + /* sanity check for PROCESS_TOAST */ + if ((params->options & VACOPT_FULL) != 0 && + (params->options & VACOPT_PROCESS_TOAST) == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PROCESS_TOAST required with VACUUM FULL"))); + + /* + * Send info about dead objects to the statistics collector, unless we are + * in autovacuum --- autovacuum.c does this for itself. + */ + if ((params->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess()) + pgstat_vacuum_stat(); + + /* + * Create special memory context for cross-transaction storage. + * + * Since it is a child of PortalContext, it will go away eventually even + * if we suffer an error; there's no need for special abort cleanup logic. + */ + vac_context = AllocSetContextCreate(PortalContext, + "Vacuum", + ALLOCSET_DEFAULT_SIZES); + + /* + * If caller didn't give us a buffer strategy object, make one in the + * cross-transaction memory context. + */ + if (bstrategy == NULL) + { + MemoryContext old_context = MemoryContextSwitchTo(vac_context); + + bstrategy = GetAccessStrategy(BAS_VACUUM); + MemoryContextSwitchTo(old_context); + } + vac_strategy = bstrategy; + + /* + * Build list of relation(s) to process, putting any new data in + * vac_context for safekeeping. + */ + if (relations != NIL) + { + List *newrels = NIL; + ListCell *lc; + + foreach(lc, relations) + { + VacuumRelation *vrel = lfirst_node(VacuumRelation, lc); + List *sublist; + MemoryContext old_context; + + sublist = expand_vacuum_rel(vrel, params->options); + old_context = MemoryContextSwitchTo(vac_context); + newrels = list_concat(newrels, sublist); + MemoryContextSwitchTo(old_context); + } + relations = newrels; + } + else + relations = get_all_vacuum_rels(params->options); + + /* + * Decide whether we need to start/commit our own transactions. + * + * For VACUUM (with or without ANALYZE): always do so, so that we can + * release locks as soon as possible. (We could possibly use the outer + * transaction for a one-table VACUUM, but handling TOAST tables would be + * problematic.) + * + * For ANALYZE (no VACUUM): if inside a transaction block, we cannot + * start/commit our own transactions. Also, there's no need to do so if + * only processing one relation. For multiple relations when not within a + * transaction block, and also in an autovacuum worker, use own + * transactions so we can release locks sooner. + */ + if (params->options & VACOPT_VACUUM) + use_own_xacts = true; + else + { + Assert(params->options & VACOPT_ANALYZE); + if (IsAutoVacuumWorkerProcess()) + use_own_xacts = true; + else if (in_outer_xact) + use_own_xacts = false; + else if (list_length(relations) > 1) + use_own_xacts = true; + else + use_own_xacts = false; + } + + /* + * vacuum_rel expects to be entered with no transaction active; it will + * start and commit its own transaction. But we are called by an SQL + * command, and so we are executing inside a transaction already. We + * commit the transaction started in PostgresMain() here, and start + * another one before exiting to match the commit waiting for us back in + * PostgresMain(). + */ + if (use_own_xacts) + { + Assert(!in_outer_xact); + + /* ActiveSnapshot is not set by autovacuum */ + if (ActiveSnapshotSet()) + PopActiveSnapshot(); + + /* matches the StartTransaction in PostgresMain() */ + CommitTransactionCommand(); + } + + /* Turn vacuum cost accounting on or off, and set/clear in_vacuum */ + PG_TRY(); + { + ListCell *cur; + + in_vacuum = true; + VacuumCostActive = (VacuumCostDelay > 0); + VacuumCostBalance = 0; + VacuumPageHit = 0; + VacuumPageMiss = 0; + VacuumPageDirty = 0; + VacuumCostBalanceLocal = 0; + VacuumSharedCostBalance = NULL; + VacuumActiveNWorkers = NULL; + + /* + * Loop to process each selected relation. + */ + foreach(cur, relations) + { + VacuumRelation *vrel = lfirst_node(VacuumRelation, cur); + + if (params->options & VACOPT_VACUUM) + { + if (!vacuum_rel(vrel->oid, vrel->relation, params)) + continue; + } + + if (params->options & VACOPT_ANALYZE) + { + /* + * If using separate xacts, start one for analyze. Otherwise, + * we can use the outer transaction. + */ + if (use_own_xacts) + { + StartTransactionCommand(); + /* functions in indexes may want a snapshot set */ + PushActiveSnapshot(GetTransactionSnapshot()); + } + + analyze_rel(vrel->oid, vrel->relation, params, + vrel->va_cols, in_outer_xact, vac_strategy); + + if (use_own_xacts) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } + else + { + /* + * If we're not using separate xacts, better separate the + * ANALYZE actions with CCIs. This avoids trouble if user + * says "ANALYZE t, t". + */ + CommandCounterIncrement(); + } + } + } + } + PG_FINALLY(); + { + in_vacuum = false; + VacuumCostActive = false; + } + PG_END_TRY(); + + /* + * Finish up processing. + */ + if (use_own_xacts) + { + /* here, we are not in a transaction */ + + /* + * This matches the CommitTransaction waiting for us in + * PostgresMain(). + */ + StartTransactionCommand(); + } + + if ((params->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess()) + { + /* + * Update pg_database.datfrozenxid, and truncate pg_xact if possible. + * (autovacuum.c does this for itself.) + */ + vac_update_datfrozenxid(); + } + + /* + * Clean up working storage --- note we must do this after + * StartTransactionCommand, else we might be trying to delete the active + * context! + */ + MemoryContextDelete(vac_context); + vac_context = NULL; +} + +/* + * Check if a given relation can be safely vacuumed or analyzed. If the + * user is not the relation owner, issue a WARNING log message and return + * false to let the caller decide what to do with this relation. This + * routine is used to decide if a relation can be processed for VACUUM or + * ANALYZE. + */ +bool +vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, bits32 options) +{ + char *relname; + + Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0); + + /* + * Check permissions. + * + * We allow the user to vacuum or analyze a table if he is superuser, the + * table owner, or the database owner (but in the latter case, only if + * it's not a shared relation). pg_class_ownercheck includes the + * superuser case. + * + * Note we choose to treat permissions failure as a WARNING and keep + * trying to vacuum or analyze the rest of the DB --- is this appropriate? + */ + if (pg_class_ownercheck(relid, GetUserId()) || + (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !reltuple->relisshared)) + return true; + + relname = NameStr(reltuple->relname); + + if ((options & VACOPT_VACUUM) != 0) + { + if (reltuple->relisshared) + ereport(WARNING, + (errmsg("skipping \"%s\" --- only superuser can vacuum it", + relname))); + else if (reltuple->relnamespace == PG_CATALOG_NAMESPACE) + ereport(WARNING, + (errmsg("skipping \"%s\" --- only superuser or database owner can vacuum it", + relname))); + else + ereport(WARNING, + (errmsg("skipping \"%s\" --- only table or database owner can vacuum it", + relname))); + + /* + * For VACUUM ANALYZE, both logs could show up, but just generate + * information for VACUUM as that would be the first one to be + * processed. + */ + return false; + } + + if ((options & VACOPT_ANALYZE) != 0) + { + if (reltuple->relisshared) + ereport(WARNING, + (errmsg("skipping \"%s\" --- only superuser can analyze it", + relname))); + else if (reltuple->relnamespace == PG_CATALOG_NAMESPACE) + ereport(WARNING, + (errmsg("skipping \"%s\" --- only superuser or database owner can analyze it", + relname))); + else + ereport(WARNING, + (errmsg("skipping \"%s\" --- only table or database owner can analyze it", + relname))); + } + + return false; +} + + +/* + * vacuum_open_relation + * + * This routine is used for attempting to open and lock a relation which + * is going to be vacuumed or analyzed. If the relation cannot be opened + * or locked, a log is emitted if possible. + */ +Relation +vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, + bool verbose, LOCKMODE lmode) +{ + Relation rel; + bool rel_lock = true; + int elevel; + + Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0); + + /* + * Open the relation and get the appropriate lock on it. + * + * There's a race condition here: the relation may have gone away since + * the last time we saw it. If so, we don't need to vacuum or analyze it. + * + * If we've been asked not to wait for the relation lock, acquire it first + * in non-blocking mode, before calling try_relation_open(). + */ + if (!(options & VACOPT_SKIP_LOCKED)) + rel = try_relation_open(relid, lmode); + else if (ConditionalLockRelationOid(relid, lmode)) + rel = try_relation_open(relid, NoLock); + else + { + rel = NULL; + rel_lock = false; + } + + /* if relation is opened, leave */ + if (rel) + return rel; + + /* + * Relation could not be opened, hence generate if possible a log + * informing on the situation. + * + * If the RangeVar is not defined, we do not have enough information to + * provide a meaningful log statement. Chances are that the caller has + * intentionally not provided this information so that this logging is + * skipped, anyway. + */ + if (relation == NULL) + return NULL; + + /* + * Determine the log level. + * + * For manual VACUUM or ANALYZE, we emit a WARNING to match the log + * statements in the permission checks; otherwise, only log if the caller + * so requested. + */ + if (!IsAutoVacuumWorkerProcess()) + elevel = WARNING; + else if (verbose) + elevel = LOG; + else + return NULL; + + if ((options & VACOPT_VACUUM) != 0) + { + if (!rel_lock) + ereport(elevel, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", + relation->relname))); + else + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + relation->relname))); + + /* + * For VACUUM ANALYZE, both logs could show up, but just generate + * information for VACUUM as that would be the first one to be + * processed. + */ + return NULL; + } + + if ((options & VACOPT_ANALYZE) != 0) + { + if (!rel_lock) + ereport(elevel, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + relation->relname))); + else + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + relation->relname))); + } + + return NULL; +} + + +/* + * Given a VacuumRelation, fill in the table OID if it wasn't specified, + * and optionally add VacuumRelations for partitions of the table. + * + * If a VacuumRelation does not have an OID supplied and is a partitioned + * table, an extra entry will be added to the output for each partition. + * Presently, only autovacuum supplies OIDs when calling vacuum(), and + * it does not want us to expand partitioned tables. + * + * We take care not to modify the input data structure, but instead build + * new VacuumRelation(s) to return. (But note that they will reference + * unmodified parts of the input, eg column lists.) New data structures + * are made in vac_context. + */ +static List * +expand_vacuum_rel(VacuumRelation *vrel, int options) +{ + List *vacrels = NIL; + MemoryContext oldcontext; + + /* If caller supplied OID, there's nothing we need do here. */ + if (OidIsValid(vrel->oid)) + { + oldcontext = MemoryContextSwitchTo(vac_context); + vacrels = lappend(vacrels, vrel); + MemoryContextSwitchTo(oldcontext); + } + else + { + /* Process a specific relation, and possibly partitions thereof */ + Oid relid; + HeapTuple tuple; + Form_pg_class classForm; + bool include_parts; + int rvr_opts; + + /* + * Since autovacuum workers supply OIDs when calling vacuum(), no + * autovacuum worker should reach this code. + */ + Assert(!IsAutoVacuumWorkerProcess()); + + /* + * We transiently take AccessShareLock to protect the syscache lookup + * below, as well as find_all_inheritors's expectation that the caller + * holds some lock on the starting relation. + */ + rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + rvr_opts, + NULL, NULL); + + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (!OidIsValid(relid)) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + return vacrels; + } + + /* + * To check whether the relation is a partitioned table and its + * ownership, fetch its syscache entry. + */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + classForm = (Form_pg_class) GETSTRUCT(tuple); + + /* + * Make a returnable VacuumRelation for this rel if user is a proper + * owner. + */ + if (vacuum_is_relation_owner(relid, classForm, options)) + { + oldcontext = MemoryContextSwitchTo(vac_context); + vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation, + relid, + vrel->va_cols)); + MemoryContextSwitchTo(oldcontext); + } + + + include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE); + ReleaseSysCache(tuple); + + /* + * If it is, make relation list entries for its partitions. Note that + * the list returned by find_all_inheritors() includes the passed-in + * OID, so we have to skip that. There's no point in taking locks on + * the individual partitions yet, and doing so would just add + * unnecessary deadlock risk. For this last reason we do not check + * yet the ownership of the partitions, which get added to the list to + * process. Ownership will be checked later on anyway. + */ + if (include_parts) + { + List *part_oids = find_all_inheritors(relid, NoLock, NULL); + ListCell *part_lc; + + foreach(part_lc, part_oids) + { + Oid part_oid = lfirst_oid(part_lc); + + if (part_oid == relid) + continue; /* ignore original table */ + + /* + * We omit a RangeVar since it wouldn't be appropriate to + * complain about failure to open one of these relations + * later. + */ + oldcontext = MemoryContextSwitchTo(vac_context); + vacrels = lappend(vacrels, makeVacuumRelation(NULL, + part_oid, + vrel->va_cols)); + MemoryContextSwitchTo(oldcontext); + } + } + + /* + * Release lock again. This means that by the time we actually try to + * process the table, it might be gone or renamed. In the former case + * we'll silently ignore it; in the latter case we'll process it + * anyway, but we must beware that the RangeVar doesn't necessarily + * identify it anymore. This isn't ideal, perhaps, but there's little + * practical alternative, since we're typically going to commit this + * transaction and begin a new one between now and then. Moreover, + * holding locks on multiple relations would create significant risk + * of deadlock. + */ + UnlockRelationOid(relid, AccessShareLock); + } + + return vacrels; +} + +/* + * Construct a list of VacuumRelations for all vacuumable rels in + * the current database. The list is built in vac_context. + */ +static List * +get_all_vacuum_rels(int options) +{ + List *vacrels = NIL; + Relation pgclass; + TableScanDesc scan; + HeapTuple tuple; + + pgclass = table_open(RelationRelationId, AccessShareLock); + + scan = table_beginscan_catalog(pgclass, 0, NULL); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); + MemoryContext oldcontext; + Oid relid = classForm->oid; + + /* check permissions of relation */ + if (!vacuum_is_relation_owner(relid, classForm, options)) + continue; + + /* + * We include partitioned tables here; depending on which operation is + * to be performed, caller will decide whether to process or ignore + * them. + */ + if (classForm->relkind != RELKIND_RELATION && + classForm->relkind != RELKIND_MATVIEW && + classForm->relkind != RELKIND_PARTITIONED_TABLE) + continue; + + /* + * Build VacuumRelation(s) specifying the table OIDs to be processed. + * We omit a RangeVar since it wouldn't be appropriate to complain + * about failure to open one of these relations later. + */ + oldcontext = MemoryContextSwitchTo(vac_context); + vacrels = lappend(vacrels, makeVacuumRelation(NULL, + relid, + NIL)); + MemoryContextSwitchTo(oldcontext); + } + + table_endscan(scan); + table_close(pgclass, AccessShareLock); + + return vacrels; +} + +/* + * vacuum_set_xid_limits() -- compute oldestXmin and freeze cutoff points + * + * Input parameters are the target relation, applicable freeze age settings. + * + * The output parameters are: + * - oldestXmin is the cutoff value used to distinguish whether tuples are + * DEAD or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum). + * - freezeLimit is the Xid below which all Xids are replaced by + * FrozenTransactionId during vacuum. + * - xidFullScanLimit (computed from freeze_table_age parameter) + * represents a minimum Xid value; a table whose relfrozenxid is older than + * this will have a full-table vacuum applied to it, to freeze tuples across + * the whole table. Vacuuming a table younger than this value can use a + * partial scan. + * - multiXactCutoff is the value below which all MultiXactIds are removed from + * Xmax. + * - mxactFullScanLimit is a value against which a table's relminmxid value is + * compared to produce a full-table vacuum, as with xidFullScanLimit. + * + * xidFullScanLimit and mxactFullScanLimit can be passed as NULL if caller is + * not interested. + */ +void +vacuum_set_xid_limits(Relation rel, + int freeze_min_age, + int freeze_table_age, + int multixact_freeze_min_age, + int multixact_freeze_table_age, + TransactionId *oldestXmin, + TransactionId *freezeLimit, + TransactionId *xidFullScanLimit, + MultiXactId *multiXactCutoff, + MultiXactId *mxactFullScanLimit) +{ + int freezemin; + int mxid_freezemin; + int effective_multixact_freeze_max_age; + TransactionId limit; + TransactionId safeLimit; + MultiXactId oldestMxact; + MultiXactId mxactLimit; + MultiXactId safeMxactLimit; + + /* + * We can always ignore processes running lazy vacuum. This is because we + * use these values only for deciding which tuples we must keep in the + * tables. Since lazy vacuum doesn't write its XID anywhere (usually no + * XID assigned), it's safe to ignore it. In theory it could be + * problematic to ignore lazy vacuums in a full vacuum, but keep in mind + * that only one vacuum process can be working on a particular table at + * any time, and that each vacuum is always an independent transaction. + */ + *oldestXmin = GetOldestNonRemovableTransactionId(rel); + + if (OldSnapshotThresholdActive()) + { + TransactionId limit_xmin; + TimestampTz limit_ts; + + if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel, + &limit_xmin, &limit_ts)) + { + /* + * TODO: We should only set the threshold if we are pruning on the + * basis of the increased limits. Not as crucial here as it is + * for opportunistic pruning (which often happens at a much higher + * frequency), but would still be a significant improvement. + */ + SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin); + *oldestXmin = limit_xmin; + } + } + + Assert(TransactionIdIsNormal(*oldestXmin)); + + /* + * Determine the minimum freeze age to use: as specified by the caller, or + * vacuum_freeze_min_age, but in any case not more than half + * autovacuum_freeze_max_age, so that autovacuums to prevent XID + * wraparound won't occur too frequently. + */ + freezemin = freeze_min_age; + if (freezemin < 0) + freezemin = vacuum_freeze_min_age; + freezemin = Min(freezemin, autovacuum_freeze_max_age / 2); + Assert(freezemin >= 0); + + /* + * Compute the cutoff XID, being careful not to generate a "permanent" XID + */ + limit = *oldestXmin - freezemin; + if (!TransactionIdIsNormal(limit)) + limit = FirstNormalTransactionId; + + /* + * If oldestXmin is very far back (in practice, more than + * autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum + * freeze age of zero. + */ + safeLimit = ReadNextTransactionId() - autovacuum_freeze_max_age; + if (!TransactionIdIsNormal(safeLimit)) + safeLimit = FirstNormalTransactionId; + + if (TransactionIdPrecedes(limit, safeLimit)) + { + ereport(WARNING, + (errmsg("oldest xmin is far in the past"), + errhint("Close open transactions soon to avoid wraparound problems.\n" + "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + limit = *oldestXmin; + } + + *freezeLimit = limit; + + /* + * Compute the multixact age for which freezing is urgent. This is + * normally autovacuum_multixact_freeze_max_age, but may be less if we are + * short of multixact member space. + */ + effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold(); + + /* + * Determine the minimum multixact freeze age to use: as specified by + * caller, or vacuum_multixact_freeze_min_age, but in any case not more + * than half effective_multixact_freeze_max_age, so that autovacuums to + * prevent MultiXact wraparound won't occur too frequently. + */ + mxid_freezemin = multixact_freeze_min_age; + if (mxid_freezemin < 0) + mxid_freezemin = vacuum_multixact_freeze_min_age; + mxid_freezemin = Min(mxid_freezemin, + effective_multixact_freeze_max_age / 2); + Assert(mxid_freezemin >= 0); + + /* compute the cutoff multi, being careful to generate a valid value */ + oldestMxact = GetOldestMultiXactId(); + mxactLimit = oldestMxact - mxid_freezemin; + if (mxactLimit < FirstMultiXactId) + mxactLimit = FirstMultiXactId; + + safeMxactLimit = + ReadNextMultiXactId() - effective_multixact_freeze_max_age; + if (safeMxactLimit < FirstMultiXactId) + safeMxactLimit = FirstMultiXactId; + + if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit)) + { + ereport(WARNING, + (errmsg("oldest multixact is far in the past"), + errhint("Close open transactions with multixacts soon to avoid wraparound problems."))); + /* Use the safe limit, unless an older mxact is still running */ + if (MultiXactIdPrecedes(oldestMxact, safeMxactLimit)) + mxactLimit = oldestMxact; + else + mxactLimit = safeMxactLimit; + } + + *multiXactCutoff = mxactLimit; + + if (xidFullScanLimit != NULL) + { + int freezetable; + + Assert(mxactFullScanLimit != NULL); + + /* + * Determine the table freeze age to use: as specified by the caller, + * or vacuum_freeze_table_age, but in any case not more than + * autovacuum_freeze_max_age * 0.95, so that if you have e.g nightly + * VACUUM schedule, the nightly VACUUM gets a chance to freeze tuples + * before anti-wraparound autovacuum is launched. + */ + freezetable = freeze_table_age; + if (freezetable < 0) + freezetable = vacuum_freeze_table_age; + freezetable = Min(freezetable, autovacuum_freeze_max_age * 0.95); + Assert(freezetable >= 0); + + /* + * Compute XID limit causing a full-table vacuum, being careful not to + * generate a "permanent" XID. + */ + limit = ReadNextTransactionId() - freezetable; + if (!TransactionIdIsNormal(limit)) + limit = FirstNormalTransactionId; + + *xidFullScanLimit = limit; + + /* + * Similar to the above, determine the table freeze age to use for + * multixacts: as specified by the caller, or + * vacuum_multixact_freeze_table_age, but in any case not more than + * autovacuum_multixact_freeze_table_age * 0.95, so that if you have + * e.g. nightly VACUUM schedule, the nightly VACUUM gets a chance to + * freeze multixacts before anti-wraparound autovacuum is launched. + */ + freezetable = multixact_freeze_table_age; + if (freezetable < 0) + freezetable = vacuum_multixact_freeze_table_age; + freezetable = Min(freezetable, + effective_multixact_freeze_max_age * 0.95); + Assert(freezetable >= 0); + + /* + * Compute MultiXact limit causing a full-table vacuum, being careful + * to generate a valid MultiXact value. + */ + mxactLimit = ReadNextMultiXactId() - freezetable; + if (mxactLimit < FirstMultiXactId) + mxactLimit = FirstMultiXactId; + + *mxactFullScanLimit = mxactLimit; + } + else + { + Assert(mxactFullScanLimit == NULL); + } +} + +/* + * vacuum_xid_failsafe_check() -- Used by VACUUM's wraparound failsafe + * mechanism to determine if its table's relfrozenxid and relminmxid are now + * dangerously far in the past. + * + * Input parameters are the target relation's relfrozenxid and relminmxid. + * + * When we return true, VACUUM caller triggers the failsafe. + */ +bool +vacuum_xid_failsafe_check(TransactionId relfrozenxid, MultiXactId relminmxid) +{ + TransactionId xid_skip_limit; + MultiXactId multi_skip_limit; + int skip_index_vacuum; + + Assert(TransactionIdIsNormal(relfrozenxid)); + Assert(MultiXactIdIsValid(relminmxid)); + + /* + * Determine the index skipping age to use. In any case no less than + * autovacuum_freeze_max_age * 1.05. + */ + skip_index_vacuum = Max(vacuum_failsafe_age, autovacuum_freeze_max_age * 1.05); + + xid_skip_limit = ReadNextTransactionId() - skip_index_vacuum; + if (!TransactionIdIsNormal(xid_skip_limit)) + xid_skip_limit = FirstNormalTransactionId; + + if (TransactionIdPrecedes(relfrozenxid, xid_skip_limit)) + { + /* The table's relfrozenxid is too old */ + return true; + } + + /* + * Similar to above, determine the index skipping age to use for + * multixact. In any case no less than autovacuum_multixact_freeze_max_age * + * 1.05. + */ + skip_index_vacuum = Max(vacuum_multixact_failsafe_age, + autovacuum_multixact_freeze_max_age * 1.05); + + multi_skip_limit = ReadNextMultiXactId() - skip_index_vacuum; + if (multi_skip_limit < FirstMultiXactId) + multi_skip_limit = FirstMultiXactId; + + if (MultiXactIdPrecedes(relminmxid, multi_skip_limit)) + { + /* The table's relminmxid is too old */ + return true; + } + + return false; +} + +/* + * vac_estimate_reltuples() -- estimate the new value for pg_class.reltuples + * + * If we scanned the whole relation then we should just use the count of + * live tuples seen; but if we did not, we should not blindly extrapolate + * from that number, since VACUUM may have scanned a quite nonrandom + * subset of the table. When we have only partial information, we take + * the old value of pg_class.reltuples/pg_class.relpages as a measurement + * of the tuple density in the unscanned pages. + * + * Note: scanned_tuples should count only *live* tuples, since + * pg_class.reltuples is defined that way. + */ +double +vac_estimate_reltuples(Relation relation, + BlockNumber total_pages, + BlockNumber scanned_pages, + double scanned_tuples) +{ + BlockNumber old_rel_pages = relation->rd_rel->relpages; + double old_rel_tuples = relation->rd_rel->reltuples; + double old_density; + double unscanned_pages; + double total_tuples; + + /* If we did scan the whole table, just use the count as-is */ + if (scanned_pages >= total_pages) + return scanned_tuples; + + /* + * If scanned_pages is zero but total_pages isn't, keep the existing value + * of reltuples. (Note: we might be returning -1 in this case.) + */ + if (scanned_pages == 0) + return old_rel_tuples; + + /* + * If old density is unknown, we can't do much except scale up + * scanned_tuples to match total_pages. + */ + if (old_rel_tuples < 0 || old_rel_pages == 0) + return floor((scanned_tuples / scanned_pages) * total_pages + 0.5); + + /* + * Okay, we've covered the corner cases. The normal calculation is to + * convert the old measurement to a density (tuples per page), then + * estimate the number of tuples in the unscanned pages using that figure, + * and finally add on the number of tuples in the scanned pages. + */ + old_density = old_rel_tuples / old_rel_pages; + unscanned_pages = (double) total_pages - (double) scanned_pages; + total_tuples = old_density * unscanned_pages + scanned_tuples; + return floor(total_tuples + 0.5); +} + + +/* + * vac_update_relstats() -- update statistics for one relation + * + * Update the whole-relation statistics that are kept in its pg_class + * row. There are additional stats that will be updated if we are + * doing ANALYZE, but we always update these stats. This routine works + * for both index and heap relation entries in pg_class. + * + * We violate transaction semantics here by overwriting the rel's + * existing pg_class tuple with the new values. This is reasonably + * safe as long as we're sure that the new values are correct whether or + * not this transaction commits. The reason for doing this is that if + * we updated these tuples in the usual way, vacuuming pg_class itself + * wouldn't work very well --- by the time we got done with a vacuum + * cycle, most of the tuples in pg_class would've been obsoleted. Of + * course, this only works for fixed-size not-null columns, but these are. + * + * Another reason for doing it this way is that when we are in a lazy + * VACUUM and have PROC_IN_VACUUM set, we mustn't do any regular updates. + * Somebody vacuuming pg_class might think they could delete a tuple + * marked with xmin = our xid. + * + * In addition to fundamentally nontransactional statistics such as + * relpages and relallvisible, we try to maintain certain lazily-updated + * DDL flags such as relhasindex, by clearing them if no longer correct. + * It's safe to do this in VACUUM, which can't run in parallel with + * CREATE INDEX/RULE/TRIGGER and can't be part of a transaction block. + * However, it's *not* safe to do it in an ANALYZE that's within an + * outer transaction, because for example the current transaction might + * have dropped the last index; then we'd think relhasindex should be + * cleared, but if the transaction later rolls back this would be wrong. + * So we refrain from updating the DDL flags if we're inside an outer + * transaction. This is OK since postponing the flag maintenance is + * always allowable. + * + * Note: num_tuples should count only *live* tuples, since + * pg_class.reltuples is defined that way. + * + * This routine is shared by VACUUM and ANALYZE. + */ +void +vac_update_relstats(Relation relation, + BlockNumber num_pages, double num_tuples, + BlockNumber num_all_visible_pages, + bool hasindex, TransactionId frozenxid, + MultiXactId minmulti, + bool in_outer_xact) +{ + Oid relid = RelationGetRelid(relation); + Relation rd; + HeapTuple ctup; + Form_pg_class pgcform; + bool dirty; + + rd = table_open(RelationRelationId, RowExclusiveLock); + + /* Fetch a copy of the tuple to scribble on */ + ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(ctup)) + elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", + relid); + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + /* Apply statistical updates, if any, to copied tuple */ + + dirty = false; + if (pgcform->relpages != (int32) num_pages) + { + pgcform->relpages = (int32) num_pages; + dirty = true; + } + if (pgcform->reltuples != (float4) num_tuples) + { + pgcform->reltuples = (float4) num_tuples; + dirty = true; + } + if (pgcform->relallvisible != (int32) num_all_visible_pages) + { + pgcform->relallvisible = (int32) num_all_visible_pages; + dirty = true; + } + + /* Apply DDL updates, but not inside an outer transaction (see above) */ + + if (!in_outer_xact) + { + /* + * If we didn't find any indexes, reset relhasindex. + */ + if (pgcform->relhasindex && !hasindex) + { + pgcform->relhasindex = false; + dirty = true; + } + + /* We also clear relhasrules and relhastriggers if needed */ + if (pgcform->relhasrules && relation->rd_rules == NULL) + { + pgcform->relhasrules = false; + dirty = true; + } + if (pgcform->relhastriggers && relation->trigdesc == NULL) + { + pgcform->relhastriggers = false; + dirty = true; + } + } + + /* + * Update relfrozenxid, unless caller passed InvalidTransactionId + * indicating it has no new data. + * + * Ordinarily, we don't let relfrozenxid go backwards: if things are + * working correctly, the only way the new frozenxid could be older would + * be if a previous VACUUM was done with a tighter freeze_min_age, in + * which case we don't want to forget the work it already did. However, + * if the stored relfrozenxid is "in the future", then it must be corrupt + * and it seems best to overwrite it with the cutoff we used this time. + * This should match vac_update_datfrozenxid() concerning what we consider + * to be "in the future". + */ + if (TransactionIdIsNormal(frozenxid) && + pgcform->relfrozenxid != frozenxid && + (TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) || + TransactionIdPrecedes(ReadNextTransactionId(), + pgcform->relfrozenxid))) + { + pgcform->relfrozenxid = frozenxid; + dirty = true; + } + + /* Similarly for relminmxid */ + if (MultiXactIdIsValid(minmulti) && + pgcform->relminmxid != minmulti && + (MultiXactIdPrecedes(pgcform->relminmxid, minmulti) || + MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid))) + { + pgcform->relminmxid = minmulti; + dirty = true; + } + + /* If anything changed, write out the tuple. */ + if (dirty) + heap_inplace_update(rd, ctup); + + table_close(rd, RowExclusiveLock); +} + + +/* + * vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB + * + * Update pg_database's datfrozenxid entry for our database to be the + * minimum of the pg_class.relfrozenxid values. + * + * Similarly, update our datminmxid to be the minimum of the + * pg_class.relminmxid values. + * + * If we are able to advance either pg_database value, also try to + * truncate pg_xact and pg_multixact. + * + * We violate transaction semantics here by overwriting the database's + * existing pg_database tuple with the new values. This is reasonably + * safe since the new values are correct whether or not this transaction + * commits. As with vac_update_relstats, this avoids leaving dead tuples + * behind after a VACUUM. + */ +void +vac_update_datfrozenxid(void) +{ + HeapTuple tuple; + Form_pg_database dbform; + Relation relation; + SysScanDesc scan; + HeapTuple classTup; + TransactionId newFrozenXid; + MultiXactId newMinMulti; + TransactionId lastSaneFrozenXid; + MultiXactId lastSaneMinMulti; + bool bogus = false; + bool dirty = false; + ScanKeyData key[1]; + + /* + * Restrict this task to one backend per database. This avoids race + * conditions that would move datfrozenxid or datminmxid backward. It + * avoids calling vac_truncate_clog() with a datfrozenxid preceding a + * datfrozenxid passed to an earlier vac_truncate_clog() call. + */ + LockDatabaseFrozenIds(ExclusiveLock); + + /* + * Initialize the "min" calculation with + * GetOldestNonRemovableTransactionId(), which is a reasonable + * approximation to the minimum relfrozenxid for not-yet-committed + * pg_class entries for new tables; see AddNewRelationTuple(). So we + * cannot produce a wrong minimum by starting with this. + */ + newFrozenXid = GetOldestNonRemovableTransactionId(NULL); + + /* + * Similarly, initialize the MultiXact "min" with the value that would be + * used on pg_class for new tables. See AddNewRelationTuple(). + */ + newMinMulti = GetOldestMultiXactId(); + + /* + * Identify the latest relfrozenxid and relminmxid values that we could + * validly see during the scan. These are conservative values, but it's + * not really worth trying to be more exact. + */ + lastSaneFrozenXid = ReadNextTransactionId(); + lastSaneMinMulti = ReadNextMultiXactId(); + + /* + * We must seqscan pg_class to find the minimum Xid, because there is no + * index that can help us here. + */ + relation = table_open(RelationRelationId, AccessShareLock); + + scan = systable_beginscan(relation, InvalidOid, false, + NULL, 0, NULL); + + while ((classTup = systable_getnext(scan)) != NULL) + { + Form_pg_class classForm = (Form_pg_class) GETSTRUCT(classTup); + + /* + * Only consider relations able to hold unfrozen XIDs (anything else + * should have InvalidTransactionId in relfrozenxid anyway). + */ + if (classForm->relkind != RELKIND_RELATION && + classForm->relkind != RELKIND_MATVIEW && + classForm->relkind != RELKIND_TOASTVALUE) + { + Assert(!TransactionIdIsValid(classForm->relfrozenxid)); + Assert(!MultiXactIdIsValid(classForm->relminmxid)); + continue; + } + + /* + * Some table AMs might not need per-relation xid / multixid horizons. + * It therefore seems reasonable to allow relfrozenxid and relminmxid + * to not be set (i.e. set to their respective Invalid*Id) + * independently. Thus validate and compute horizon for each only if + * set. + * + * If things are working properly, no relation should have a + * relfrozenxid or relminmxid that is "in the future". However, such + * cases have been known to arise due to bugs in pg_upgrade. If we + * see any entries that are "in the future", chicken out and don't do + * anything. This ensures we won't truncate clog & multixact SLRUs + * before those relations have been scanned and cleaned up. + */ + + if (TransactionIdIsValid(classForm->relfrozenxid)) + { + Assert(TransactionIdIsNormal(classForm->relfrozenxid)); + + /* check for values in the future */ + if (TransactionIdPrecedes(lastSaneFrozenXid, classForm->relfrozenxid)) + { + bogus = true; + break; + } + + /* determine new horizon */ + if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid)) + newFrozenXid = classForm->relfrozenxid; + } + + if (MultiXactIdIsValid(classForm->relminmxid)) + { + /* check for values in the future */ + if (MultiXactIdPrecedes(lastSaneMinMulti, classForm->relminmxid)) + { + bogus = true; + break; + } + + /* determine new horizon */ + if (MultiXactIdPrecedes(classForm->relminmxid, newMinMulti)) + newMinMulti = classForm->relminmxid; + } + } + + /* we're done with pg_class */ + systable_endscan(scan); + table_close(relation, AccessShareLock); + + /* chicken out if bogus data found */ + if (bogus) + return; + + Assert(TransactionIdIsNormal(newFrozenXid)); + Assert(MultiXactIdIsValid(newMinMulti)); + + /* Now fetch the pg_database tuple we need to update. */ + relation = table_open(DatabaseRelationId, RowExclusiveLock); + + /* + * Get the pg_database tuple to scribble on. Note that this does not + * directly rely on the syscache to avoid issues with flattened toast + * values for the in-place update. + */ + ScanKeyInit(&key[0], + Anum_pg_database_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(MyDatabaseId)); + + scan = systable_beginscan(relation, DatabaseOidIndexId, true, + NULL, 1, key); + tuple = systable_getnext(scan); + tuple = heap_copytuple(tuple); + systable_endscan(scan); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for database %u", MyDatabaseId); + + dbform = (Form_pg_database) GETSTRUCT(tuple); + + /* + * As in vac_update_relstats(), we ordinarily don't want to let + * datfrozenxid go backward; but if it's "in the future" then it must be + * corrupt and it seems best to overwrite it. + */ + if (dbform->datfrozenxid != newFrozenXid && + (TransactionIdPrecedes(dbform->datfrozenxid, newFrozenXid) || + TransactionIdPrecedes(lastSaneFrozenXid, dbform->datfrozenxid))) + { + dbform->datfrozenxid = newFrozenXid; + dirty = true; + } + else + newFrozenXid = dbform->datfrozenxid; + + /* Ditto for datminmxid */ + if (dbform->datminmxid != newMinMulti && + (MultiXactIdPrecedes(dbform->datminmxid, newMinMulti) || + MultiXactIdPrecedes(lastSaneMinMulti, dbform->datminmxid))) + { + dbform->datminmxid = newMinMulti; + dirty = true; + } + else + newMinMulti = dbform->datminmxid; + + if (dirty) + heap_inplace_update(relation, tuple); + + heap_freetuple(tuple); + table_close(relation, RowExclusiveLock); + + /* + * If we were able to advance datfrozenxid or datminmxid, see if we can + * truncate pg_xact and/or pg_multixact. Also do it if the shared + * XID-wrap-limit info is stale, since this action will update that too. + */ + if (dirty || ForceTransactionIdLimitUpdate()) + vac_truncate_clog(newFrozenXid, newMinMulti, + lastSaneFrozenXid, lastSaneMinMulti); +} + + +/* + * vac_truncate_clog() -- attempt to truncate the commit log + * + * Scan pg_database to determine the system-wide oldest datfrozenxid, + * and use it to truncate the transaction commit log (pg_xact). + * Also update the XID wrap limit info maintained by varsup.c. + * Likewise for datminmxid. + * + * The passed frozenXID and minMulti are the updated values for my own + * pg_database entry. They're used to initialize the "min" calculations. + * The caller also passes the "last sane" XID and MXID, since it has + * those at hand already. + * + * This routine is only invoked when we've managed to change our + * DB's datfrozenxid/datminmxid values, or we found that the shared + * XID-wrap-limit info is stale. + */ +static void +vac_truncate_clog(TransactionId frozenXID, + MultiXactId minMulti, + TransactionId lastSaneFrozenXid, + MultiXactId lastSaneMinMulti) +{ + TransactionId nextXID = ReadNextTransactionId(); + Relation relation; + TableScanDesc scan; + HeapTuple tuple; + Oid oldestxid_datoid; + Oid minmulti_datoid; + bool bogus = false; + bool frozenAlreadyWrapped = false; + + /* Restrict task to one backend per cluster; see SimpleLruTruncate(). */ + LWLockAcquire(WrapLimitsVacuumLock, LW_EXCLUSIVE); + + /* init oldest datoids to sync with my frozenXID/minMulti values */ + oldestxid_datoid = MyDatabaseId; + minmulti_datoid = MyDatabaseId; + + /* + * Scan pg_database to compute the minimum datfrozenxid/datminmxid + * + * Since vac_update_datfrozenxid updates datfrozenxid/datminmxid in-place, + * the values could change while we look at them. Fetch each one just + * once to ensure sane behavior of the comparison logic. (Here, as in + * many other places, we assume that fetching or updating an XID in shared + * storage is atomic.) + * + * Note: we need not worry about a race condition with new entries being + * inserted by CREATE DATABASE. Any such entry will have a copy of some + * existing DB's datfrozenxid, and that source DB cannot be ours because + * of the interlock against copying a DB containing an active backend. + * Hence the new entry will not reduce the minimum. Also, if two VACUUMs + * concurrently modify the datfrozenxid's of different databases, the + * worst possible outcome is that pg_xact is not truncated as aggressively + * as it could be. + */ + relation = table_open(DatabaseRelationId, AccessShareLock); + + scan = table_beginscan_catalog(relation, 0, NULL); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + volatile FormData_pg_database *dbform = (Form_pg_database) GETSTRUCT(tuple); + TransactionId datfrozenxid = dbform->datfrozenxid; + TransactionId datminmxid = dbform->datminmxid; + + Assert(TransactionIdIsNormal(datfrozenxid)); + Assert(MultiXactIdIsValid(datminmxid)); + + /* + * If things are working properly, no database should have a + * datfrozenxid or datminmxid that is "in the future". However, such + * cases have been known to arise due to bugs in pg_upgrade. If we + * see any entries that are "in the future", chicken out and don't do + * anything. This ensures we won't truncate clog before those + * databases have been scanned and cleaned up. (We will issue the + * "already wrapped" warning if appropriate, though.) + */ + if (TransactionIdPrecedes(lastSaneFrozenXid, datfrozenxid) || + MultiXactIdPrecedes(lastSaneMinMulti, datminmxid)) + bogus = true; + + if (TransactionIdPrecedes(nextXID, datfrozenxid)) + frozenAlreadyWrapped = true; + else if (TransactionIdPrecedes(datfrozenxid, frozenXID)) + { + frozenXID = datfrozenxid; + oldestxid_datoid = dbform->oid; + } + + if (MultiXactIdPrecedes(datminmxid, minMulti)) + { + minMulti = datminmxid; + minmulti_datoid = dbform->oid; + } + } + + table_endscan(scan); + + table_close(relation, AccessShareLock); + + /* + * Do not truncate CLOG if we seem to have suffered wraparound already; + * the computed minimum XID might be bogus. This case should now be + * impossible due to the defenses in GetNewTransactionId, but we keep the + * test anyway. + */ + if (frozenAlreadyWrapped) + { + ereport(WARNING, + (errmsg("some databases have not been vacuumed in over 2 billion transactions"), + errdetail("You might have already suffered transaction-wraparound data loss."))); + return; + } + + /* chicken out if data is bogus in any other way */ + if (bogus) + return; + + /* + * Advance the oldest value for commit timestamps before truncating, so + * that if a user requests a timestamp for a transaction we're truncating + * away right after this point, they get NULL instead of an ugly "file not + * found" error from slru.c. This doesn't matter for xact/multixact + * because they are not subject to arbitrary lookups from users. + */ + AdvanceOldestCommitTsXid(frozenXID); + + /* + * Truncate CLOG, multixact and CommitTs to the oldest computed value. + */ + TruncateCLOG(frozenXID, oldestxid_datoid); + TruncateCommitTs(frozenXID); + TruncateMultiXact(minMulti, minmulti_datoid); + + /* + * Update the wrap limit for GetNewTransactionId and creation of new + * MultiXactIds. Note: these functions will also signal the postmaster + * for an(other) autovac cycle if needed. XXX should we avoid possibly + * signaling twice? + */ + SetTransactionIdLimit(frozenXID, oldestxid_datoid); + SetMultiXactIdLimit(minMulti, minmulti_datoid, false); + + LWLockRelease(WrapLimitsVacuumLock); +} + + +/* + * vacuum_rel() -- vacuum one heap relation + * + * relid identifies the relation to vacuum. If relation is supplied, + * use the name therein for reporting any failure to open/lock the rel; + * do not use it once we've successfully opened the rel, since it might + * be stale. + * + * Returns true if it's okay to proceed with a requested ANALYZE + * operation on this table. + * + * Doing one heap at a time incurs extra overhead, since we need to + * check that the heap exists again just before we vacuum it. The + * reason that we do this is so that vacuuming can be spread across + * many small transactions. Otherwise, two-phase locking would require + * us to lock the entire database during one pass of the vacuum cleaner. + * + * At entry and exit, we are not inside a transaction. + */ +static bool +vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) +{ + LOCKMODE lmode; + Relation rel; + LockRelId lockrelid; + Oid toast_relid; + Oid save_userid; + int save_sec_context; + int save_nestlevel; + + Assert(params != NULL); + + /* Begin a transaction for vacuuming this relation */ + StartTransactionCommand(); + + if (!(params->options & VACOPT_FULL)) + { + /* + * In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets + * other concurrent VACUUMs know that they can ignore this one while + * determining their OldestXmin. (The reason we don't set it during a + * full VACUUM is exactly that we may have to run user-defined + * functions for functional indexes, and we want to make sure that if + * they use the snapshot set above, any tuples it requires can't get + * removed from other tables. An index function that depends on the + * contents of other tables is arguably broken, but we won't break it + * here by violating transaction semantics.) + * + * We also set the VACUUM_FOR_WRAPAROUND flag, which is passed down by + * autovacuum; it's used to avoid canceling a vacuum that was invoked + * in an emergency. + * + * Note: these flags remain set until CommitTransaction or + * AbortTransaction. We don't want to clear them until we reset + * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId() + * might appear to go backwards, which is probably Not Good. (We also + * set PROC_IN_VACUUM *before* taking our own snapshot, so that our + * xmin doesn't become visible ahead of setting the flag.) + */ + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + MyProc->statusFlags |= PROC_IN_VACUUM; + if (params->is_wraparound) + MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND; + ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; + LWLockRelease(ProcArrayLock); + } + + /* + * Need to acquire a snapshot to prevent pg_subtrans from being truncated, + * cutoff xids in local memory wrapping around, and to have updated xmin + * horizons. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + + /* + * Check for user-requested abort. Note we want this to be inside a + * transaction, so xact.c doesn't issue useless WARNING. + */ + CHECK_FOR_INTERRUPTS(); + + /* + * Determine the type of lock we want --- hard exclusive lock for a FULL + * vacuum, but just ShareUpdateExclusiveLock for concurrent vacuum. Either + * way, we can be sure that no other backend is vacuuming the same table. + */ + lmode = (params->options & VACOPT_FULL) ? + AccessExclusiveLock : ShareUpdateExclusiveLock; + + /* open the relation and get the appropriate lock on it */ + rel = vacuum_open_relation(relid, relation, params->options, + params->log_min_duration >= 0, lmode); + + /* leave if relation could not be opened or locked */ + if (!rel) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + return false; + } + + /* + * Check if relation needs to be skipped based on ownership. This check + * happens also when building the relation list to vacuum for a manual + * operation, and needs to be done additionally here as VACUUM could + * happen across multiple transactions where relation ownership could have + * changed in-between. Make sure to only generate logs for VACUUM in this + * case. + */ + if (!vacuum_is_relation_owner(RelationGetRelid(rel), + rel->rd_rel, + params->options & VACOPT_VACUUM)) + { + relation_close(rel, lmode); + PopActiveSnapshot(); + CommitTransactionCommand(); + return false; + } + + /* + * Check that it's of a vacuumable relkind. + */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW && + rel->rd_rel->relkind != RELKIND_TOASTVALUE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + { + ereport(WARNING, + (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", + RelationGetRelationName(rel)))); + relation_close(rel, lmode); + PopActiveSnapshot(); + CommitTransactionCommand(); + return false; + } + + /* + * Silently ignore tables that are temp tables of other backends --- + * trying to vacuum these will lead to great unhappiness, since their + * contents are probably not up-to-date on disk. (We don't throw a + * warning here; it would just lead to chatter during a database-wide + * VACUUM.) + */ + if (RELATION_IS_OTHER_TEMP(rel)) + { + relation_close(rel, lmode); + PopActiveSnapshot(); + CommitTransactionCommand(); + return false; + } + + /* + * Silently ignore partitioned tables as there is no work to be done. The + * useful work is on their child partitions, which have been queued up for + * us separately. + */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + relation_close(rel, lmode); + PopActiveSnapshot(); + CommitTransactionCommand(); + /* It's OK to proceed with ANALYZE on this table */ + return true; + } + + /* + * Get a session-level lock too. This will protect our access to the + * relation across multiple transactions, so that we can vacuum the + * relation's TOAST table (if any) secure in the knowledge that no one is + * deleting the parent relation. + * + * NOTE: this cannot block, even if someone else is waiting for access, + * because the lock manager knows that both lock requests are from the + * same process. + */ + lockrelid = rel->rd_lockInfo.lockRelId; + LockRelationIdForSession(&lockrelid, lmode); + + /* + * Set index_cleanup option based on index_cleanup reloption if it wasn't + * specified in VACUUM command, or when running in an autovacuum worker + */ + if (params->index_cleanup == VACOPTVALUE_UNSPECIFIED) + { + StdRdOptIndexCleanup vacuum_index_cleanup; + + if (rel->rd_options == NULL) + vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO; + else + vacuum_index_cleanup = + ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup; + + if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO) + params->index_cleanup = VACOPTVALUE_AUTO; + else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON) + params->index_cleanup = VACOPTVALUE_ENABLED; + else + { + Assert(vacuum_index_cleanup == + STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF); + params->index_cleanup = VACOPTVALUE_DISABLED; + } + } + + /* + * Set truncate option based on truncate reloption if it wasn't specified + * in VACUUM command, or when running in an autovacuum worker + */ + if (params->truncate == VACOPTVALUE_UNSPECIFIED) + { + if (rel->rd_options == NULL || + ((StdRdOptions *) rel->rd_options)->vacuum_truncate) + params->truncate = VACOPTVALUE_ENABLED; + else + params->truncate = VACOPTVALUE_DISABLED; + } + + /* + * Remember the relation's TOAST relation for later, if the caller asked + * us to process it. In VACUUM FULL, though, the toast table is + * automatically rebuilt by cluster_rel so we shouldn't recurse to it. + */ + if ((params->options & VACOPT_PROCESS_TOAST) != 0 && + (params->options & VACOPT_FULL) == 0) + toast_relid = rel->rd_rel->reltoastrelid; + else + toast_relid = InvalidOid; + + /* + * Switch to the table owner's userid, so that any index functions are run + * as that user. Also lock down security-restricted operations and + * arrange to make GUC variable changes local to this command. (This is + * unnecessary, but harmless, for lazy VACUUM.) + */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(rel->rd_rel->relowner, + save_sec_context | SECURITY_RESTRICTED_OPERATION); + save_nestlevel = NewGUCNestLevel(); + + /* + * Do the actual work --- either FULL or "lazy" vacuum + */ + if (params->options & VACOPT_FULL) + { + ClusterParams cluster_params = {0}; + + /* close relation before vacuuming, but hold lock until commit */ + relation_close(rel, NoLock); + rel = NULL; + + if ((params->options & VACOPT_VERBOSE) != 0) + cluster_params.options |= CLUOPT_VERBOSE; + + /* VACUUM FULL is now a variant of CLUSTER; see cluster.c */ + cluster_rel(relid, InvalidOid, &cluster_params); + } + else + table_relation_vacuum(rel, params, vac_strategy); + + /* Roll back any GUC changes executed by index functions */ + AtEOXact_GUC(false, save_nestlevel); + + /* Restore userid and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + + /* all done with this class, but hold lock until commit */ + if (rel) + relation_close(rel, NoLock); + + /* + * Complete the transaction and free all temporary memory used. + */ + PopActiveSnapshot(); + CommitTransactionCommand(); + + /* + * If the relation has a secondary toast rel, vacuum that too while we + * still hold the session lock on the main table. Note however that + * "analyze" will not get done on the toast table. This is good, because + * the toaster always uses hardcoded index access and statistics are + * totally unimportant for toast relations. + */ + if (toast_relid != InvalidOid) + vacuum_rel(toast_relid, NULL, params); + + /* + * Now release the session-level lock on the main table. + */ + UnlockRelationIdForSession(&lockrelid, lmode); + + /* Report that we really did it. */ + return true; +} + + +/* + * Open all the vacuumable indexes of the given relation, obtaining the + * specified kind of lock on each. Return an array of Relation pointers for + * the indexes into *Irel, and the number of indexes into *nindexes. + * + * We consider an index vacuumable if it is marked insertable (indisready). + * If it isn't, probably a CREATE INDEX CONCURRENTLY command failed early in + * execution, and what we have is too corrupt to be processable. We will + * vacuum even if the index isn't indisvalid; this is important because in a + * unique index, uniqueness checks will be performed anyway and had better not + * hit dangling index pointers. + */ +void +vac_open_indexes(Relation relation, LOCKMODE lockmode, + int *nindexes, Relation **Irel) +{ + List *indexoidlist; + ListCell *indexoidscan; + int i; + + Assert(lockmode != NoLock); + + indexoidlist = RelationGetIndexList(relation); + + /* allocate enough memory for all indexes */ + i = list_length(indexoidlist); + + if (i > 0) + *Irel = (Relation *) palloc(i * sizeof(Relation)); + else + *Irel = NULL; + + /* collect just the ready indexes */ + i = 0; + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirst_oid(indexoidscan); + Relation indrel; + + indrel = index_open(indexoid, lockmode); + if (indrel->rd_index->indisready) + (*Irel)[i++] = indrel; + else + index_close(indrel, lockmode); + } + + *nindexes = i; + + list_free(indexoidlist); +} + +/* + * Release the resources acquired by vac_open_indexes. Optionally release + * the locks (say NoLock to keep 'em). + */ +void +vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode) +{ + if (Irel == NULL) + return; + + while (nindexes--) + { + Relation ind = Irel[nindexes]; + + index_close(ind, lockmode); + } + pfree(Irel); +} + +/* + * vacuum_delay_point --- check for interrupts and cost-based delay. + * + * This should be called in each major loop of VACUUM processing, + * typically once per page processed. + */ +void +vacuum_delay_point(void) +{ + double msec = 0; + + /* Always check for interrupts */ + CHECK_FOR_INTERRUPTS(); + + if (!VacuumCostActive || InterruptPending) + return; + + /* + * For parallel vacuum, the delay is computed based on the shared cost + * balance. See compute_parallel_delay. + */ + if (VacuumSharedCostBalance != NULL) + msec = compute_parallel_delay(); + else if (VacuumCostBalance >= VacuumCostLimit) + msec = VacuumCostDelay * VacuumCostBalance / VacuumCostLimit; + + /* Nap if appropriate */ + if (msec > 0) + { + if (msec > VacuumCostDelay * 4) + msec = VacuumCostDelay * 4; + + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + msec, + WAIT_EVENT_VACUUM_DELAY); + ResetLatch(MyLatch); + + VacuumCostBalance = 0; + + /* update balance values for workers */ + AutoVacuumUpdateDelay(); + + /* Might have gotten an interrupt while sleeping */ + CHECK_FOR_INTERRUPTS(); + } +} + +/* + * Computes the vacuum delay for parallel workers. + * + * The basic idea of a cost-based delay for parallel vacuum is to allow each + * worker to sleep in proportion to the share of work it's done. We achieve this + * by allowing all parallel vacuum workers including the leader process to + * have a shared view of cost related parameters (mainly VacuumCostBalance). + * We allow each worker to update it as and when it has incurred any cost and + * then based on that decide whether it needs to sleep. We compute the time + * to sleep for a worker based on the cost it has incurred + * (VacuumCostBalanceLocal) and then reduce the VacuumSharedCostBalance by + * that amount. This avoids putting to sleep those workers which have done less + * I/O than other workers and therefore ensure that workers + * which are doing more I/O got throttled more. + * + * We allow a worker to sleep only if it has performed I/O above a certain + * threshold, which is calculated based on the number of active workers + * (VacuumActiveNWorkers), and the overall cost balance is more than + * VacuumCostLimit set by the system. Testing reveals that we achieve + * the required throttling if we force a worker that has done more than 50% + * of its share of work to sleep. + */ +static double +compute_parallel_delay(void) +{ + double msec = 0; + uint32 shared_balance; + int nworkers; + + /* Parallel vacuum must be active */ + Assert(VacuumSharedCostBalance); + + nworkers = pg_atomic_read_u32(VacuumActiveNWorkers); + + /* At least count itself */ + Assert(nworkers >= 1); + + /* Update the shared cost balance value atomically */ + shared_balance = pg_atomic_add_fetch_u32(VacuumSharedCostBalance, VacuumCostBalance); + + /* Compute the total local balance for the current worker */ + VacuumCostBalanceLocal += VacuumCostBalance; + + if ((shared_balance >= VacuumCostLimit) && + (VacuumCostBalanceLocal > 0.5 * ((double) VacuumCostLimit / nworkers))) + { + /* Compute sleep time based on the local cost balance */ + msec = VacuumCostDelay * VacuumCostBalanceLocal / VacuumCostLimit; + pg_atomic_sub_fetch_u32(VacuumSharedCostBalance, VacuumCostBalanceLocal); + VacuumCostBalanceLocal = 0; + } + + /* + * Reset the local balance as we accumulated it into the shared value. + */ + VacuumCostBalance = 0; + + return msec; +} + +/* + * A wrapper function of defGetBoolean(). + * + * This function returns VACOPTVALUE_ENABLED and VACOPTVALUE_DISABLED instead + * of true and false. + */ +static VacOptValue +get_vacoptval_from_boolean(DefElem *def) +{ + return defGetBoolean(def) ? VACOPTVALUE_ENABLED : VACOPTVALUE_DISABLED; +} |