@ -55,7 +55,7 @@
*
*
* IDENTIFICATION
* $ PostgreSQL : pgsql / src / backend / postmaster / autovacuum . c , v 1.83 2008 / 07 / 23 20 : 20 : 1 0 alvherre Exp $
* $ PostgreSQL : pgsql / src / backend / postmaster / autovacuum . c , v 1.84 2008 / 08 / 13 00 : 07 : 5 0 alvherre Exp $
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
@ -93,6 +93,7 @@
# include "storage/procarray.h"
# include "storage/sinvaladt.h"
# include "tcop/tcopprot.h"
# include "utils/dynahash.h"
# include "utils/flatfiles.h"
# include "utils/fmgroids.h"
# include "utils/lsyscache.h"
@ -161,8 +162,8 @@ typedef struct avw_dbase
/* struct to keep track of tables to vacuum and/or analyze, in 1st pass */
typedef struct av_relation
{
Oid ar_toastrelid ; /* hash key - must be first */
Oid ar_relid ;
Oid ar_toastrelid ;
} av_relation ;
/* struct to keep track of tables to vacuum and/or analyze, after rechecking */
@ -279,7 +280,7 @@ static void autovac_balance_cost(void);
static void do_autovacuum ( void ) ;
static void FreeWorkerInfo ( int code , Datum arg ) ;
static autovac_table * table_recheck_autovac ( Oid relid ) ;
static autovac_table * table_recheck_autovac ( Oid relid , HTAB * table_toast_map ) ;
static void relation_needs_vacanalyze ( Oid relid , Form_pg_autovacuum avForm ,
Form_pg_class classForm ,
PgStat_StatTabEntry * tabentry , bool * dovacuum ,
@ -287,7 +288,8 @@ static void relation_needs_vacanalyze(Oid relid, Form_pg_autovacuum avForm,
static void autovacuum_do_vac_analyze ( autovac_table * tab ,
BufferAccessStrategy bstrategy ) ;
static HeapTuple get_pg_autovacuum_tuple_relid ( Relation avRel , Oid relid ) ;
static HeapTuple get_pg_autovacuum_tuple_relid ( Relation avRel , Oid relid ,
HTAB * table_toast_map ) ;
static PgStat_StatTabEntry * get_pgstat_tabentry_relid ( Oid relid , bool isshared ,
PgStat_StatDBEntry * shared ,
PgStat_StatDBEntry * dbentry ) ;
@ -1821,12 +1823,13 @@ do_autovacuum(void)
HeapScanDesc relScan ;
Form_pg_database dbForm ;
List * table_oids = NIL ;
List * toast_oids = NIL ;
List * table_toast_list = NIL ;
HASHCTL ctl ;
HTAB * table_toast_map ;
ListCell * volatile cell ;
PgStat_StatDBEntry * shared ;
PgStat_StatDBEntry * dbentry ;
BufferAccessStrategy bstrategy ;
ScanKeyData key ;
/*
* StartTransactionCommand and CommitTransactionCommand will automatically
@ -1884,25 +1887,42 @@ do_autovacuum(void)
classRel = heap_open ( RelationRelationId , AccessShareLock ) ;
avRel = heap_open ( AutovacuumRelationId , AccessShareLock ) ;
/* create hash table for toast <-> main relid mapping */
MemSet ( & ctl , 0 , sizeof ( ctl ) ) ;
ctl . keysize = sizeof ( Oid ) ;
ctl . entrysize = sizeof ( Oid ) * 2 ;
ctl . hash = oid_hash ;
table_toast_map = hash_create ( " TOAST to main relid map " ,
100 ,
& ctl ,
HASH_ELEM | HASH_FUNCTION ) ;
/*
* Scan pg_class and determine which tables to vacuum .
* Scan pg_class to determine which tables to vacuum .
*
* The stats subsystem collects stats for toast tables independently of
* the stats for their parent tables . We need to check those stats since
* in cases with short , wide tables there might be proportionally much
* more activity in the toast table than in its parent .
* We do this in two passes : on the first one we collect the list of
* plain relations , and on the second one we collect TOAST tables .
* The reason for doing the second pass is that during it we want to use
* the main relation ' s pg_autovacuum entry if the TOAST table does not have
* any , and we cannot obtain it unless we know beforehand what ' s the main
* table OID .
*
* Since we can only issue VACUUM against the parent table , we need to
* transpose a decision to vacuum a toast table into a decision to vacuum
* its parent . There ' s no point in considering ANALYZE on a toast table ,
* either . To support this , we keep a list of OIDs of toast tables that
* need vacuuming alongside the list of regular tables . Regular tables
* will be entered into the table list even if they appear not to need
* vacuuming ; we go back and re - mark them after finding all the vacuumable
* toast tables .
* We need to check TOAST tables separately because in cases with short ,
* wide tables there might be proportionally much more activity in the
* TOAST table than in its parent .
*/
relScan = heap_beginscan ( classRel , SnapshotNow , 0 , NULL ) ;
ScanKeyInit ( & key ,
Anum_pg_class_relkind ,
BTEqualStrategyNumber , F_CHAREQ ,
CharGetDatum ( RELKIND_RELATION ) ) ;
relScan = heap_beginscan ( classRel , SnapshotNow , 1 , & key ) ;
/*
* On the first pass , we collect main tables to vacuum , and also the
* main table relid to TOAST relid mapping .
*/
while ( ( tuple = heap_getnext ( relScan , ForwardScanDirection ) ) ! = NULL )
{
Form_pg_class classForm = ( Form_pg_class ) GETSTRUCT ( tuple ) ;
@ -1915,15 +1935,10 @@ do_autovacuum(void)
bool wraparound ;
int backendID ;
/* Consider only regular and toast tables. */
if ( classForm - > relkind ! = RELKIND_RELATION & &
classForm - > relkind ! = RELKIND_TOASTVALUE )
continue ;
relid = HeapTupleGetOid ( tuple ) ;
/* Fetch the pg_autovacuum tuple for the relation, if any */
avTup = get_pg_autovacuum_tuple_relid ( avRel , relid ) ;
avTup = get_pg_autovacuum_tuple_relid ( avRel , relid , NULL ) ;
if ( HeapTupleIsValid ( avTup ) )
avForm = ( Form_pg_autovacuum ) GETSTRUCT ( avTup ) ;
@ -1952,7 +1967,7 @@ do_autovacuum(void)
* vacuum for wraparound , forcibly drop it . Otherwise just
* log a complaint .
*/
if ( wraparound & & classForm - > relkind = = RELKIND_RELATION )
if ( wraparound )
{
ObjectAddress object ;
@ -1976,65 +1991,88 @@ do_autovacuum(void)
}
}
}
else if ( classForm - > relkind = = RELKIND_RELATION )
else
{
/* Plain relations that need work are added to table_oids */
if ( dovacuum | | doanalyze )
table_oids = lappend_oid ( table_oids , relid ) ;
else if ( OidIsValid ( classForm - > reltoastrelid ) )
/*
* Remember the association for the second pass . Note : we must do
* this even if the table is going to be vacuumed , because we
* don ' t automatically vacuum toast tables along the parent table .
*/
if ( OidIsValid ( classForm - > reltoastrelid ) )
{
/*
* If it doesn ' t appear to need vacuuming , but it has a toast
* table , remember the association to revisit below .
*/
av_relation * rel = palloc ( sizeof ( av_relation ) ) ;
av_relation * hentry ;
bool found ;
rel - > ar_relid = relid ;
rel - > ar_toastrelid = classForm - > reltoastrelid ;
hentry = hash_search ( table_toast_map ,
& classForm - > reltoastrelid ,
HASH_ENTER , & found ) ;
table_toast_list = lappend ( table_toast_list , rel ) ;
if ( ! found )
{
/* hash_search already filled in the key */
hentry - > ar_relid = relid ;
}
}
}
else
{
/* TOAST relations that need vacuum are added to toast_oids */
if ( dovacuum )
toast_oids = lappend_oid ( toast_oids , relid ) ;
}
if ( HeapTupleIsValid ( avTup ) )
heap_freetuple ( avTup ) ;
}
heap_endscan ( relScan ) ;
heap_close ( avRel , AccessShareLock ) ;
heap_close ( classRel , AccessShareLock ) ;
/*
* Add to the list of tables to vacuum , the OIDs of the tables that
* correspond to the saved OIDs of toast tables needing vacuum .
*/
foreach ( cell , toast_oids )
/* second pass: check TOAST tables */
ScanKeyInit ( & key ,
Anum_pg_class_relkind ,
BTEqualStrategyNumber , F_CHAREQ ,
CharGetDatum ( RELKIND_TOASTVALUE ) ) ;
relScan = heap_beginscan ( classRel , SnapshotNow , 1 , & key ) ;
while ( ( tuple = heap_getnext ( relScan , ForwardScanDirection ) ) ! = NULL )
{
Oid toastoid = lfirst_oid ( cell ) ;
ListCell * cell2 ;
Form_pg_class classForm = ( Form_pg_class ) GETSTRUCT ( tuple ) ;
Form_pg_autovacuum avForm = NULL ;
PgStat_StatTabEntry * tabentry ;
HeapTuple avTup ;
Oid relid ;
bool dovacuum ;
bool doanalyze ;
bool wraparound ;
foreach ( cell2 , table_toast_list )
{
av_relation * ar = lfirst ( cell2 ) ;
/*
* Skip temp tables ( i . e . those in temp namespaces ) . We cannot safely
* process other backends ' temp tables .
*/
if ( isAnyTempNamespace ( classForm - > relnamespace ) )
continue ;
if ( ar - > ar_toastrelid = = toastoid )
{
table_oids = lappend_oid ( table_oids , ar - > ar_relid ) ;
break ;
}
}
relid = HeapTupleGetOid ( tuple ) ;
/* Fetch the pg_autovacuum tuple for this rel */
avTup = get_pg_autovacuum_tuple_relid ( avRel , relid , table_toast_map ) ;
if ( HeapTupleIsValid ( avTup ) )
avForm = ( Form_pg_autovacuum ) GETSTRUCT ( avTup ) ;
/* Fetch the pgstat entry for this table */
tabentry = get_pgstat_tabentry_relid ( relid , classForm - > relisshared ,
shared , dbentry ) ;
relation_needs_vacanalyze ( relid , avForm , classForm , tabentry ,
& dovacuum , & doanalyze , & wraparound ) ;
/* ignore analyze for toast tables */
if ( dovacuum )
table_oids = lappend_oid ( table_oids , relid ) ;
}
list_free_deep ( table_toast_list ) ;
table_toast_list = NIL ;
list_free ( toast_oids ) ;
toast_oids = NIL ;
heap_endscan ( relScan ) ;
heap_close ( avRel , AccessShareLock ) ;
heap_close ( classRel , AccessShareLock ) ;
/*
* Create a buffer access strategy object for VACUUM to use . We want to
@ -2118,7 +2156,7 @@ do_autovacuum(void)
* vacuumed in the last 500 ms ( PGSTAT_STAT_INTERVAL ) . This is a bug .
*/
MemoryContextSwitchTo ( AutovacMemCxt ) ;
tab = table_recheck_autovac ( relid ) ;
tab = table_recheck_autovac ( relid , table_toast_map ) ;
if ( tab = = NULL )
{
/* someone else vacuumed the table */
@ -2231,6 +2269,11 @@ deleted:
LWLockRelease ( AutovacuumLock ) ;
}
/*
* We leak table_toast_map here ( among other things ) , but since we ' re going
* away soon , it ' s not a problem .
*/
/*
* Update pg_database . datfrozenxid , and truncate pg_clog if possible . We
* only need to do this once , not after each table .
@ -2244,9 +2287,14 @@ deleted:
/*
* Returns a copy of the pg_autovacuum tuple for the given relid , or NULL if
* there isn ' t any . avRel is pg_autovacuum , already open and suitably locked .
*
* If table_toast_map is not null , use it to find an alternative OID with which
* to search a pg_autovacuum entry , if the passed relid does not yield one
* directly .
*/
static HeapTuple
get_pg_autovacuum_tuple_relid ( Relation avRel , Oid relid )
get_pg_autovacuum_tuple_relid ( Relation avRel , Oid relid ,
HTAB * table_toast_map )
{
ScanKeyData entry [ 1 ] ;
SysScanDesc avScan ;
@ -2267,6 +2315,18 @@ get_pg_autovacuum_tuple_relid(Relation avRel, Oid relid)
systable_endscan ( avScan ) ;
if ( ! HeapTupleIsValid ( avTup ) & & table_toast_map ! = NULL )
{
av_relation * hentry ;
bool found ;
hentry = hash_search ( table_toast_map , & relid , HASH_FIND , & found ) ;
if ( found )
/* avoid second recursion */
avTup = get_pg_autovacuum_tuple_relid ( avRel , hentry - > ar_relid ,
NULL ) ;
}
return avTup ;
}
@ -2297,14 +2357,13 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared,
/*
* table_recheck_autovac
*
* Recheck whether a plain table still needs vacuum or analyze ; be it because
* it does directly , or because its TOAST table does . Return value is a valid
* autovac_table pointer if it does , NULL otherwise .
* Recheck whether a table still needs vacuum or analyze . Return value is a
* valid autovac_table pointer if it does , NULL otherwise .
*
* Note that the returned autovac_table does not have the name fields set .
*/
static autovac_table *
table_recheck_autovac ( Oid relid )
table_recheck_autovac ( Oid relid , HTAB * table_toast_map )
{
Form_pg_autovacuum avForm = NULL ;
Form_pg_class classForm ;
@ -2315,11 +2374,9 @@ table_recheck_autovac(Oid relid)
bool doanalyze ;
autovac_table * tab = NULL ;
PgStat_StatTabEntry * tabentry ;
bool doit = false ;
PgStat_StatDBEntry * shared ;
PgStat_StatDBEntry * dbentry ;
bool wraparound ,
toast_wraparound = false ;
bool wraparound ;
/* use fresh stats */
autovac_refresh_stats ( ) ;
@ -2335,9 +2392,15 @@ table_recheck_autovac(Oid relid)
return NULL ;
classForm = ( Form_pg_class ) GETSTRUCT ( classTup ) ;
/* fetch the pg_autovacuum entry, if any */
/*
* Fetch the pg_autovacuum entry , if any . For a toast table , also try the
* main rel ' s pg_autovacuum entry if there isn ' t one for the TOAST table
* itself .
*/
avRel = heap_open ( AutovacuumRelationId , AccessShareLock ) ;
avTup = get_pg_autovacuum_tuple_relid ( avRel , relid ) ;
avTup = get_pg_autovacuum_tuple_relid ( avRel , relid ,
classForm - > relkind = = RELKIND_TOASTVALUE ? table_toast_map : NULL ) ;
if ( HeapTupleIsValid ( avTup ) )
avForm = ( Form_pg_autovacuum ) GETSTRUCT ( avTup ) ;
@ -2348,51 +2411,12 @@ table_recheck_autovac(Oid relid)
relation_needs_vacanalyze ( relid , avForm , classForm , tabentry ,
& dovacuum , & doanalyze , & wraparound ) ;
/* OK, it needs vacuum by itself */
if ( dovacuum )
doit = true ;
/* it doesn't need vacuum, but what about it's TOAST table? */
else if ( OidIsValid ( classForm - > reltoastrelid ) )
{
Oid toastrelid = classForm - > reltoastrelid ;
HeapTuple toastClassTup ;
toastClassTup = SearchSysCacheCopy ( RELOID ,
ObjectIdGetDatum ( toastrelid ) ,
0 , 0 , 0 ) ;
if ( HeapTupleIsValid ( toastClassTup ) )
{
bool toast_dovacuum ;
bool toast_doanalyze ;
bool toast_wraparound ;
Form_pg_class toastClassForm ;
PgStat_StatTabEntry * toasttabentry ;
toastClassForm = ( Form_pg_class ) GETSTRUCT ( toastClassTup ) ;
toasttabentry = get_pgstat_tabentry_relid ( toastrelid ,
toastClassForm - > relisshared ,
shared , dbentry ) ;
/* note we use the pg_autovacuum entry for the main table */
relation_needs_vacanalyze ( toastrelid , avForm ,
toastClassForm , toasttabentry ,
& toast_dovacuum , & toast_doanalyze ,
& toast_wraparound ) ;
/* we only consider VACUUM for toast tables */
if ( toast_dovacuum )
{
dovacuum = true ;
doit = true ;
}
heap_freetuple ( toastClassTup ) ;
}
}
if ( doanalyze )
doit = true ;
/* ignore ANALYZE for toast tables */
if ( classForm - > relkind = = RELKIND_TOASTVALUE )
doanalyze = false ;
if ( doit )
/* OK, it needs something done */
if ( doanalyze | | dovacuum )
{
int freeze_min_age ;
int vac_cost_limit ;
@ -2439,7 +2463,7 @@ table_recheck_autovac(Oid relid)
tab - > at_freeze_min_age = freeze_min_age ;
tab - > at_vacuum_cost_limit = vac_cost_limit ;
tab - > at_vacuum_cost_delay = vac_cost_delay ;
tab - > at_wraparound = wraparound | | toast_wraparound ;
tab - > at_wraparound = wraparound ;
tab - > at_relname = NULL ;
tab - > at_nspname = NULL ;
tab - > at_datname = NULL ;
@ -2633,7 +2657,7 @@ autovacuum_do_vac_analyze(autovac_table *tab,
/* Let pgstat know what we're doing */
autovac_report_activity ( tab ) ;
vacuum ( & vacstmt , tab - > at_relid , bstrategy , tab - > at_wraparound , true ) ;
vacuum ( & vacstmt , tab - > at_relid , false , bstrategy , tab - > at_wraparound , true ) ;
}
/*