📄 autovacuum.c
字号:
/*------------------------------------------------------------------------- * * autovacuum.c * * PostgreSQL Integrated Autovacuum Daemon * * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.5.2.6 2006/05/19 15:15:38 alvherre Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include <signal.h>#include <sys/types.h>#include <time.h>#include <unistd.h>#include "access/genam.h"#include "access/heapam.h"#include "access/xlog.h"#include "catalog/indexing.h"#include "catalog/namespace.h"#include "catalog/pg_autovacuum.h"#include "catalog/pg_database.h"#include "commands/vacuum.h"#include "libpq/hba.h"#include "libpq/pqsignal.h"#include "miscadmin.h"#include "pgstat.h"#include "postmaster/autovacuum.h"#include "postmaster/fork_process.h"#include "postmaster/postmaster.h"#include "storage/fd.h"#include "storage/ipc.h"#include "storage/proc.h"#include "storage/sinval.h"#include "tcop/tcopprot.h"#include "utils/flatfiles.h"#include "utils/fmgroids.h"#include "utils/memutils.h"#include "utils/ps_status.h"#include "utils/lsyscache.h"#include "utils/rel.h"#include "utils/relcache.h"/* * GUC parameters */bool autovacuum_start_daemon = false;int autovacuum_naptime;int autovacuum_vac_thresh;double autovacuum_vac_scale;int autovacuum_anl_thresh;double autovacuum_anl_scale;int autovacuum_vac_cost_delay;int autovacuum_vac_cost_limit;/* Flag to tell if we are in the autovacuum daemon process */static bool am_autovacuum = false;/* Last time autovac daemon started/stopped (only valid in postmaster) */static time_t last_autovac_start_time = 0;static time_t last_autovac_stop_time = 0;/* Memory context for long-lived data */static MemoryContext AutovacMemCxt;/* struct to keep list of candidate databases for vacuum */typedef struct autovac_dbase{ Oid oid; char *name; TransactionId frozenxid; TransactionId vacuumxid; PgStat_StatDBEntry *entry; int32 age;} autovac_dbase;/* struct to keep track of tables to vacuum and/or analyze */typedef struct autovac_table{ Oid relid; Oid toastrelid; bool dovacuum; bool doanalyze; int vacuum_cost_delay; int vacuum_cost_limit;} autovac_table;#ifdef EXEC_BACKENDstatic pid_t autovac_forkexec(void);#endifNON_EXEC_STATIC void AutoVacMain(int argc, char *argv[]);static void process_whole_db(void);static void do_autovacuum(PgStat_StatDBEntry *dbentry);static List *autovac_get_database_list(void);static void test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, Form_pg_class classForm, Form_pg_autovacuum avForm, List **vacuum_tables, List **toast_table_ids);static void autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze, bool freeze);static void autovac_report_activity(VacuumStmt *vacstmt, List *relids);/* * Main entry point for autovacuum controller process. * * This code is heavily based on pgarch.c, q.v. */intautovac_start(void){ time_t curtime; pid_t AutoVacPID; /* Do nothing if no autovacuum process needed */ if (!AutoVacuumingActive()) return 0; /* * Do nothing if too soon since last autovacuum exit. This limits how * often the daemon runs. Since the time per iteration can be quite * variable, it seems more useful to measure/control the time since last * subprocess exit than since last subprocess launch. * * However, we *also* check the time since last subprocess launch; this * prevents thrashing under fork-failure conditions. * * Note that since we will be re-called from the postmaster main loop, we * will get another chance later if we do nothing now. * * XXX todo: implement sleep scale factor that existed in contrib code. */ curtime = time(NULL); if ((unsigned int) (curtime - last_autovac_stop_time) < (unsigned int) autovacuum_naptime) return 0; if ((unsigned int) (curtime - last_autovac_start_time) < (unsigned int) autovacuum_naptime) return 0; last_autovac_start_time = curtime;#ifdef EXEC_BACKEND switch ((AutoVacPID = autovac_forkexec()))#else switch ((AutoVacPID = fork_process()))#endif { case -1: ereport(LOG, (errmsg("could not fork autovacuum process: %m"))); return 0;#ifndef EXEC_BACKEND case 0: /* in postmaster child ... */ /* Close the postmaster's sockets */ ClosePostmasterPorts(false); AutoVacMain(0, NULL); break;#endif default: return (int) AutoVacPID; } /* shouldn't get here */ return 0;}/* * autovac_stopped --- called by postmaster when subprocess exit is detected */voidautovac_stopped(void){ last_autovac_stop_time = time(NULL);}#ifdef EXEC_BACKEND/* * autovac_forkexec() * * Format up the arglist for the autovacuum process, then fork and exec. */static pid_tautovac_forkexec(void){ char *av[10]; int ac = 0; av[ac++] = "postgres"; av[ac++] = "-forkautovac"; av[ac++] = NULL; /* filled in by postmaster_forkexec */ av[ac] = NULL; Assert(ac < lengthof(av)); return postmaster_forkexec(ac, av);}#endif /* EXEC_BACKEND *//* * AutoVacMain */NON_EXEC_STATIC voidAutoVacMain(int argc, char *argv[]){ ListCell *cell; List *dblist; TransactionId nextXid; autovac_dbase *db; bool whole_db; sigjmp_buf local_sigjmp_buf; /* we are a postmaster subprocess now */ IsUnderPostmaster = true; am_autovacuum = true; /* reset MyProcPid */ MyProcPid = getpid(); /* Lose the postmaster's on-exit routines */ on_exit_reset(); /* Identify myself via ps */ init_ps_display("autovacuum process", "", ""); set_ps_display(""); SetProcessingMode(InitProcessing); /* * Set up signal handlers. We operate on databases much like a regular * backend, so we use the same signal handling. See equivalent code in * tcop/postgres.c. * * Currently, we don't pay attention to postgresql.conf changes that * happen during a single daemon iteration, so we can ignore SIGHUP. */ pqsignal(SIGHUP, SIG_IGN); /* * Presently, SIGINT will lead to autovacuum shutdown, because that's how * we handle ereport(ERROR). It could be improved however. */ pqsignal(SIGINT, StatementCancelHandler); pqsignal(SIGTERM, die); pqsignal(SIGQUIT, quickdie); pqsignal(SIGALRM, handle_sig_alarm); pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, CatchupInterruptHandler); /* We don't listen for async notifies */ pqsignal(SIGUSR2, SIG_IGN); pqsignal(SIGFPE, FloatExceptionHandler); pqsignal(SIGCHLD, SIG_DFL); /* Early initialization */ BaseInit(); /* * If an exception is encountered, processing resumes here. * * See notes in postgres.c about the design of this coding. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { /* Prevents interrupts while cleaning up */ HOLD_INTERRUPTS(); /* Report the error to the server log */ EmitErrorReport(); /* * We can now go away. Note that because we'll call InitProcess, a * callback will be registered to do ProcKill, which will clean up * necessary state. */ proc_exit(0); } /* We can now handle ereport(ERROR) */ PG_exception_stack = &local_sigjmp_buf; PG_SETMASK(&UnBlockSig); /* Get a list of databases */ dblist = autovac_get_database_list(); /* * Get the next Xid that was current as of the last checkpoint. We need it * to determine whether databases are about to need database-wide vacuums. */ nextXid = GetRecentNextXid(); /* * Choose a database to connect to. We pick the database that was least * recently auto-vacuumed, or one that needs database-wide vacuum (to * prevent Xid wraparound-related data loss). * * Note that a database with no stats entry is not considered, except for * Xid wraparound purposes. The theory is that if no one has ever * connected to it since the stats were last initialized, it doesn't need * vacuuming. * * XXX This could be improved if we had more info about whether it needs * vacuuming before connecting to it. Perhaps look through the pgstats * data for the database's tables? One idea is to keep track of the * number of new and dead tuples per database in pgstats. However it * isn't clear how to construct a metric that measures that and not cause * starvation for less busy databases. */ db = NULL; whole_db = false; foreach(cell, dblist) { autovac_dbase *tmp = lfirst(cell); bool this_whole_db; int32 freeze_age, vacuum_age; /* * We look for the database that most urgently needs a database-wide * vacuum. We decide that a database-wide vacuum is needed 100000 * transactions sooner than vacuum.c's vac_truncate_clog() would * decide to start giving warnings. If any such db is found, we * ignore all other dbs. * * Unlike vacuum.c, we also look at vacuumxid. This is so that * pg_clog can be kept trimmed to a reasonable size. */ freeze_age = (int32) (nextXid - tmp->frozenxid); vacuum_age = (int32) (nextXid - tmp->vacuumxid); tmp->age = Max(freeze_age, vacuum_age); this_whole_db = (tmp->age > (int32) ((MaxTransactionId >> 3) * 3 - 100000)); if (whole_db || this_whole_db) { if (!this_whole_db) continue; if (db == NULL || tmp->age > db->age) { db = tmp; whole_db = true; } continue; } /* * Otherwise, skip a database with no pgstat entry; it means it hasn't * seen any activity. */ tmp->entry = pgstat_fetch_stat_dbentry(tmp->oid); if (!tmp->entry) continue; /* * Don't try to access a database that was dropped. This could only * happen if we read the pg_database flat file right before it was * modified, after the database was dropped from the pg_database * table. (This is of course a not-very-bulletproof test, but it's * cheap to make. If we do mistakenly choose a recently dropped * database, InitPostgres will fail and we'll drop out until the next * autovac run.) */ if (tmp->entry->destroy != 0) continue; /* * Else remember the db with oldest autovac time. */ if (db == NULL || tmp->entry->last_autovac_time < db->entry->last_autovac_time) db = tmp; } if (db) { /* * Report autovac startup to the stats collector. We deliberately do * this before InitPostgres, so that the last_autovac_time will get * updated even if the connection attempt fails. This is to prevent * autovac from getting "stuck" repeatedly selecting an unopenable * database, rather than making any progress on stuff it can connect * to. */ pgstat_report_autovac(db->oid); /* * Connect to the selected database */ InitPostgres(db->name, NULL); SetProcessingMode(NormalProcessing); set_ps_display(db->name); ereport(LOG, (errmsg("autovacuum: processing database \"%s\"", db->name))); /* Create the memory context where cross-transaction state is stored */ AutovacMemCxt = AllocSetContextCreate(TopMemoryContext, "Autovacuum context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * And do an appropriate amount of work */ if (whole_db) process_whole_db(); else do_autovacuum(db->entry); } /* One iteration done, go away */ proc_exit(0);}/* * autovac_get_database_list * * Return a list of all databases. Note we cannot use pg_database, * because we aren't connected yet; we use the flat database file. */static List *autovac_get_database_list(void){ char *filename; List *dblist = NIL; char thisname[NAMEDATALEN]; FILE *db_file; Oid db_id; Oid db_tablespace; TransactionId db_frozenxid; TransactionId db_vacuumxid; filename = database_getflatfilename(); db_file = AllocateFile(filename, "r"); if (db_file == NULL) ereport(FATAL, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", filename))); while (read_pg_database_line(db_file, thisname, &db_id, &db_tablespace, &db_frozenxid, &db_vacuumxid)) { autovac_dbase *db; db = (autovac_dbase *) palloc(sizeof(autovac_dbase)); db->oid = db_id; db->name = pstrdup(thisname); db->frozenxid = db_frozenxid; db->vacuumxid = db_vacuumxid; /* these get set later: */ db->entry = NULL; db->age = 0; dblist = lappend(dblist, db); } FreeFile(db_file); pfree(filename); return dblist;}/* * Process a whole database. If it's a template database or is disallowing * connection by means of datallowconn=false, then issue a VACUUM FREEZE. * Else use a plain VACUUM. */static voidprocess_whole_db(void){ Relation dbRel; ScanKeyData entry[1]; SysScanDesc scan; HeapTuple tup; Form_pg_database dbForm; bool freeze; /* Start a transaction so our commands have one to play into. */ StartTransactionCommand(); /* functions in indexes may want a snapshot set */ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); /* * Clean up any dead statistics collector entries for this DB. */ pgstat_vacuum_tabstat(); dbRel = heap_open(DatabaseRelationId, AccessShareLock); /* Must use a table scan, since there's no syscache for pg_database */ ScanKeyInit(&entry[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(MyDatabaseId)); scan = systable_beginscan(dbRel, DatabaseOidIndexId, true, SnapshotNow, 1, entry); tup = systable_getnext(scan);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -