Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuples inaccurate. - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuples inaccurate. |
Date | |
Msg-id | 27818.1520892301@sss.pgh.pa.us Whole thread Raw |
In response to | Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuples inaccurate. (Tom Lane <tgl@sss.pgh.pa.us>) |
List | pgsql-hackers |
I wrote: > Re-reading that thread, it seems like we should have applied Jeff's > initial trivial patch[1] (to not hold AutovacuumScheduleLock across > table_recheck_autovac) rather than waiting around for a super duper > improvement to get agreed on. I'm a bit tempted to go do that; > if nothing else, it seems simple enough to back-patch, unlike most > of the rest of what was discussed. Jeff mentioned that that patch wasn't entirely trivial to rebase over 15739393e, and I now see why: in the code structure as it stands, we don't know soon enough whether the table is shared. In the attached rebase, I solved that with the brute-force method of just reading the pg_class tuple an extra time. I think this is probably good enough, really. I thought about having do_autovacuum pass down the tuple to table_recheck_autovac so as to not increase the net number of syscache fetches, but I'm slightly worried about whether we could be passing a stale pg_class tuple to table_recheck_autovac if we do it like that. OTOH, that just raises the question of why we are doing any of this while holding no lock whatever on the target table :-(. I'm content to leave that question to the major redesign that was speculated about in the bug #13750 thread. This also corrects the inconsistency that at the bottom, do_autovacuum clears wi_tableoid while taking AutovacuumLock, not AutovacuumScheduleLock as is the documented lock for that field. There's no actual bug there, but cleaning this up might provide a slight improvement in concurrency for operations that need AutovacuumLock but aren't looking at other processes' wi_tableoid. (Alternatively, we could decide that there's no real need anymore for the separate AutovacuumScheduleLock, but that's more churn than I wanted to deal with here.) I think this is OK to commit/backpatch --- any objections? regards, tom lane diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 21f5e2c..639bd71 100644 *** a/src/backend/postmaster/autovacuum.c --- b/src/backend/postmaster/autovacuum.c *************** typedef struct autovac_table *** 212,220 **** * wi_launchtime Time at which this worker was launched * wi_cost_* Vacuum cost-based delay parameters current in this worker * ! * All fields are protected by AutovacuumLock, except for wi_tableoid which is ! * protected by AutovacuumScheduleLock (which is read-only for everyone except ! * that worker itself). *------------- */ typedef struct WorkerInfoData --- 212,220 ---- * wi_launchtime Time at which this worker was launched * wi_cost_* Vacuum cost-based delay parameters current in this worker * ! * All fields are protected by AutovacuumLock, except for wi_tableoid and ! * wi_sharedrel which are protected by AutovacuumScheduleLock (note these ! * two fields are read-only for everyone except that worker itself). *------------- */ typedef struct WorkerInfoData *************** do_autovacuum(void) *** 2317,2323 **** --- 2317,2325 ---- foreach(cell, table_oids) { Oid relid = lfirst_oid(cell); + HeapTuple classTup; autovac_table *tab; + bool isshared; bool skipit; int stdVacuumCostDelay; int stdVacuumCostLimit; *************** do_autovacuum(void) *** 2342,2350 **** } /* ! * hold schedule lock from here until we're sure that this table still ! * needs vacuuming. We also need the AutovacuumLock to walk the ! * worker array, but we'll let go of that one quickly. */ LWLockAcquire(AutovacuumScheduleLock, LW_EXCLUSIVE); LWLockAcquire(AutovacuumLock, LW_SHARED); --- 2344,2366 ---- } /* ! * Find out whether the table is shared or not. (It's slightly ! * annoying to fetch the syscache entry just for this, but in typical ! * cases it adds little cost because table_recheck_autovac would ! * refetch the entry anyway. We could buy that back by copying the ! * tuple here and passing it to table_recheck_autovac, but that ! * increases the odds of that function working with stale data.) ! */ ! classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); ! if (!HeapTupleIsValid(classTup)) ! continue; /* somebody deleted the rel, forget it */ ! isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; ! ReleaseSysCache(classTup); ! ! /* ! * Hold schedule lock from here until we've claimed the table. We ! * also need the AutovacuumLock to walk the worker array, but that one ! * can just be a shared lock. */ LWLockAcquire(AutovacuumScheduleLock, LW_EXCLUSIVE); LWLockAcquire(AutovacuumLock, LW_SHARED); *************** do_autovacuum(void) *** 2381,2386 **** --- 2397,2412 ---- } /* + * Store the table's OID in shared memory before releasing the + * schedule lock, so that other workers don't try to vacuum it + * concurrently. (We claim it here so as not to hold + * AutovacuumScheduleLock while rechecking the stats.) + */ + MyWorkerInfo->wi_tableoid = relid; + MyWorkerInfo->wi_sharedrel = isshared; + LWLockRelease(AutovacuumScheduleLock); + + /* * Check whether pgstat data still says we need to vacuum this table. * It could have changed if something else processed the table while * we weren't looking. *************** do_autovacuum(void) *** 2396,2414 **** if (tab == NULL) { /* someone else vacuumed the table, or it went away */ LWLockRelease(AutovacuumScheduleLock); continue; } /* - * Ok, good to go. Store the table in shared memory before releasing - * the lock so that other workers don't vacuum it concurrently. - */ - MyWorkerInfo->wi_tableoid = relid; - MyWorkerInfo->wi_sharedrel = tab->at_sharedrel; - LWLockRelease(AutovacuumScheduleLock); - - /* * Remember the prevailing values of the vacuum cost GUCs. We have to * restore these at the bottom of the loop, else we'll compute wrong * values in the next iteration of autovac_balance_cost(). --- 2422,2435 ---- if (tab == NULL) { /* someone else vacuumed the table, or it went away */ + LWLockAcquire(AutovacuumScheduleLock, LW_EXCLUSIVE); + MyWorkerInfo->wi_tableoid = InvalidOid; + MyWorkerInfo->wi_sharedrel = false; LWLockRelease(AutovacuumScheduleLock); continue; } /* * Remember the prevailing values of the vacuum cost GUCs. We have to * restore these at the bottom of the loop, else we'll compute wrong * values in the next iteration of autovac_balance_cost(). *************** deleted: *** 2522,2531 **** * settings, so we don't want to give up our share of I/O for a very * short interval and thereby thrash the global balance. */ ! LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); MyWorkerInfo->wi_tableoid = InvalidOid; MyWorkerInfo->wi_sharedrel = false; ! LWLockRelease(AutovacuumLock); /* restore vacuum cost GUCs for the next iteration */ VacuumCostDelay = stdVacuumCostDelay; --- 2543,2552 ---- * settings, so we don't want to give up our share of I/O for a very * short interval and thereby thrash the global balance. */ ! LWLockAcquire(AutovacuumScheduleLock, LW_EXCLUSIVE); MyWorkerInfo->wi_tableoid = InvalidOid; MyWorkerInfo->wi_sharedrel = false; ! LWLockRelease(AutovacuumScheduleLock); /* restore vacuum cost GUCs for the next iteration */ VacuumCostDelay = stdVacuumCostDelay;
pgsql-hackers by date: