I have been working on integrating pg_autovacuum into the backend. I have used Tom's recent work on the bgwriter as an example. This patch accomplishes the following:
* pg_autovacuum is now started and stopped by the postmaster * pg_autovacuum is no longer a stand alone executable * pg_autovacuum now uses elog I am submitting this patch for review to make sure that I'm headed in the right direction, so please give it a cursory glance and tell me if you see any glaring problems. I am planning on doing much more in the next few days including: * creating pg_autovacuum related guc variables * create a new system table for pg_autovacuum settings * alter pg_class (or create another new system table) to store additional per table pg_autovacuum data which will allow per table settings and persistent data To use this patch, move pg_autovacuum.c into src/backend/postmaster, move pg_autovacuum.h into src/include/postmaster and apply the patch. Matthew O'Connor ps, I am hoping to get this work in before feature freeze.
*** ./src/backend/bootstrap/bootstrap.c.orig 2004-06-05 15:32:02.000000000 -0400 --- ./src/backend/bootstrap/bootstrap.c 2004-06-05 15:33:13.000000000 -0400 *************** *** 34,39 **** --- 34,40 ---- #include "libpq/pqsignal.h" #include "miscadmin.h" #include "postmaster/bgwriter.h" + #include "postmaster/pg_autovacuum.h" #include "storage/freespace.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" *************** *** 358,363 **** --- 359,367 ---- case BS_XLOG_BGWRITER: statmsg = "writer process"; break; + case BS_XLOG_AUTOVAC: + statmsg = "auto vacuum process"; + break; default: statmsg = "??? process"; break; *************** *** 394,399 **** --- 398,406 ---- case BS_XLOG_BGWRITER: InitDummyProcess(DUMMY_PROC_BGWRITER); break; + case BS_XLOG_AUTOVAC: + InitDummyProcess(DUMMY_PROC_AUTOVAC); + break; default: InitDummyProcess(DUMMY_PROC_DEFAULT); *************** *** 430,435 **** --- 437,448 ---- BackgroundWriterMain(); proc_exit(1); /* should never return */ + case BS_XLOG_AUTOVAC: + /* don't set signals, autovac has its own agenda */ + InitXLOGAccess(); + AutoVacMain(); + proc_exit(1); /* should never return */ + default: elog(PANIC, "unrecognized XLOG op: %d", xlogop); proc_exit(1); *** ./src/backend/Makefile.orig 2004-06-07 01:21:43.515373849 -0400 --- ./src/backend/Makefile 2004-06-05 13:46:24.000000000 -0400 *************** *** 29,41 **** ########################################################################## ! all: submake-libpgport postgres $(POSTGRES_IMP) ifneq ($(PORTNAME), cygwin) ifneq ($(PORTNAME), win32) postgres: $(OBJS) ! $(CC) $(CFLAGS) $(LDFLAGS) $(export_dynamic) $^ $(LIBS) -o $@ endif endif --- 29,41 ---- ########################################################################## ! all: submake-libpgport submake-libpq postgres $(POSTGRES_IMP) ifneq ($(PORTNAME), cygwin) ifneq ($(PORTNAME), win32) postgres: $(OBJS) ! $(CC) $(CFLAGS) $(LDFLAGS) -I $(libpq_srcdir) $(export_dynamic) $^ $(LIBS) $(libpq) -o $@ endif endif *** ./src/backend/postmaster/Makefile.orig 2004-06-05 00:58:08.000000000 -0400 --- ./src/backend/postmaster/Makefile 2004-06-05 13:45:20.000000000 -0400 *************** *** 12,18 **** top_builddir = ../../.. include $(top_builddir)/src/Makefile.global ! OBJS = postmaster.o bgwriter.o pgstat.o all: SUBSYS.o --- 12,18 ---- top_builddir = ../../.. include $(top_builddir)/src/Makefile.global ! OBJS = postmaster.o bgwriter.o pgstat.o pg_autovacuum.o all: SUBSYS.o *** ./src/backend/postmaster/pg_autovacuum.c.orig 2004-06-07 00:39:57.000000000 -0400 --- ./src/backend/postmaster/pg_autovacuum.c 2004-06-07 00:35:51.000000000 -0400 *************** *** 1,65 **** ! /* pg_autovacuum.c * All the code for the pg_autovacuum program * (c) 2003 Matthew T. O'Connor * Revisions by Christopher B. Browne, Liberty RMS */ ! #include "pg_autovacuum.h" - FILE *LOGOUTPUT; char logbuffer[4096]; ! static void ! log_entry(const char *logentry) { ! time_t curtime; ! struct tm *loctime; ! char timebuffer[128]; ! ! curtime = time(NULL); ! loctime = localtime(&curtime); ! strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S %Z", loctime); ! fprintf(LOGOUTPUT, "[%s] %s\n", timebuffer, logentry); } /* ! * Function used to detach the pg_autovacuum daemon from the tty and go into ! * the background. * ! * This code is mostly ripped directly from pm_dameonize in postmaster.c with ! * unneeded code removed. */ static void ! daemonize() { ! pid_t pid; ! pid = fork(); ! if (pid == (pid_t) -1) ! { ! log_entry("Error: cannot disassociate from controlling TTY"); ! fflush(LOGOUTPUT); ! _exit(1); ! } ! else if (pid) ! { /* parent */ ! /* Parent should just exit, without doing any atexit cleanup */ ! _exit(0); ! } ! /* GH: If there's no setsid(), we hopefully don't need silent mode. ! * Until there's a better solution. */ ! #ifdef HAVE_SETSID ! if (setsid() < 0) ! { ! log_entry("Error: cannot disassociate from controlling TTY"); ! fflush(LOGOUTPUT); ! _exit(1); ! } ! #endif } /* Create and return tbl_info struct with initialized to values from row or res */ static tbl_info * init_table_info(PGresult *res, int row, db_info * dbi) --- 1,223 ---- ! /*------------------------------------------------------------------------- ! * ! * pg_autovacuum.c ! * ! * The background autovacuum daemon was in 7.4 contribis but is being newly ! * integrated into 7.5. It monitors database activity using data from the ! * stats system (though at some point is should also look at FSM data) so ! * as to perform vacuum commands on specific tables when and only when ! * a sufficient amount activity has been performed on that table. ! * ! * The autovacuum process is started by the postmaster on startup. ! * It remains alive until the postmaster commands it to terminate. Normal ! * termination is by SIGUSR2, which instructs the autovacuum process to exit(0). ! * Emergency termination is by SIGQUIT; like any ! * backend, the autovacuum process will simply abort and exit on SIGQUIT. ! * * All the code for the pg_autovacuum program * (c) 2003 Matthew T. O'Connor * Revisions by Christopher B. Browne, Liberty RMS + *------------------------------------------------------------------------- */ + #include "postgres.h" + + #include <signal.h> + #include <time.h> ! #include "access/xlog.h" ! #include "libpq/pqsignal.h" ! #include "miscadmin.h" ! #include "postmaster/bgwriter.h" ! #include "storage/bufmgr.h" ! #include "storage/freespace.h" ! #include "storage/ipc.h" ! #include "storage/pmsignal.h" ! #include "storage/smgr.h" ! #include "tcop/tcopprot.h" ! #include "utils/guc.h" ! #include "postmaster/pg_autovacuum.h" char logbuffer[4096]; ! /* ! * GUC parameters ! */ ! /* ! int BgWriterDelay = 200; ! int BgWriterPercent = 1; ! int BgWriterMaxPages = 100; ! ! int CheckPointTimeout = 300; ! int CheckPointWarning = 30; ! */ ! ! /* ! * Flags set by interrupt handlers for later service in the main loop. ! */ ! static volatile sig_atomic_t got_SIGHUP = false; ! static volatile sig_atomic_t shutdown_requested = false; ! ! /* ! * Private state ! */ ! static bool am_autovac = false; ! ! /* static bool ckpt_active = false; */ ! ! /* static time_t last_checkpoint_time; */ ! ! ! static void autovac_quickdie(SIGNAL_ARGS); ! static void AutoVacSigHupHandler(SIGNAL_ARGS); ! static void ReqShutdownHandler(SIGNAL_ARGS); ! ! ! /* ! * Main entry point for bgwriter process ! * ! * This is invoked from BootstrapMain, which has already created the basic ! * execution environment, but not enabled signals yet. ! */ ! void ! AutoVacMain(void) { ! am_autovac = true; ! ! /* ! * Properly accept or ignore signals the postmaster might send us ! * ! * Note: we deliberately ignore SIGTERM, because during a standard Unix ! * system shutdown cycle, init will SIGTERM all processes at once. We ! * want to wait for the backends to exit, whereupon the postmaster will ! * tell us it's okay to shut down (via SIGUSR2). ! * ! * SIGUSR1 is presently unused; keep it spare in case someday we want ! * this process to participate in sinval messaging. ! */ ! pqsignal(SIGHUP, AutoVacSigHupHandler); /* set flag to read config file */ ! pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */ ! pqsignal(SIGQUIT, autovac_quickdie); /* hard crash time */ ! pqsignal(SIGALRM, SIG_IGN); ! pqsignal(SIGPIPE, SIG_IGN); ! pqsignal(SIGUSR1, SIG_IGN); /* reserve for sinval */ ! pqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */ ! ! /* ! * Reset some signals that are accepted by postmaster but not here ! */ ! pqsignal(SIGCHLD, SIG_DFL); ! pqsignal(SIGTTIN, SIG_DFL); ! pqsignal(SIGTTOU, SIG_DFL); ! pqsignal(SIGCONT, SIG_DFL); ! pqsignal(SIGWINCH, SIG_DFL); ! ! /* We allow SIGQUIT (quickdie) at all times */ ! #ifdef HAVE_SIGPROCMASK ! sigdelset(&BlockSig, SIGQUIT); ! #else ! BlockSig &= ~(sigmask(SIGQUIT)); ! #endif ! ! /* ! * If an exception is encountered, processing resumes here. ! */ ! if (sigsetjmp(Warn_restart, 1) != 0) ! { ! /* ! * Make sure we're not interrupted while cleaning up. Also forget ! * any pending QueryCancel request, since we're aborting anyway. ! * Force InterruptHoldoffCount to a known state in case we ! * ereport'd from inside a holdoff section. ! */ ! ImmediateInterruptOK = false; ! QueryCancelPending = false; ! InterruptHoldoffCount = 1; ! CritSectionCount = 0; /* should be unnecessary, but... */ ! ! /* ! * These operations are really just a minimal subset of ! * AbortTransaction(). We don't have very many resources ! * to worry about in bgwriter, but we do have LWLocks and buffers. ! */ ! LWLockReleaseAll(); ! AbortBufferIO(); ! UnlockBuffers(); ! ! /* ! * Clear flag to indicate that we got out of error recovery mode ! * successfully. (Flag was set in elog.c before longjmp().) ! */ ! InError = false; ! ! /* ! * Exit interrupt holdoff section we implicitly established above. ! */ ! RESUME_INTERRUPTS(); ! ! /* ! * Sleep at least 1 second after any error. A write error is ! * likely to be repeated, and we don't want to be filling the ! * error logs as fast as we can. (XXX think about ways to make ! * progress when the LRU dirty buffer cannot be written...) ! */ ! pg_usleep(1000000L); ! } ! ! Warn_restart_ready = true; /* we can now handle ereport(ERROR) */ ! ! /* ! * Unblock signals (they were blocked when the postmaster forked us) ! */ ! PG_SETMASK(&UnBlockSig); ! ! AutoVacLoop(); } + + /* -------------------------------- + * signal handler routines + * -------------------------------- + */ + /* ! * autovac_quickdie() occurs when signalled SIGQUIT by the postmaster. * ! * Some backend has bought the farm, ! * so we need to stop what we're doing and exit. */ static void ! autovac_quickdie(SIGNAL_ARGS) { ! PG_SETMASK(&BlockSig); ! /* ! * DO NOT proc_exit() -- we're here because shared memory may be ! * corrupted, so we don't want to try to clean up our transaction. ! * Just nail the windows shut and get out of town. ! * ! * Note we do exit(1) not exit(0). This is to force the postmaster into ! * a system reset cycle if some idiot DBA sends a manual SIGQUIT to a ! * random backend. This is necessary precisely because we don't clean ! * up our shared memory state. ! */ ! exit(1); ! } ! /* SIGHUP: set flag to re-read config file at next convenient time */ ! static void ! AutoVacSigHupHandler(SIGNAL_ARGS) ! { ! got_SIGHUP = true; ! } + /* SIGUSR2: set flag to run a shutdown checkpoint and exit */ + static void + ReqShutdownHandler(SIGNAL_ARGS) + { + shutdown_requested = true; } + /* Create and return tbl_info struct with initialized to values from row or res */ static tbl_info * init_table_info(PGresult *res, int row, db_info * dbi) *************** *** 68,75 **** if (!new_tbl) { ! log_entry("init_table_info: Cannot get memory"); ! fflush(LOGOUTPUT); return NULL; } --- 226,232 ---- if (!new_tbl) { ! elog(ERROR, "pg_autovacuum: init_table_info: Cannot get memory"); return NULL; } *************** *** 82,89 **** malloc(strlen(PQgetvalue(res, row, PQfnumber(res, "schemaname"))) + 1); if (!new_tbl->schema_name) { ! log_entry("init_table_info: malloc failed on new_tbl->schema_name"); ! fflush(LOGOUTPUT); return NULL; } strcpy(new_tbl->schema_name, --- 239,245 ---- malloc(strlen(PQgetvalue(res, row, PQfnumber(res, "schemaname"))) + 1); if (!new_tbl->schema_name) { ! elog(ERROR, "pg_autovacuum: init_table_info: malloc failed on new_tbl->schema_name"); return NULL; } strcpy(new_tbl->schema_name, *************** *** 94,101 **** strlen(new_tbl->schema_name) + 6); if (!new_tbl->table_name) { ! log_entry("init_table_info: malloc failed on new_tbl->table_name"); ! fflush(LOGOUTPUT); return NULL; } --- 250,256 ---- strlen(new_tbl->schema_name) + 6); if (!new_tbl->table_name) { ! elog(ERROR, "pg_autovacuum: init_table_info: malloc failed on new_tbl->table_name"); return NULL; } *************** *** 283,298 **** * now in the tbl_list */ { DLAddTail(dbi->table_list, DLNewElem(init_table_info(res, i, dbi))); ! if (args->debug >= 1) ! { ! sprintf(logbuffer, "added table: %s.%s", dbi->dbname, ! ((tbl_info *) DLE_VAL(DLGetTail(dbi->table_list)))->table_name); ! log_entry(logbuffer); ! } } } /* end of for loop that adds tables */ } - fflush(LOGOUTPUT); PQclear(res); res = NULL; if (args->debug >= 3) --- 438,449 ---- * now in the tbl_list */ { DLAddTail(dbi->table_list, DLNewElem(init_table_info(res, i, dbi))); ! sprintf(logbuffer, "pg_autovacuum: added table: %s.%s", dbi->dbname, ! ((tbl_info *) DLE_VAL(DLGetTail(dbi->table_list)))->table_name); ! elog(DEBUG1, logbuffer); } } /* end of for loop that adds tables */ } PQclear(res); res = NULL; if (args->debug >= 3) *************** *** 310,318 **** if (args->debug >= 1) { ! sprintf(logbuffer, "Removing table: %s from list.", tbl->table_name); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); } DLRemove(tbl_to_remove); --- 461,468 ---- if (args->debug >= 1) { ! sprintf(logbuffer, "pg_autovacuum: Removing table: %s from list.", tbl->table_name); ! elog(DEBUG1, logbuffer); } DLRemove(tbl_to_remove); *************** *** 366,386 **** print_table_info(tbl_info * tbl) { sprintf(logbuffer, " table name: %s.%s", tbl->dbi->dbname, tbl->table_name); ! log_entry(logbuffer); sprintf(logbuffer, " relid: %u; relisshared: %i", tbl->relid, tbl->relisshared); ! log_entry(logbuffer); sprintf(logbuffer, " reltuples: %f; relpages: %u", tbl->reltuples, tbl->relpages); ! log_entry(logbuffer); sprintf(logbuffer, " curr_analyze_count: %li; curr_vacuum_count: %li", tbl->curr_analyze_count, tbl->curr_vacuum_count); ! log_entry(logbuffer); sprintf(logbuffer, " last_analyze_count: %li; last_vacuum_count: %li", tbl->CountAtLastAnalyze, tbl->CountAtLastVacuum); ! log_entry(logbuffer); sprintf(logbuffer, " analyze_threshold: %li; vacuum_threshold: %li", tbl->analyze_threshold, tbl->vacuum_threshold); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); } /* End of table Management Functions */ --- 516,540 ---- print_table_info(tbl_info * tbl) { sprintf(logbuffer, " table name: %s.%s", tbl->dbi->dbname, tbl->table_name); ! elog(LOG, logbuffer); ! sprintf(logbuffer, " relid: %u; relisshared: %i", tbl->relid, tbl->relisshared); ! elog(LOG, logbuffer); ! sprintf(logbuffer, " reltuples: %f; relpages: %u", tbl->reltuples, tbl->relpages); ! elog(LOG, logbuffer); ! sprintf(logbuffer, " curr_analyze_count: %li; curr_vacuum_count: %li", tbl->curr_analyze_count, tbl->curr_vacuum_count); ! elog(LOG, logbuffer); ! sprintf(logbuffer, " last_analyze_count: %li; last_vacuum_count: %li", tbl->CountAtLastAnalyze, tbl->CountAtLastVacuum); ! elog(LOG, logbuffer); ! sprintf(logbuffer, " analyze_threshold: %li; vacuum_threshold: %li", tbl->analyze_threshold, tbl->vacuum_threshold); ! elog(LOG, logbuffer); } /* End of table Management Functions */ *************** *** 398,405 **** DLAddHead(db_list, DLNewElem(init_dbinfo((char *) "template1", 0, 0))); if (DLGetHead(db_list) == NULL) { /* Make sure init_dbinfo was successful */ ! log_entry("init_db_list(): Error creating db_list for db: template1."); ! fflush(LOGOUTPUT); return NULL; } --- 552,558 ---- DLAddHead(db_list, DLNewElem(init_dbinfo((char *) "template1", 0, 0))); if (DLGetHead(db_list) == NULL) { /* Make sure init_dbinfo was successful */ ! elog(ERROR, logbuffer); return NULL; } *************** *** 476,486 **** t = 0, found_match = 0; ! if (args->debug >= 2) ! { ! log_entry("updating the database list"); ! fflush(LOGOUTPUT); ! } if (dbi_template1->conn == NULL) { --- 629,635 ---- t = 0, found_match = 0; ! elog(DEBUG2, "pg_autovacuum: updating the database list"); if (dbi_template1->conn == NULL) { *************** *** 566,577 **** if (args->debug >= 1) { sprintf(logbuffer, "added database: %s", ((db_info *) DLE_VAL(DLGetTail(db_list)))->dbname); ! log_entry(logbuffer); } } ! } /* end of for loop that adds tables */ } - fflush(LOGOUTPUT); PQclear(res); res = NULL; if (args->debug >= 3) --- 715,725 ---- if (args->debug >= 1) { sprintf(logbuffer, "added database: %s", ((db_info *) DLE_VAL(DLGetTail(db_list)))->dbname); ! elog(DEBUG1, logbuffer); } } ! } /* end of for loop that adds tables */ } PQclear(res); res = NULL; if (args->debug >= 3) *************** *** 625,636 **** { db_info *dbi = ((db_info *) DLE_VAL(db_to_remove)); ! if (args->debug >= 1) ! { ! sprintf(logbuffer, "Removing db: %s from list.", dbi->dbname); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); ! } DLRemove(db_to_remove); if (dbi->conn) db_disconnect(dbi); --- 773,781 ---- { db_info *dbi = ((db_info *) DLE_VAL(db_to_remove)); ! sprintf(logbuffer, "pg_autovacuum: Removing db: %s from list.", dbi->dbname); ! elog(DEBUG1, logbuffer); ! DLRemove(db_to_remove); if (dbi->conn) db_disconnect(dbi); *************** *** 696,724 **** print_db_info(db_info * dbi, int print_tbl_list) { sprintf(logbuffer, "dbname: %s", (dbi->dbname) ? dbi->dbname : "(null)"); ! log_entry(logbuffer); sprintf(logbuffer, " oid: %u", dbi->oid); ! log_entry(logbuffer); sprintf(logbuffer, " username: %s", (dbi->username) ? dbi->username : "(null)"); ! log_entry(logbuffer); sprintf(logbuffer, " password: %s", (dbi->password) ? dbi->password : "(null)"); ! log_entry(logbuffer); if (dbi->conn != NULL) ! log_entry(" conn is valid, (connected)"); else ! log_entry(" conn is null, (not connected)"); sprintf(logbuffer, " default_analyze_threshold: %li", dbi->analyze_threshold); ! log_entry(logbuffer); sprintf(logbuffer, " default_vacuum_threshold: %li", dbi->vacuum_threshold); ! log_entry(logbuffer); - fflush(LOGOUTPUT); if (print_tbl_list > 0) print_table_list(dbi->table_list); } --- 841,868 ---- print_db_info(db_info * dbi, int print_tbl_list) { sprintf(logbuffer, "dbname: %s", (dbi->dbname) ? dbi->dbname : "(null)"); ! elog(LOG, logbuffer); sprintf(logbuffer, " oid: %u", dbi->oid); ! elog(LOG, logbuffer); sprintf(logbuffer, " username: %s", (dbi->username) ? dbi->username : "(null)"); ! elog(LOG, logbuffer); sprintf(logbuffer, " password: %s", (dbi->password) ? dbi->password : "(null)"); ! elog(LOG, logbuffer); if (dbi->conn != NULL) ! elog(LOG, " conn is valid, (connected)"); else ! elog(LOG, " conn is null, (not connected)"); sprintf(logbuffer, " default_analyze_threshold: %li", dbi->analyze_threshold); ! elog(LOG, logbuffer); sprintf(logbuffer, " default_vacuum_threshold: %li", dbi->vacuum_threshold); ! elog(LOG, logbuffer); if (print_tbl_list > 0) print_table_list(dbi->table_list); } *************** *** 739,746 **** { sprintf(logbuffer, "Failed connection to database %s with error: %s.", dbi->dbname, PQerrorMessage(db_conn)); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); PQfinish(db_conn); db_conn = NULL; } --- 883,889 ---- { sprintf(logbuffer, "Failed connection to database %s with error: %s.", dbi->dbname, PQerrorMessage(db_conn)); ! elog(LOG, logbuffer); PQfinish(db_conn); db_conn = NULL; } *************** *** 780,799 **** if (dbi->conn == NULL) return NULL; ! if (args->debug >= 4) ! log_entry(query); res = PQexec(dbi->conn, query); if (!res) { sprintf(logbuffer, ! "Fatal error occured while sending query (%s) to database %s", query, dbi->dbname); ! log_entry(logbuffer); sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res)); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); return NULL; } if (PQresultStatus(res) != PGRES_TUPLES_OK && --- 923,940 ---- if (dbi->conn == NULL) return NULL; ! elog(DEBUG3, query); res = PQexec(dbi->conn, query); if (!res) { sprintf(logbuffer, ! "pg_autovacuum: Fatal error occured while sending query (%s) to database %s", query, dbi->dbname); ! elog(ERROR, logbuffer); sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res)); ! elog(ERROR, logbuffer); return NULL; } if (PQresultStatus(res) != PGRES_TUPLES_OK && *************** *** 802,811 **** sprintf(logbuffer, "Can not refresh statistics information from the database %s.", dbi->dbname); ! log_entry(logbuffer); sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res)); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); PQclear(res); return NULL; } --- 943,951 ---- sprintf(logbuffer, "Can not refresh statistics information from the database %s.", dbi->dbname); ! elog(ERROR, logbuffer); sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res)); ! elog(ERROR, logbuffer); PQclear(res); return NULL; } *************** *** 829,835 **** static cmd_args * get_cmd_args(int argc, char *argv[]) { ! int c; args = (cmd_args *) malloc(sizeof(cmd_args)); args->sleep_base_value = SLEEPBASEVALUE; --- 969,975 ---- static cmd_args * get_cmd_args(int argc, char *argv[]) { ! // int c; args = (cmd_args *) malloc(sizeof(cmd_args)); args->sleep_base_value = SLEEPBASEVALUE; *************** *** 838,845 **** args->vacuum_scaling_factor = VACSCALINGFACTOR; args->analyze_base_threshold = -1; args->analyze_scaling_factor = -1; ! args->debug = AUTOVACUUM_DEBUG; ! args->daemonize = 0; args->user = 0; args->password = 0; args->host = 0; --- 978,985 ---- args->vacuum_scaling_factor = VACSCALINGFACTOR; args->analyze_base_threshold = -1; args->analyze_scaling_factor = -1; ! // args->debug = AUTOVACUUM_DEBUG; ! args->debug = 2; args->user = 0; args->password = 0; args->host = 0; *************** *** 850,856 **** * Fixme: Should add some sanity checking such as positive integer * values etc */ ! while ((c = getopt(argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hD")) != -1) { switch (c) { --- 990,996 ---- * Fixme: Should add some sanity checking such as positive integer * values etc */ ! /* while ((c = getopt(argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hD")) != -1) { switch (c) { *************** *** 872,880 **** case 'A': args->analyze_scaling_factor = atof(optarg); break; - case 'D': - args->daemonize++; - break; case 'd': args->debug = atoi(optarg); break; --- 1012,1017 ---- *************** *** 897,999 **** usage(); exit(0); default: ! /* * It's here that we know that things are invalid... It is * not forcibly an error to call usage */ ! fprintf(stderr, "Error: Invalid Command Line Options.\n"); usage(); exit(1); break; } - - /* - * if values for insert thresholds are not specified, then they - * default to 1/2 of the delete values - */ - if (args->analyze_base_threshold == -1) - args->analyze_base_threshold = args->vacuum_base_threshold / 2; - if (args->analyze_scaling_factor == -1) - args->analyze_scaling_factor = args->vacuum_scaling_factor / 2; } return args; } - static void - usage() - { - int i = 0; - float f = 0; - - fprintf(stderr, "usage: pg_autovacuum \n"); - fprintf(stderr, " [-D] Daemonize (Detach from tty and run in the background)\n"); - i = AUTOVACUUM_DEBUG; - fprintf(stderr, " [-d] debug (debug level=0,1,2,3; default=%i)\n", i); - - i = SLEEPBASEVALUE; - fprintf(stderr, " [-s] sleep base value (default=%i)\n", i); - f = SLEEPSCALINGFACTOR; - fprintf(stderr, " [-S] sleep scaling factor (default=%f)\n", f); - - i = VACBASETHRESHOLD; - fprintf(stderr, " [-v] vacuum base threshold (default=%i)\n", i); - f = VACSCALINGFACTOR; - fprintf(stderr, " [-V] vacuum scaling factor (default=%f)\n", f); - i = i / 2; - fprintf(stderr, " [-a] analyze base threshold (default=%i)\n", i); - f = f / 2; - fprintf(stderr, " [-A] analyze scaling factor (default=%f)\n", f); - - fprintf(stderr, " [-L] logfile (default=none)\n"); - - fprintf(stderr, " [-U] username (libpq default)\n"); - fprintf(stderr, " [-P] password (libpq default)\n"); - fprintf(stderr, " [-H] host (libpq default)\n"); - fprintf(stderr, " [-p] port (libpq default)\n"); - - fprintf(stderr, " [-h] help (Show this output)\n"); - } - - static void - print_cmd_args() - { - sprintf(logbuffer, "Printing command_args"); - log_entry(logbuffer); - sprintf(logbuffer, " args->host=%s", (args->host) ? args->host : "(null)"); - log_entry(logbuffer); - sprintf(logbuffer, " args->port=%s", (args->port) ? args->port : "(null)"); - log_entry(logbuffer); - sprintf(logbuffer, " args->username=%s", (args->user) ? args->user : "(null)"); - log_entry(logbuffer); - sprintf(logbuffer, " args->password=%s", (args->password) ? args->password : "(null)"); - log_entry(logbuffer); - sprintf(logbuffer, " args->logfile=%s", (args->logfile) ? args->logfile : "(null)"); - log_entry(logbuffer); - sprintf(logbuffer, " args->daemonize=%i", args->daemonize); - log_entry(logbuffer); - - sprintf(logbuffer, " args->sleep_base_value=%i", args->sleep_base_value); - log_entry(logbuffer); - sprintf(logbuffer, " args->sleep_scaling_factor=%f", args->sleep_scaling_factor); - log_entry(logbuffer); - sprintf(logbuffer, " args->vacuum_base_threshold=%i", args->vacuum_base_threshold); - log_entry(logbuffer); - sprintf(logbuffer, " args->vacuum_scaling_factor=%f", args->vacuum_scaling_factor); - log_entry(logbuffer); - sprintf(logbuffer, " args->analyze_base_threshold=%i", args->analyze_base_threshold); - log_entry(logbuffer); - sprintf(logbuffer, " args->analyze_scaling_factor=%f", args->analyze_scaling_factor); - log_entry(logbuffer); - sprintf(logbuffer, " args->debug=%i", args->debug); - log_entry(logbuffer); - - fflush(LOGOUTPUT); - } /* Beginning of AutoVacuum Main Program */ ! int ! main(int argc, char *argv[]) { char buf[256]; int j = 0, --- 1034,1065 ---- usage(); exit(0); default: ! */ /* * It's here that we know that things are invalid... It is * not forcibly an error to call usage */ ! /* fprintf(stderr, "Error: Invalid Command Line Options.\n"); usage(); exit(1); break; } } + */ + /* + * if values for insert thresholds are not specified, then they + * default to 1/2 of the delete values + */ + if (args->analyze_base_threshold == -1) + args->analyze_base_threshold = args->vacuum_base_threshold / 2; + if (args->analyze_scaling_factor == -1) + args->analyze_scaling_factor = args->vacuum_scaling_factor / 2; return args; } /* Beginning of AutoVacuum Main Program */ ! void AutoVacLoop(void) { char buf[256]; int j = 0, *************** *** 1011,1048 **** struct timeval now, then; ! args = get_cmd_args(argc, argv); /* Get Command Line Args and put * them in the args struct */ - /* Dameonize if requested */ - if (args->daemonize == 1) - daemonize(); - - if (args->logfile) - { - LOGOUTPUT = fopen(args->logfile, "a"); - if (!LOGOUTPUT) - { - fprintf(stderr, "Could not open log file - [%s]\n", args->logfile); - exit(-1); - } - } - else - LOGOUTPUT = stderr; - if (args->debug >= 2) - print_cmd_args(); - /* Init the db list with template1 */ db_list = init_db_list(); if (db_list == NULL) ! return 1; if (check_stats_enabled(((db_info *) DLE_VAL(DLGetHead(db_list)))) != 0) { ! log_entry("Error: GUC variable stats_row_level must be enabled."); ! log_entry(" Please fix the problems and try again."); ! fflush(LOGOUTPUT); ! exit(1); } --- 1077,1095 ---- struct timeval now, then; ! // args = get_cmd_args(argc, argv); /* Get Command Line Args and put ! args = get_cmd_args(0, NULL); /* Get Command Line Args and put * them in the args struct */ /* Init the db list with template1 */ db_list = init_db_list(); if (db_list == NULL) ! exit(1); if (check_stats_enabled(((db_info *) DLE_VAL(DLGetHead(db_list)))) != 0) { ! elog(ERROR, "pg_autovacuum: GUC variable stats_row_level must be enabled."); ! elog(ERROR, " Please fix the problems and try again."); exit(1); } *************** *** 1050,1055 **** --- 1097,1123 ---- while (1) { /* Main Loop */ + + /* + * Emergency bailout if postmaster has died. This is to avoid the + * necessity for manual cleanup of all postmaster children. + */ + if (!PostmasterIsAlive(true)) + exit(1); + + if (got_SIGHUP) + { + got_SIGHUP = false; + ProcessConfigFile(PGC_SIGHUP); + } + if (shutdown_requested) + { + ShutdownXLOG(0, 0); + DumpFreeSpaceMap(0, 0); + /* Normal exit from the bgwriter is here */ + proc_exit(0); /* done */ + } + db_elem = DLGetHead(db_list); /* Reset cur_db_node to the * beginning of the db_list */ *************** *** 1061,1069 **** if (dbs->conn == NULL) { /* Serious problem: We can't connect to * template1 */ ! log_entry("Error: Cannot connect to template1, exiting."); ! fflush(LOGOUTPUT); ! fclose(LOGOUTPUT); exit(1); } } --- 1129,1135 ---- if (dbs->conn == NULL) { /* Serious problem: We can't connect to * template1 */ ! elog(ERROR, "pg_autovacuum: Cannot connect to template1, exiting."); exit(1); } } *************** *** 1134,1142 **** snprintf(buf, sizeof(buf), "VACUUM ANALYZE %s", tbl->table_name); if (args->debug >= 1) { ! sprintf(logbuffer, "Performing: %s", buf); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); } send_query(buf, dbs); update_table_thresholds(dbs, tbl, VACUUM_ANALYZE); --- 1200,1207 ---- snprintf(buf, sizeof(buf), "VACUUM ANALYZE %s", tbl->table_name); if (args->debug >= 1) { ! sprintf(logbuffer, "pg_autovacuum: Performing: %s", buf); ! elog(DEBUG1, logbuffer); } send_query(buf, dbs); update_table_thresholds(dbs, tbl, VACUUM_ANALYZE); *************** *** 1148,1156 **** snprintf(buf, sizeof(buf), "ANALYZE %s", tbl->table_name); if (args->debug >= 1) { ! sprintf(logbuffer, "Performing: %s", buf); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); } send_query(buf, dbs); update_table_thresholds(dbs, tbl, ANALYZE_ONLY); --- 1213,1220 ---- snprintf(buf, sizeof(buf), "ANALYZE %s", tbl->table_name); if (args->debug >= 1) { ! sprintf(logbuffer, "pg_autovacuum: Performing: %s", buf); ! elog(DEBUG1, logbuffer); } send_query(buf, dbs); update_table_thresholds(dbs, tbl, ANALYZE_ONLY); *************** *** 1187,1206 **** sleep_secs = args->sleep_base_value + args->sleep_scaling_factor * diff / 1000000.0; loops++; ! if (args->debug >= 2) ! { ! sprintf(logbuffer, ! "%i All DBs checked in: %.0f usec, will sleep for %i secs.", ! loops, diff, sleep_secs); ! log_entry(logbuffer); ! fflush(LOGOUTPUT); ! } ! ! sleep(sleep_secs); /* Larger Pause between outer loops */ gettimeofday(&then, 0); /* Reset time counter */ ! } /* end of while loop */ /* * program is exiting, this should never run, but is here to make --- 1251,1269 ---- sleep_secs = args->sleep_base_value + args->sleep_scaling_factor * diff / 1000000.0; loops++; ! ! sprintf(logbuffer, ! "pg_autovacuum: loop %i; All DBs checked in: %.0f usec, will sleep for %i secs.", ! loops, diff, sleep_secs); ! elog(DEBUG1, logbuffer); ! ! /* Larger Pause between outer loops */ ! if (!(got_SIGHUP || shutdown_requested)) ! pg_usleep((long)(sleep_secs * 1000000L)); gettimeofday(&then, 0); /* Reset time counter */ ! } /* end of while loop */ /* * program is exiting, this should never run, but is here to make *************** *** 1208,1212 **** */ free_db_list(db_list); free_cmd_args(); ! return EXIT_SUCCESS; } --- 1271,1275 ---- */ free_db_list(db_list); free_cmd_args(); ! exit(0); } *** ./src/backend/postmaster/postmaster.c.orig 2004-06-05 15:05:18.000000000 -0400 --- ./src/backend/postmaster/postmaster.c 2004-06-05 15:37:48.000000000 -0400 *************** *** 191,197 **** /* PIDs of special child processes; 0 when not running */ static pid_t StartupPID = 0, ! BgWriterPID = 0; /* Startup/shutdown state */ #define NoShutdown 0 --- 191,198 ---- /* PIDs of special child processes; 0 when not running */ static pid_t StartupPID = 0, ! BgWriterPID = 0, ! AutoVacPID = 0; /* Startup/shutdown state */ #define NoShutdown 0 *************** *** 293,298 **** --- 294,300 ---- #define StartupDataBase() StartChildProcess(BS_XLOG_STARTUP) #define StartBackgroundWriter() StartChildProcess(BS_XLOG_BGWRITER) + #define StartAutoVac() StartChildProcess(BS_XLOG_AUTOVAC) /* *************** *** 780,786 **** * * CAUTION: when changing this list, check for side-effects on the signal * handling setup of child processes. See tcop/postgres.c, ! * bootstrap/bootstrap.c, postmaster/bgwriter.c, and postmaster/pgstat.c. */ pqinitmask(); PG_SETMASK(&BlockSig); --- 782,789 ---- * * CAUTION: when changing this list, check for side-effects on the signal * handling setup of child processes. See tcop/postgres.c, ! * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/pgstat.c, ! * and postmaster/pg_autovacuum.c. */ pqinitmask(); PG_SETMASK(&BlockSig); *************** *** 1144,1149 **** --- 1147,1165 ---- kill(BgWriterPID, SIGUSR2); } + /* + * If no AutoVacuum process is running, and we are not in + * a state that prevents it, start one. It doesn't matter if this + * fails, we'll just try again later. + */ + if (AutoVacPID == 0 && StartupPID == 0 && !FatalError) + { + AutoVacPID = StartAutoVac(); + /* If shutdown is pending, set it going */ + if (Shutdown > NoShutdown && AutoVacPID != 0) + kill(AutoVacPID, SIGUSR2); + } + /* If we have lost the stats collector, try to start a new one */ if (!pgstat_is_running) pgstat_start(); *************** *** 1512,1517 **** --- 1528,1540 ---- backendPID))); return; } + if (backendPID == AutoVacPID) + { + ereport(DEBUG2, + (errmsg_internal("ignoring cancel request for autovacuum process %d", + backendPID))); + return; + } /* * See if we have a matching backend. In the EXEC_BACKEND case, we *************** *** 1694,1699 **** --- 1717,1724 ---- SignalChildren(SIGHUP); if (BgWriterPID != 0) kill(BgWriterPID, SIGHUP); + if (AutoVacPID != 0) + kill(AutoVacPID, SIGHUP); load_hba(); load_ident(); *************** *** 1751,1756 **** --- 1776,1785 ---- /* And tell it to shut down */ if (BgWriterPID != 0) kill(BgWriterPID, SIGUSR2); + /* I don't think we need to Start the autovac process if not running */ + /* And tell it to shut down */ + if (AutoVacPID != 0) + kill(AutoVacPID, SIGUSR2); break; case SIGINT: *************** *** 1792,1797 **** --- 1821,1829 ---- /* And tell it to shut down */ if (BgWriterPID != 0) kill(BgWriterPID, SIGUSR2); + /* And tell it to shut down */ + if (AutoVacPID != 0) + kill(AutoVacPID, SIGUSR2); break; case SIGQUIT: *************** *** 1807,1812 **** --- 1839,1846 ---- kill(StartupPID, SIGQUIT); if (BgWriterPID != 0) kill(BgWriterPID, SIGQUIT); + if (AutoVacPID != 0) + kill(AutoVacPID, SIGQUIT); if (DLGetHead(BackendList)) SignalChildren(SIGQUIT); ExitPostmaster(0); *************** *** 1909,1914 **** --- 1943,1961 ---- if (Shutdown > NoShutdown && BgWriterPID != 0) kill(BgWriterPID, SIGUSR2); + /* + * Crank up the AutoVac. It doesn't matter if this + * fails, we'll just try again later. + */ + Assert(AutoVacPID == 0); + AutoVacPID = StartAutoVac(); + + /* + * Go to shutdown mode if a shutdown request was pending. + */ + if (Shutdown > NoShutdown && AutoVacPID != 0) + kill(AutoVacPID, SIGUSR2); + continue; } *************** *** 1950,1956 **** * Wait for all children exit, then reset shmem and * StartupDataBase. */ ! if (DLGetHead(BackendList) || StartupPID != 0 || BgWriterPID != 0) goto reaper_done; ereport(LOG, (errmsg("all server processes terminated; reinitializing"))); --- 1997,2003 ---- * Wait for all children exit, then reset shmem and * StartupDataBase. */ ! if (DLGetHead(BackendList) || StartupPID != 0 || BgWriterPID != 0 || AutoVacPID != 0) goto reaper_done; ereport(LOG, (errmsg("all server processes terminated; reinitializing"))); *************** *** 1973,1978 **** --- 2020,2028 ---- /* And tell it to shut down */ if (BgWriterPID != 0) kill(BgWriterPID, SIGUSR2); + /* Tell AutoVac to shut down */ + if (AutoVacPID != 0) + kill(AutoVacPID, SIGUSR2); } reaper_done: *************** *** 2110,2115 **** --- 2160,2179 ---- } FatalError = true; + + /* Take care of the autovac too */ + if (pid == AutoVacPID) + AutoVacPID = 0; + else if (AutoVacPID != 0 && !FatalError) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) AutoVacPID))); + kill(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + + FatalError = true; } /* *************** *** 3074,3079 **** --- 3138,3147 ---- ereport(LOG, (errmsg("could not fork background writer process: %m"))); break; + case BS_XLOG_AUTOVAC: + ereport(LOG, + (errmsg("could not fork auto vacuum process: %m"))); + break; default: ereport(LOG, (errmsg("could not fork process: %m"))); *** ./src/include/bootstrap/bootstrap.h.orig 2004-06-05 15:07:32.000000000 -0400 --- ./src/include/bootstrap/bootstrap.h 2004-06-05 15:07:04.000000000 -0400 *************** *** 59,63 **** --- 59,64 ---- #define BS_XLOG_BOOTSTRAP 1 #define BS_XLOG_STARTUP 2 #define BS_XLOG_BGWRITER 3 + #define BS_XLOG_AUTOVAC 4 #endif /* BOOTSTRAP_H */ *** ./src/include/postmaster/pg_autovacuum.h.orig 2004-06-07 00:40:16.000000000 -0400 --- ./src/include/postmaster/pg_autovacuum.h 2004-06-07 00:37:45.000000000 -0400 *************** *** 3,42 **** * (c) 2003 Matthew T. O'Connor */ #include "postgres_fe.h" #include <unistd.h> - #ifdef HAVE_GETOPT_H - #include <getopt.h> - #endif #include <time.h> #include <sys/time.h> ! /* These next two lines are correct when pg_autovaccum is compiled ! from within the postgresql source tree */ ! #include "libpq-fe.h" #include "lib/dllist.h" - /* Had to change the last two lines to compile on - Redhat outside of postgresql source tree */ - /* - #include "/usr/include/libpq-fe.h" - #include "/usr/include/pgsql/server/lib/dllist.h" - */ #define AUTOVACUUM_DEBUG 1 #define VACBASETHRESHOLD 1000 #define VACSCALINGFACTOR 2 ! #define SLEEPBASEVALUE 300 #define SLEEPSCALINGFACTOR 2 ! #define UPDATE_INTERVAL 2 /* these two constants are used to tell update_table_stats what operation we just perfomred */ #define VACUUM_ANALYZE 0 #define ANALYZE_ONLY 1 ! #define TABLE_STATS_QUERY "select a.oid,a.relname,a.relnamespace,a.relpages,a.relisshared,a.reltuples,b.schemaname,b.n_tup_ins,b.n_tup_upd,b.n_tup_del from pg_class a, pg_stat_all_tables b where a.oid=b.relid and a.relkind = 'r' and schemaname not like 'pg_temp_%'" ! ! #define FRONTEND #define PAGES_QUERY "select oid,reltuples,relpages from pg_class where oid=%u" #define FROZENOID_QUERY "select oid,age(datfrozenxid) from pg_database where datname = 'template1'" #define FROZENOID_QUERY2 "select oid,datname,age(datfrozenxid) from pg_database where datname!='template0'" --- 3,35 ---- * (c) 2003 Matthew T. O'Connor */ + #ifndef _AUTOVAC_H + #define _AUTOVAC_H + + #define FRONTEND #include "postgres_fe.h" #include <unistd.h> #include <time.h> #include <sys/time.h> ! #include "../../interfaces/libpq/libpq-fe.h" #include "lib/dllist.h" + /* default settings defined here */ #define AUTOVACUUM_DEBUG 1 #define VACBASETHRESHOLD 1000 #define VACSCALINGFACTOR 2 ! #define SLEEPBASEVALUE 15 #define SLEEPSCALINGFACTOR 2 ! #define UPDATE_INTERVAL 1 /* these two constants are used to tell update_table_stats what operation we just perfomred */ #define VACUUM_ANALYZE 0 #define ANALYZE_ONLY 1 ! /* define the main queries */ ! #define TABLE_STATS_QUERY "select a.oid, a.relname, a.relnamespace, a.relpages, a.relisshared, a.reltuples, b.schemaname, b.n_tup_ins, b.n_tup_upd, b.n_tup_del from pg_class a, pg_stat_all_tables b where a.oid=b.relid and a.relkind = 'r' and schemaname not like 'pg_temp_%'" #define PAGES_QUERY "select oid,reltuples,relpages from pg_class where oid=%u" #define FROZENOID_QUERY "select oid,age(datfrozenxid) from pg_database where datname = 'template1'" #define FROZENOID_QUERY2 "select oid,datname,age(datfrozenxid) from pg_database where datname!='template0'" *************** *** 107,116 **** typedef struct tableinfo tbl_info; /* Functions for dealing with command line arguements */ static cmd_args *get_cmd_args(int argc, char *argv[]); - static void print_cmd_args(void); static void free_cmd_args(void); - static void usage(void); /* Functions for managing database lists */ static Dllist *init_db_list(void); --- 100,109 ---- typedef struct tableinfo tbl_info; /* Functions for dealing with command line arguements */ + void AutoVacMain(void); + void AutoVacLoop(void); static cmd_args *get_cmd_args(int argc, char *argv[]); static void free_cmd_args(void); /* Functions for managing database lists */ static Dllist *init_db_list(void); *************** *** 137,142 **** static void db_disconnect(db_info * dbi); static PGresult *send_query(const char *query, db_info * dbi); ! /* Other Generally needed Functions */ ! static void daemonize(void); ! static void log_entry(const char *logentry); --- 130,133 ---- static void db_disconnect(db_info * dbi); static PGresult *send_query(const char *query, db_info * dbi); ! #endif /* _AUTOVAC_H_ */ *** ./src/include/storage/proc.h.orig 2004-06-05 15:31:36.000000000 -0400 --- ./src/include/storage/proc.h 2004-06-05 15:32:20.000000000 -0400 *************** *** 88,94 **** #define DUMMY_PROC_DEFAULT 0 #define DUMMY_PROC_BGWRITER 1 ! #define NUM_DUMMY_PROCS 2 /* configurable options */ --- 88,95 ---- #define DUMMY_PROC_DEFAULT 0 #define DUMMY_PROC_BGWRITER 1 ! #define DUMMY_PROC_AUTOVAC 2 ! #define NUM_DUMMY_PROCS 3 /* configurable options */
---------------------------(end of broadcast)--------------------------- TIP 7: don't forget to increase your free space map settings