In the spirit of incremental improvement, here is a patch that turns the couple of bools in PGPROC into a bitmask, and associated fallout.
This patch also contains a change to make a cancelled autovacuum continue with the schedule (indeed to continue with the schedule on any error), rather than aborting completely. My idea is to add a flag for "analyze" and another for "is for xid wraparound", and to build on that for the cancel-autovac-on-deadlock- checker project, as submitted by Simon. -- Alvaro Herrera Valdivia, Chile ICBM: S 39º 49' 18.1", W 73º 13' 56.4" "El sudor es la mejor cura para un pensamiento enfermo" (Bardia)
Index: src/backend/access/transam/twophase.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/access/transam/twophase.c,v retrieving revision 1.36 diff -c -p -r1.36 twophase.c *** src/backend/access/transam/twophase.c 21 Sep 2007 16:32:19 -0000 1.36 --- src/backend/access/transam/twophase.c 23 Oct 2007 20:22:51 -0000 *************** MarkAsPreparing(TransactionId xid, const *** 283,290 **** gxact->proc.databaseId = databaseid; gxact->proc.roleId = owner; gxact->proc.inCommit = false; ! gxact->proc.inVacuum = false; ! gxact->proc.isAutovacuum = false; gxact->proc.lwWaiting = false; gxact->proc.lwExclusive = false; gxact->proc.lwWaitLink = NULL; --- 283,289 ---- gxact->proc.databaseId = databaseid; gxact->proc.roleId = owner; gxact->proc.inCommit = false; ! gxact->proc.vacuumFlags = 0; gxact->proc.lwWaiting = false; gxact->proc.lwExclusive = false; gxact->proc.lwWaitLink = NULL; Index: src/backend/commands/vacuum.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/commands/vacuum.c,v retrieving revision 1.359 diff -c -p -r1.359 vacuum.c *** src/backend/commands/vacuum.c 20 Sep 2007 17:56:31 -0000 1.359 --- src/backend/commands/vacuum.c 23 Oct 2007 20:26:39 -0000 *************** vacuum_set_xid_limits(int freeze_min_age *** 660,668 **** * fixed-size never-null columns, but these are. * * Another reason for doing it this way is that when we are in a lazy ! * VACUUM and have inVacuum set, we mustn't do any updates --- somebody ! * vacuuming pg_class might think they could delete a tuple marked with ! * xmin = our xid. * * This routine is shared by full VACUUM, lazy VACUUM, and stand-alone * ANALYZE. --- 660,668 ---- * fixed-size never-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 updates --- ! * somebody vacuuming pg_class might think they could delete a tuple ! * marked with xmin = our xid. * * This routine is shared by full VACUUM, lazy VACUUM, and stand-alone * ANALYZE. *************** vacuum_rel(Oid relid, VacuumStmt *vacstm *** 987,995 **** * During a lazy VACUUM we do not run any user-supplied functions, and * so it should be safe to not create a transaction snapshot. * ! * We can furthermore set the inVacuum flag, which lets other * concurrent VACUUMs know that they can ignore this one while ! * determining their OldestXmin. (The reason we don't set inVacuum * 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 --- 987,995 ---- * During a lazy VACUUM we do not run any user-supplied functions, and * so it should be safe to not create a transaction snapshot. * ! * We can furthermore 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 *************** vacuum_rel(Oid relid, VacuumStmt *vacstm *** 997,1008 **** * depends on the contents of other tables is arguably broken, but we * won't break it here by violating transaction semantics.) * ! * Note: the inVacuum flag remains set until CommitTransaction or * AbortTransaction. We don't want to clear it until we reset * MyProc->xid/xmin, else OldestXmin might appear to go backwards, * which is probably Not Good. */ ! MyProc->inVacuum = true; } /* --- 997,1008 ---- * depends on the contents of other tables is arguably broken, but we * won't break it here by violating transaction semantics.) * ! * Note: this flag remains set until CommitTransaction or * AbortTransaction. We don't want to clear it until we reset * MyProc->xid/xmin, else OldestXmin might appear to go backwards, * which is probably Not Good. */ ! MyProc->vacuumFlags |= PROC_IN_VACUUM; } /* Index: src/backend/postmaster/autovacuum.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/postmaster/autovacuum.c,v retrieving revision 1.61 diff -c -p -r1.61 autovacuum.c *** src/backend/postmaster/autovacuum.c 24 Sep 2007 04:12:01 -0000 1.61 --- src/backend/postmaster/autovacuum.c 23 Oct 2007 20:49:24 -0000 *************** typedef struct autovac_table *** 182,188 **** * wi_links entry into free list or running list * wi_dboid OID of the database this worker is supposed to work on * wi_tableoid OID of the table currently being vacuumed ! * wi_workerpid PID of the running worker, 0 if not yet started * wi_launchtime Time at which this worker was launched * wi_cost_* Vacuum cost-based delay parameters current in this worker * --- 182,188 ---- * wi_links entry into free list or running list * wi_dboid OID of the database this worker is supposed to work on * wi_tableoid OID of the table currently being vacuumed ! * wi_proc pointer to PGPROC of the running worker, NULL if not started * wi_launchtime Time at which this worker was launched * wi_cost_* Vacuum cost-based delay parameters current in this worker * *************** typedef struct WorkerInfoData *** 196,202 **** SHM_QUEUE wi_links; Oid wi_dboid; Oid wi_tableoid; ! int wi_workerpid; TimestampTz wi_launchtime; int wi_cost_delay; int wi_cost_limit; --- 196,202 ---- SHM_QUEUE wi_links; Oid wi_dboid; Oid wi_tableoid; ! PGPROC *wi_proc; TimestampTz wi_launchtime; int wi_cost_delay; int wi_cost_limit; *************** AutoVacLauncherMain(int argc, char *argv *** 694,700 **** worker = (WorkerInfo) MAKE_PTR(AutoVacuumShmem->av_startingWorker); worker->wi_dboid = InvalidOid; worker->wi_tableoid = InvalidOid; ! worker->wi_workerpid = 0; worker->wi_launchtime = 0; worker->wi_links.next = AutoVacuumShmem->av_freeWorkers; AutoVacuumShmem->av_freeWorkers = MAKE_OFFSET(worker); --- 694,700 ---- worker = (WorkerInfo) MAKE_PTR(AutoVacuumShmem->av_startingWorker); worker->wi_dboid = InvalidOid; worker->wi_tableoid = InvalidOid; ! worker->wi_proc = NULL; worker->wi_launchtime = 0; worker->wi_links.next = AutoVacuumShmem->av_freeWorkers; AutoVacuumShmem->av_freeWorkers = MAKE_OFFSET(worker); *************** do_start_worker(void) *** 1198,1204 **** AutoVacuumShmem->av_freeWorkers = worker->wi_links.next; worker->wi_dboid = avdb->adw_datid; ! worker->wi_workerpid = 0; worker->wi_launchtime = GetCurrentTimestamp(); AutoVacuumShmem->av_startingWorker = sworker; --- 1198,1204 ---- AutoVacuumShmem->av_freeWorkers = worker->wi_links.next; worker->wi_dboid = avdb->adw_datid; ! worker->wi_proc = NULL; worker->wi_launchtime = GetCurrentTimestamp(); AutoVacuumShmem->av_startingWorker = sworker; *************** AutoVacWorkerMain(int argc, char *argv[] *** 1542,1548 **** { MyWorkerInfo = (WorkerInfo) MAKE_PTR(AutoVacuumShmem->av_startingWorker); dbid = MyWorkerInfo->wi_dboid; ! MyWorkerInfo->wi_workerpid = MyProcPid; /* insert into the running list */ SHMQueueInsertBefore(&AutoVacuumShmem->av_runningWorkers, --- 1542,1548 ---- { MyWorkerInfo = (WorkerInfo) MAKE_PTR(AutoVacuumShmem->av_startingWorker); dbid = MyWorkerInfo->wi_dboid; ! MyWorkerInfo->wi_proc = MyProc; /* insert into the running list */ SHMQueueInsertBefore(&AutoVacuumShmem->av_runningWorkers, *************** FreeWorkerInfo(int code, Datum arg) *** 1637,1643 **** MyWorkerInfo->wi_links.next = AutoVacuumShmem->av_freeWorkers; MyWorkerInfo->wi_dboid = InvalidOid; MyWorkerInfo->wi_tableoid = InvalidOid; ! MyWorkerInfo->wi_workerpid = 0; MyWorkerInfo->wi_launchtime = 0; MyWorkerInfo->wi_cost_delay = 0; MyWorkerInfo->wi_cost_limit = 0; --- 1637,1643 ---- MyWorkerInfo->wi_links.next = AutoVacuumShmem->av_freeWorkers; MyWorkerInfo->wi_dboid = InvalidOid; MyWorkerInfo->wi_tableoid = InvalidOid; ! MyWorkerInfo->wi_proc = NULL; MyWorkerInfo->wi_launchtime = 0; MyWorkerInfo->wi_cost_delay = 0; MyWorkerInfo->wi_cost_limit = 0; *************** autovac_balance_cost(void) *** 1701,1707 **** offsetof(WorkerInfoData, wi_links)); while (worker) { ! if (worker->wi_workerpid != 0 && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) cost_total += (double) worker->wi_cost_limit_base / worker->wi_cost_delay; --- 1701,1707 ---- offsetof(WorkerInfoData, wi_links)); while (worker) { ! if (worker->wi_proc != NULL && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) cost_total += (double) worker->wi_cost_limit_base / worker->wi_cost_delay; *************** autovac_balance_cost(void) *** 1724,1730 **** offsetof(WorkerInfoData, wi_links)); while (worker) { ! if (worker->wi_workerpid != 0 && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) { int limit = (int) --- 1724,1730 ---- offsetof(WorkerInfoData, wi_links)); while (worker) { ! if (worker->wi_proc != NULL && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) { int limit = (int) *************** autovac_balance_cost(void) *** 1737,1743 **** worker->wi_cost_limit = Max(Min(limit, worker->wi_cost_limit_base), 1); elog(DEBUG2, "autovac_balance_cost(pid=%u db=%u, rel=%u, cost_limit=%d, cost_delay=%d)", ! worker->wi_workerpid, worker->wi_dboid, worker->wi_tableoid, worker->wi_cost_limit, worker->wi_cost_delay); } --- 1737,1743 ---- worker->wi_cost_limit = Max(Min(limit, worker->wi_cost_limit_base), 1); elog(DEBUG2, "autovac_balance_cost(pid=%u db=%u, rel=%u, cost_limit=%d, cost_delay=%d)", ! worker->wi_proc->pid, worker->wi_dboid, worker->wi_tableoid, worker->wi_cost_limit, worker->wi_cost_delay); } *************** next_worker: *** 2062,2086 **** VacuumCostDelay = tab->at_vacuum_cost_delay; VacuumCostLimit = tab->at_vacuum_cost_limit; ! /* ! * Advertise my cost delay parameters for the balancing algorithm, and ! * do a balance ! */ LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); MyWorkerInfo->wi_cost_delay = tab->at_vacuum_cost_delay; MyWorkerInfo->wi_cost_limit = tab->at_vacuum_cost_limit; MyWorkerInfo->wi_cost_limit_base = tab->at_vacuum_cost_limit; autovac_balance_cost(); LWLockRelease(AutovacuumLock); /* clean up memory before each iteration */ MemoryContextResetAndDeleteChildren(PortalContext); /* ! * We will abort vacuuming the current table if we are interrupted, and ! * continue with the next one in schedule; but if anything else ! * happens, we will do our usual error handling which is to cause the ! * worker process to exit. */ PG_TRY(); { --- 2062,2088 ---- VacuumCostDelay = tab->at_vacuum_cost_delay; VacuumCostLimit = tab->at_vacuum_cost_limit; ! /* Last fixups before actually starting to work */ LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); + + /* advertise my cost delay parameters for the balancing algorithm */ MyWorkerInfo->wi_cost_delay = tab->at_vacuum_cost_delay; MyWorkerInfo->wi_cost_limit = tab->at_vacuum_cost_limit; MyWorkerInfo->wi_cost_limit_base = tab->at_vacuum_cost_limit; + + /* do a balance */ autovac_balance_cost(); + + /* done */ LWLockRelease(AutovacuumLock); /* clean up memory before each iteration */ MemoryContextResetAndDeleteChildren(PortalContext); /* ! * We will abort vacuuming the current table if something errors out, ! * and continue with the next one in schedule; in particular, this ! * happens if we are interrupted with SIGINT. */ PG_TRY(); { *************** next_worker: *** 2094,2132 **** } PG_CATCH(); { - ErrorData *errdata; - - MemoryContextSwitchTo(TopTransactionContext); - errdata = CopyErrorData(); - /* ! * If we errored out due to a cancel request, abort and restart the ! * transaction and go to the next table. Otherwise rethrow the ! * error so that the outermost handler deals with it. */ ! if (errdata->sqlerrcode == ERRCODE_QUERY_CANCELED) ! { ! HOLD_INTERRUPTS(); ! elog(LOG, "cancelling autovacuum of table \"%s.%s.%s\"", ! get_database_name(MyDatabaseId), ! get_namespace_name(get_rel_namespace(tab->at_relid)), ! get_rel_name(tab->at_relid)); ! ! AbortOutOfAnyTransaction(); ! FlushErrorState(); ! MemoryContextResetAndDeleteChildren(PortalContext); ! ! /* restart our transaction for the following operations */ ! StartTransactionCommand(); ! RESUME_INTERRUPTS(); ! } else ! PG_RE_THROW(); } PG_END_TRY(); /* be tidy */ pfree(tab); } /* --- 2096,2135 ---- } PG_CATCH(); { /* ! * Abort the transaction, start a new one, and proceed with the ! * next table in our list. */ ! HOLD_INTERRUPTS(); ! if (tab->at_dovacuum) ! errcontext("automatic vacuum of table \"%s.%s.%s\"", ! get_database_name(MyDatabaseId), ! get_namespace_name(get_rel_namespace(tab->at_relid)), ! get_rel_name(tab->at_relid)); else ! errcontext("automatic analyze of table \"%s.%s.%s\"", ! get_database_name(MyDatabaseId), ! get_namespace_name(get_rel_namespace(tab->at_relid)), ! get_rel_name(tab->at_relid)); ! EmitErrorReport(); ! ! AbortOutOfAnyTransaction(); ! FlushErrorState(); ! MemoryContextResetAndDeleteChildren(PortalContext); ! ! /* restart our transaction for the following operations */ ! StartTransactionCommand(); ! RESUME_INTERRUPTS(); } PG_END_TRY(); /* be tidy */ pfree(tab); + + /* remove my info from shared memory */ + LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); + MyWorkerInfo->wi_tableoid = InvalidOid; + LWLockRelease(AutovacuumLock); } /* Index: src/backend/storage/ipc/procarray.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/storage/ipc/procarray.c,v retrieving revision 1.35 diff -c -p -r1.35 procarray.c *** src/backend/storage/ipc/procarray.c 23 Sep 2007 18:50:38 -0000 1.35 --- src/backend/storage/ipc/procarray.c 23 Oct 2007 20:31:13 -0000 *************** ProcArrayEndTransaction(PGPROC *proc, Tr *** 242,248 **** proc->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; ! proc->inVacuum = false; /* must be cleared with xid/xmin */ proc->inCommit = false; /* be sure this is cleared in abort */ /* Clear the subtransaction-XID cache too while holding the lock */ --- 242,248 ---- proc->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; ! proc->vacuumFlags &= ~PROC_IN_VACUUM; /* must be cleared with xid/xmin */ proc->inCommit = false; /* be sure this is cleared in abort */ /* Clear the subtransaction-XID cache too while holding the lock */ *************** ProcArrayEndTransaction(PGPROC *proc, Tr *** 267,273 **** proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; ! proc->inVacuum = false; /* must be cleared with xid/xmin */ proc->inCommit = false; /* be sure this is cleared in abort */ Assert(proc->subxids.nxids == 0); --- 267,273 ---- proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; ! proc->vacuumFlags &= ~PROC_IN_VACUUM; /* must be cleared with xid/xmin */ proc->inCommit = false; /* be sure this is cleared in abort */ Assert(proc->subxids.nxids == 0); *************** ProcArrayClearTransaction(PGPROC *proc) *** 296,302 **** proc->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; ! proc->inVacuum = false; /* redundant, but just in case */ proc->inCommit = false; /* ditto */ /* Clear the subtransaction-XID cache too */ --- 296,302 ---- proc->xid = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; ! proc->vacuumFlags &= ~PROC_IN_VACUUM; /* redundant, but just in case */ proc->inCommit = false; /* ditto */ /* Clear the subtransaction-XID cache too */ *************** TransactionIdIsActive(TransactionId xid) *** 546,552 **** * If allDbs is TRUE then all backends are considered; if allDbs is FALSE * then only backends running in my own database are considered. * ! * If ignoreVacuum is TRUE then backends with inVacuum set are ignored. * * This is used by VACUUM to decide which deleted tuples must be preserved * in a table. allDbs = TRUE is needed for shared relations, but allDbs = --- 546,553 ---- * If allDbs is TRUE then all backends are considered; if allDbs is FALSE * then only backends running in my own database are considered. * ! * If ignoreVacuum is TRUE then backends with the PROC_IN_VACUUM flag set are ! * ignored. * * This is used by VACUUM to decide which deleted tuples must be preserved * in a table. allDbs = TRUE is needed for shared relations, but allDbs = *************** GetOldestXmin(bool allDbs, bool ignoreVa *** 586,592 **** { volatile PGPROC *proc = arrayP->procs[index]; ! if (ignoreVacuum && proc->inVacuum) continue; if (allDbs || proc->databaseId == MyDatabaseId) --- 587,593 ---- { volatile PGPROC *proc = arrayP->procs[index]; ! if (ignoreVacuum && (proc->vacuumFlags & PROC_IN_VACUUM)) continue; if (allDbs || proc->databaseId == MyDatabaseId) *************** GetSnapshotData(Snapshot snapshot, bool *** 723,729 **** TransactionId xid; /* Ignore procs running LAZY VACUUM */ ! if (proc->inVacuum) continue; /* Update globalxmin to be the smallest valid xmin */ --- 724,730 ---- TransactionId xid; /* Ignore procs running LAZY VACUUM */ ! if (proc->vacuumFlags & PROC_IN_VACUUM) continue; /* Update globalxmin to be the smallest valid xmin */ *************** CheckOtherDBBackends(Oid databaseId) *** 1193,1199 **** found = true; ! if (proc->isAutovacuum) { /* an autovacuum --- send it SIGTERM before sleeping */ int autopid = proc->pid; --- 1194,1200 ---- found = true; ! if (proc->vacuumFlags & PROC_IS_AUTOVACUUM) { /* an autovacuum --- send it SIGTERM before sleeping */ int autopid = proc->pid; Index: src/backend/storage/lmgr/proc.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/storage/lmgr/proc.c,v retrieving revision 1.194 diff -c -p -r1.194 proc.c *** src/backend/storage/lmgr/proc.c 8 Sep 2007 20:31:15 -0000 1.194 --- src/backend/storage/lmgr/proc.c 23 Oct 2007 20:25:08 -0000 *************** InitProcess(void) *** 291,298 **** MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->inCommit = false; ! MyProc->inVacuum = false; ! MyProc->isAutovacuum = IsAutoVacuumWorkerProcess(); MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; --- 291,298 ---- MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->inCommit = false; ! if (IsAutoVacuumWorkerProcess()) ! MyProc->vacuumFlags |= PROC_IS_AUTOVACUUM; MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; *************** InitAuxiliaryProcess(void) *** 429,436 **** MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->inCommit = false; ! MyProc->inVacuum = false; ! MyProc->isAutovacuum = IsAutoVacuumLauncherProcess(); /* is this needed? */ MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; --- 429,436 ---- MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->inCommit = false; ! /* we don't set the "is autovacuum" flag in the launcher */ ! MyProc->vacuumFlags = 0; MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; Index: src/include/storage/proc.h =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/include/storage/proc.h,v retrieving revision 1.100 diff -c -p -r1.100 proc.h *** src/include/storage/proc.h 5 Sep 2007 18:10:48 -0000 1.100 --- src/include/storage/proc.h 23 Oct 2007 21:10:46 -0000 *************** struct XidCache *** 38,43 **** --- 38,47 ---- TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS]; }; + /* Flags for PGPROC->vacuumFlags */ + #define PROC_IS_AUTOVACUUM (1 << 0) /* is it an autovac worker? */ + #define PROC_IN_VACUUM (1 << 1) /* currently running lazy vacuum */ + /* * Each backend has a PGPROC struct in shared memory. There is also a list of * currently-unused PGPROC structs that will be reallocated to new backends. *************** struct PGPROC *** 82,93 **** bool inCommit; /* true if within commit critical section */ - bool inVacuum; /* true if current xact is a LAZY VACUUM */ - bool isAutovacuum; /* true if it's autovacuum */ - /* Info about LWLock the process is currently waiting for, if any. */ bool lwWaiting; /* true if waiting for an LW lock */ bool lwExclusive; /* true if waiting for exclusive access */ struct PGPROC *lwWaitLink; /* next waiter for same LW lock */ /* Info about lock the process is currently waiting for, if any. */ --- 86,96 ---- bool inCommit; /* true if within commit critical section */ /* Info about LWLock the process is currently waiting for, if any. */ bool lwWaiting; /* true if waiting for an LW lock */ bool lwExclusive; /* true if waiting for exclusive access */ + + char vacuumFlags; /* vacuum-related flags, see above */ struct PGPROC *lwWaitLink; /* next waiter for same LW lock */ /* Info about lock the process is currently waiting for, if any. */
---------------------------(end of broadcast)--------------------------- TIP 3: Have you checked our extensive FAQ? http://www.postgresql.org/docs/faq