autovac.patch

application/octet-stream

Filename: autovac.patch
Type: application/octet-stream
Part: 0
Message: FW: autovac issue with large number of tables

Patch

Format: unified
File+
src/backend/postmaster/autovacuum.c 65 0
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