diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index a8d4dfdd7c..3c2027e77c 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -1947,6 +1947,8 @@ do_autovacuum(void) bool did_vacuum = false; bool found_concurrent_worker = false; int i; + int skipped, modulo, to_skip; + skipped = modulo = to_skip = 0; /* * StartTransactionCommand and CommitTransactionCommand will automatically @@ -2325,6 +2327,66 @@ do_autovacuum(void) */ } + /* + * If we've had to skip a large number of tables consecutively, that's + * an indication that either our initial list is now very out-of-date + * with reality, or we are competing with other workers to process a + * lot of really small tables. Both cases are problematic because on a + * database with enough tables to trigger this the hash search to find + * updated stats for the table we're looking at (in + * table_recheck_autovac) will be non-trivially expensive. When this + * happens, we need to take some defensive measures to avoid hopelessly + * spinning our wheels. + */ + if (skipped > 1000) + { + if (modulo == 0) + { + /* + * The first time we get here, we just assume it's because we're + * competing with other workers over the same set of tables, not + * because our list is very out of date. If it turns out our list + * is way out of date we'll quickly max out skipped again and exit + * the loop. + * + * Figure out how many other workers are handling this database and + * start skipping enough records to "stay ahead of" them. This + * doesn't need to be perfect; the goal is simply to try and get + * real work done. If it turns out there's no competing workers + * we'll break out soon enough anyway. + */ + LWLockAcquire(AutovacuumLock, LW_SHARED); + dlist_foreach(iter, &AutoVacuumShmem->av_runningWorkers) + { + WorkerInfo worker = dlist_container(WorkerInfoData, wi_links, iter.cur); + + /* we intentionally count ourselves to ensure modulo > 0 */ + + /* ignore workers in other databases */ + if (worker->wi_dboid != MyDatabaseId) + continue; + + modulo++; + } + LWLockRelease(AutovacuumLock); + to_skip = modulo; + } + /* + * Handle the case of our list being hopelessly out of date. In + * this scenario we built a very large initial list, then spent + * enough time processing a table (while other workers carried on) + * that our list is hopelessly out of date, so just exit and let + * the launcher fire up a new worker. + */ + else + break; + } + + if (--to_skip > 0) + continue; + else + to_skip = modulo; + /* * Find out whether the table is shared or not. (It's slightly * annoying to fetch the syscache entry just for this, but in typical @@ -2408,8 +2470,11 @@ do_autovacuum(void) MyWorkerInfo->wi_tableoid = InvalidOid; MyWorkerInfo->wi_sharedrel = false; LWLockRelease(AutovacuumScheduleLock); + skipped++; continue; } + else + skipped = 0; /* * Remember the prevailing values of the vacuum cost GUCs. We have to