@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $ PostgreSQL : pgsql / src / backend / postmaster / autovacuum . c , v 1.2 2005 / 07 / 29 19 : 30 : 0 4 tgl Exp $
* $ PostgreSQL : pgsql / src / backend / postmaster / autovacuum . c , v 1.3 2005 / 08 / 11 21 : 11 : 4 4 tgl Exp $
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
@ -27,6 +27,7 @@
# include "catalog/indexing.h"
# include "catalog/namespace.h"
# include "catalog/pg_autovacuum.h"
# include "catalog/pg_database.h"
# include "commands/vacuum.h"
# include "libpq/hba.h"
# include "libpq/pqsignal.h"
@ -57,6 +58,9 @@ double autovacuum_vac_scale;
int autovacuum_anl_thresh ;
double autovacuum_anl_scale ;
int autovacuum_vac_cost_delay ;
int autovacuum_vac_cost_limit ;
/* Flag to tell if we are in the autovacuum daemon process */
static bool am_autovacuum = false ;
@ -64,27 +68,43 @@ static bool am_autovacuum = false;
static time_t last_autovac_start_time = 0 ;
static time_t last_autovac_stop_time = 0 ;
/* Memory context for long-lived data */
static MemoryContext AutovacMemCxt ;
/* struct to keep list of candidate databases for vacuum */
typedef struct autovac_dbase
{
Oid oid ;
char * name ;
TransactionId frozenxid ;
TransactionId vacuumxid ;
PgStat_StatDBEntry * entry ;
int32 age ;
} autovac_dbase ;
/* struct to keep track of tables to vacuum and/or analyze */
typedef struct autovac_table
{
Oid relid ;
bool dovacuum ;
bool doanalyze ;
int vacuum_cost_delay ;
int vacuum_cost_limit ;
} autovac_table ;
# ifdef EXEC_BACKEND
static pid_t autovac_forkexec ( void ) ;
# endif
NON_EXEC_STATIC void AutoVacMain ( int argc , char * argv [ ] ) ;
static void do_autovacuum ( bool whole_db , PgStat_StatDBEntry * dbentry ) ;
static void process_whole_db ( void ) ;
static void do_autovacuum ( PgStat_StatDBEntry * dbentry ) ;
static List * autovac_get_database_list ( void ) ;
static void test_rel_for_autovac ( Oid relid , PgStat_StatTabEntry * tabentry ,
Form_pg_class classForm , Form_pg_autovacuum avForm ,
List * * vacuum_tables , List * * analyze_tables ) ;
static void autovacuum_do_vac_analyze ( List * relids , bool dovacuum ) ;
List * * vacuum_tables ) ;
static void autovacuum_do_vac_analyze ( List * relids , bool dovacuum ,
bool doanalyze , bool freeze ) ;
/*
@ -210,6 +230,12 @@ AutoVacMain(int argc, char *argv[])
/* Lose the postmaster's on-exit routines */
on_exit_reset ( ) ;
/* Identify myself via ps */
init_ps_display ( " autovacuum process " , " " , " " ) ;
set_ps_display ( " " ) ;
SetProcessingMode ( InitProcessing ) ;
/*
* Set up signal handlers . We operate on databases much like a
* regular backend , so we use the same signal handling . See
@ -233,12 +259,9 @@ AutoVacMain(int argc, char *argv[])
pqsignal ( SIGUSR1 , CatchupInterruptHandler ) ;
/* We don't listen for async notifies */
pqsignal ( SIGUSR2 , SIG_IGN ) ;
pqsignal ( SIGFPE , FloatExceptionHandler ) ;
pqsignal ( SIGCHLD , SIG_DFL ) ;
/* Identify myself via ps */
init_ps_display ( " autovacuum process " , " " , " " ) ;
set_ps_display ( " " ) ;
/* Early initialization */
BaseInit ( ) ;
@ -302,6 +325,8 @@ AutoVacMain(int argc, char *argv[])
{
autovac_dbase * tmp = lfirst ( cell ) ;
bool this_whole_db ;
int32 freeze_age ,
vacuum_age ;
/*
* We look for the database that most urgently needs a database - wide
@ -309,9 +334,16 @@ AutoVacMain(int argc, char *argv[])
* transactions sooner than vacuum . c ' s vac_truncate_clog ( ) would
* decide to start giving warnings . If any such db is found , we
* ignore all other dbs .
*
* Unlike vacuum . c , we also look at vacuumxid . This is so that
* pg_clog can be kept trimmed to a reasonable size .
*/
tmp - > age = ( int32 ) ( nextXid - tmp - > frozenxid ) ;
this_whole_db = ( tmp - > age > ( int32 ) ( ( MaxTransactionId > > 3 ) * 3 - 100000 ) ) ;
freeze_age = ( int32 ) ( nextXid - tmp - > frozenxid ) ;
vacuum_age = ( int32 ) ( nextXid - tmp - > vacuumxid ) ;
tmp - > age = Max ( freeze_age , vacuum_age ) ;
this_whole_db = ( tmp - > age >
( int32 ) ( ( MaxTransactionId > > 3 ) * 3 - 100000 ) ) ;
if ( whole_db | | this_whole_db )
{
if ( ! this_whole_db )
@ -363,10 +395,21 @@ AutoVacMain(int argc, char *argv[])
set_ps_display ( db - > name ) ;
ereport ( LOG ,
( errmsg ( " autovacuum: processing database \" %s \" " , db - > name ) ) ) ;
/* Create the memory context where cross-transaction state is stored */
AutovacMemCxt = AllocSetContextCreate ( TopMemoryContext ,
" Autovacuum context " ,
ALLOCSET_DEFAULT_MINSIZE ,
ALLOCSET_DEFAULT_INITSIZE ,
ALLOCSET_DEFAULT_MAXSIZE ) ;
/*
* And do an appropriate amount of work on it
* And do an appropriate amount of work
*/
do_autovacuum ( whole_db , db - > entry ) ;
if ( whole_db )
process_whole_db ( ) ;
else
do_autovacuum ( db - > entry ) ;
}
/* One iteration done, go away */
@ -389,6 +432,7 @@ autovac_get_database_list(void)
Oid db_id ;
Oid db_tablespace ;
TransactionId db_frozenxid ;
TransactionId db_vacuumxid ;
filename = database_getflatfilename ( ) ;
db_file = AllocateFile ( filename , " r " ) ;
@ -398,7 +442,8 @@ autovac_get_database_list(void)
errmsg ( " could not open file \" %s \" : %m " , filename ) ) ) ;
while ( read_pg_database_line ( db_file , thisname , & db_id ,
& db_tablespace , & db_frozenxid ) )
& db_tablespace , & db_frozenxid ,
& db_vacuumxid ) )
{
autovac_dbase * db ;
@ -407,6 +452,7 @@ autovac_get_database_list(void)
db - > oid = db_id ;
db - > name = pstrdup ( thisname ) ;
db - > frozenxid = db_frozenxid ;
db - > vacuumxid = db_vacuumxid ;
/* these get set later: */
db - > entry = NULL ;
db - > age = 0 ;
@ -421,40 +467,79 @@ autovac_get_database_list(void)
}
/*
* Process a database .
*
* If whole_db is true , the database is processed as a whole , and the
* dbentry parameter is ignored . If it ' s false , dbentry must be a valid
* pointer to the database entry in the stats databases ' hash table , and
* it will be used to determine whether vacuum or analyze is needed on a
* per - table basis .
* Process a whole database . If it ' s a template database or is disallowing
* connection by means of datallowconn = false , then issue a VACUUM FREEZE .
* Else use a plain VACUUM .
*/
static void
process_whole_db ( void )
{
Relation dbRel ;
ScanKeyData entry [ 1 ] ;
SysScanDesc scan ;
HeapTuple tup ;
Form_pg_database dbForm ;
bool freeze ;
/* Start a transaction so our commands have one to play into. */
StartTransactionCommand ( ) ;
dbRel = heap_open ( DatabaseRelationId , AccessShareLock ) ;
/* Must use a table scan, since there's no syscache for pg_database */
ScanKeyInit ( & entry [ 0 ] ,
ObjectIdAttributeNumber ,
BTEqualStrategyNumber , F_OIDEQ ,
ObjectIdGetDatum ( MyDatabaseId ) ) ;
scan = systable_beginscan ( dbRel , DatabaseOidIndexId , true ,
SnapshotNow , 1 , entry ) ;
tup = systable_getnext ( scan ) ;
if ( ! HeapTupleIsValid ( tup ) )
elog ( ERROR , " could not find tuple for database %u " , MyDatabaseId ) ;
dbForm = ( Form_pg_database ) GETSTRUCT ( tup ) ;
if ( ! dbForm - > datallowconn | | dbForm - > datistemplate )
freeze = true ;
else
freeze = false ;
systable_endscan ( scan ) ;
heap_close ( dbRel , AccessShareLock ) ;
elog ( DEBUG2 , " autovacuum: VACUUM%s whole database " ,
( freeze ) ? " FREEZE " : " " ) ;
autovacuum_do_vac_analyze ( NIL , true , false , freeze ) ;
/* Finally close out the last transaction. */
CommitTransactionCommand ( ) ;
}
/*
* Process a database table - by - table
*
* Note that test_rel_for_autovac generates two separate lists , one for
* vacuum and other for analyze . This is to facilitate processing all
* analyzes first , and then all vacuums .
* dbentry must be a valid pointer to the database entry in the stats
* databases ' hash table , and it will be used to determine whether vacuum or
* analyze is needed on a per - table basi s .
*
* Note that CHECK_FOR_INTERRUPTS is supposed to be used in certain spots in
* order not to ignore shutdown commands for too long .
*/
static void
do_autovacuum ( bool whole_db , PgStat_StatDBEntry * dbentry )
do_autovacuum ( PgStat_StatDBEntry * dbentry )
{
Relation classRel ,
avRel ;
HeapTuple tuple ;
HeapScanDesc relScan ;
List * vacuum_tables = NIL ,
* analyze_tables = NIL ;
MemoryContext AutovacMemCxt ;
Assert ( whole_db | | PointerIsValid ( dbentry ) ) ;
/* Memory context where cross-transaction state is stored */
AutovacMemCxt = AllocSetContextCreate ( TopMemoryContext ,
" Autovacuum context " ,
ALLOCSET_DEFAULT_MINSIZE ,
ALLOCSET_DEFAULT_INITSIZE ,
ALLOCSET_DEFAULT_MAXSIZE ) ;
List * vacuum_tables = NIL ;
ListCell * cell ;
PgStat_StatDBEntry * shared ;
/* Start a transaction so our commands have one to play into. */
StartTransactionCommand ( ) ;
@ -467,93 +552,87 @@ do_autovacuum(bool whole_db, PgStat_StatDBEntry *dbentry)
*/
MemoryContextSwitchTo ( AutovacMemCxt ) ;
if ( whole_db )
{
elog ( DEBUG2 , " autovacuum: VACUUM ANALYZE whole database " ) ;
autovacuum_do_vac_analyze ( NIL , true ) ;
}
else
{
/* the hash entry where pgstat stores shared relations */
PgStat_StatDBEntry * shared = pgstat_fetch_stat_dbentry ( InvalidOid ) ;
classRel = heap_open ( RelationRelationId , AccessShareLock ) ;
avRel = heap_open ( AutovacuumRelationId , AccessShareLock ) ;
relScan = heap_beginscan ( classRel , SnapshotNow , 0 , NULL ) ;
/* The database hash where pgstat keeps shared relations */
shared = pgstat_fetch_stat_dbentry ( InvalidOid ) ;
/* Scan pg_class looking for tables to vacuum */
while ( ( tuple = heap_getnext ( relScan , ForwardScanDirection ) ) ! = NULL )
{
Form_pg_class classForm = ( Form_pg_class ) GETSTRUCT ( tuple ) ;
Form_pg_autovacuum avForm = NULL ;
PgStat_StatTabEntry * tabentry ;
SysScanDesc avScan ;
HeapTuple avTup ;
ScanKeyData entry [ 1 ] ;
Oid relid ;
/* Skip non-table entries. */
/* XXX possibly allow RELKIND_TOASTVALUE entries here too? */
if ( classForm - > relkind ! = RELKIND_RELATION )
continue ;
classRel = heap_open ( RelationRelationId , AccessShareLock ) ;
avRel = heap_open ( AutovacuumRelationId , AccessShareLock ) ;
/*
* Skip temp tables ( i . e . those in temp namespaces ) . We cannot
* safely process other backends ' temp tables .
*/
if ( isTempNamespace ( classForm - > relnamespace ) )
continue ;
relScan = heap_beginscan ( classRel , SnapshotNow , 0 , NULL ) ;
relid = HeapTupleGetOid ( tuple ) ;
/* Scan pg_class looking for tables to vacuum */
while ( ( tuple = heap_getnext ( relScan , ForwardScanDirection ) ) ! = NULL )
{
Form_pg_class classForm = ( Form_pg_class ) GETSTRUCT ( tuple ) ;
Form_pg_autovacuum avForm = NULL ;
PgStat_StatTabEntry * tabentry ;
SysScanDesc avScan ;
HeapTuple avTup ;
ScanKeyData entry [ 1 ] ;
Oid relid ;
/* Skip non-table entries. */
/* XXX possibly allow RELKIND_TOASTVALUE entries here too? */
if ( classForm - > relkind ! = RELKIND_RELATION )
continue ;
/* See if we have a pg_autovacuum entry for this relation. */
ScanKeyInit ( & entry [ 0 ] ,
Anum_pg_autovacuum_vacrelid ,
BTEqualStrategyNumber , F_OIDEQ ,
ObjectIdGetDatum ( relid ) ) ;
/*
* Skip temp tables ( i . e . those in temp namespaces ) . We cannot
* safely process other backends ' temp tables .
*/
if ( isTempNamespace ( classForm - > relnamespace ) )
continue ;
avScan = systable_beginscan ( avRel , AutovacuumRelidIndexId , true ,
SnapshotNow , 1 , entry ) ;
relid = HeapTupleGetOid ( tuple ) ;
avTup = systable_getnext ( avScan ) ;
/* See if we have a pg_autovacuum entry for this relation. */
ScanKeyInit ( & entry [ 0 ] ,
Anum_pg_autovacuum_vacrelid ,
BTEqualStrategyNumber , F_OIDEQ ,
ObjectIdGetDatum ( relid ) ) ;
if ( HeapTupleIsValid ( avTup ) )
avForm = ( Form_pg_autovacuum ) GETSTRUCT ( avTup ) ;
avScan = systable_beginscan ( avRel , AutovacuumRelidIndexId , true ,
SnapshotNow , 1 , entry ) ;
if ( classForm - > relisshared & & PointerIsValid ( shared ) )
tabentry = hash_search ( shared - > tables , & relid ,
HASH_FIND , NULL ) ;
else
tabentry = hash_search ( dbentry - > tables , & relid ,
HASH_FIND , NULL ) ;
avTup = systable_getnext ( avScan ) ;
test_rel_for_autovac ( relid , tabentry , classForm , avForm ,
& vacuum_tables , & analyze_tables ) ;
if ( HeapTupleIsValid ( avTup ) )
avForm = ( Form_pg_autovacuum ) GETSTRUCT ( avTup ) ;
systable_endscan ( avScan ) ;
}
if ( classForm - > relisshared & & PointerIsValid ( shared ) )
tabentry = hash_search ( shared - > tables , & relid ,
HASH_FIND , NULL ) ;
else
tabentry = hash_search ( dbentry - > tables , & relid ,
HASH_FIND , NULL ) ;
heap_endscan ( relScan ) ;
heap_close ( avRel , AccessShareLock ) ;
heap_close ( classRel , AccessShareLock ) ;
test_rel_for_autovac ( relid , tabentry , classForm , avForm ,
& vacuum_tables ) ;
CHECK_FOR_INTERRUPTS ( ) ;
systable_endscan ( avScan ) ;
}
/*
* Perform operations on collected tables .
*/
heap_endscan ( relScan ) ;
heap_close ( avRel , AccessShareLock ) ;
heap_close ( classRel , AccessShareLock ) ;
if ( analyze_tables )
autovacuum_do_vac_analyze ( analyze_tables , false ) ;
/*
* Perform operations on collected tables .
*/
foreach ( cell , vacuum_tables )
{
autovac_table * tab = lfirst ( cell ) ;
CHECK_FOR_INTERRUPTS ( ) ;
/* get back to proper context */
MemoryContextSwitchTo ( AutovacMemCxt ) ;
/* Set the vacuum cost parameters for this table */
VacuumCostDelay = tab - > vacuum_cost_delay ;
VacuumCostLimit = tab - > vacuum_cost_limit ;
if ( vacuum_tables )
autovacuum_do_vac_analyze ( vacuum_tables , true ) ;
autovacuum_do_vac_analyze ( list_make1_oid ( tab - > relid ) ,
tab - > dovacuum ,
tab - > doanalyze ,
false ) ;
}
/* Finally close out the last transaction. */
@ -564,7 +643,7 @@ do_autovacuum(bool whole_db, PgStat_StatDBEntry *dbentry)
* test_rel_for_autovac
*
* Check whether a table needs to be vacuumed or analyzed . Add it to the
* respective list if so .
* output list if so .
*
* A table needs to be vacuumed if the number of dead tuples exceeds a
* threshold . This threshold is calculated as
@ -591,7 +670,7 @@ static void
test_rel_for_autovac ( Oid relid , PgStat_StatTabEntry * tabentry ,
Form_pg_class classForm ,
Form_pg_autovacuum avForm ,
List * * vacuum_tables , List * * analyze_tables )
List * * vacuum_tables )
{
Relation rel ;
float4 reltuples ; /* pg_class.reltuples */
@ -606,6 +685,11 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
/* number of vacuum (resp. analyze) tuples at this time */
float4 vactuples ,
anltuples ;
/* cost-based vacuum delay parameters */
int vac_cost_limit ;
int vac_cost_delay ;
bool dovacuum ;
bool doanalyze ;
/* User disabled it in pg_autovacuum? */
if ( avForm & & ! avForm - > enabled )
@ -636,15 +720,25 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
*/
if ( avForm ! = NULL )
{
vac_scale_factor = ( avForm - > vac_scale_factor < 0 ) ?
autovacuum_vac_scale : avForm - > vac_scale_factor ;
vac_base_thresh = ( avForm - > vac_base_thresh < 0 ) ?
autovacuum_vac_thresh : avForm - > vac_base_thresh ;
anl_scale_factor = ( avForm - > anl_scale_factor < 0 ) ?
autovacuum_anl_scale : avForm - > anl_scale_factor ;
anl_base_thresh = ( avForm - > anl_base_thresh < 0 ) ?
autovacuum_anl_thresh : avForm - > anl_base_thresh ;
vac_scale_factor = ( avForm - > vac_scale_factor > = 0 ) ?
avForm - > vac_scale_factor : autovacuum_vac_scale ;
vac_base_thresh = ( avForm - > vac_base_thresh > = 0 ) ?
avForm - > vac_base_thresh : autovacuum_vac_thresh ;
anl_scale_factor = ( avForm - > anl_scale_factor > = 0 ) ?
avForm - > anl_scale_factor : autovacuum_anl_scale ;
anl_base_thresh = ( avForm - > anl_base_thresh > = 0 ) ?
avForm - > anl_base_thresh : autovacuum_anl_thresh ;
vac_cost_limit = ( avForm - > vac_cost_limit > = 0 ) ?
avForm - > vac_cost_limit :
( ( autovacuum_vac_cost_limit > = 0 ) ?
autovacuum_vac_cost_limit : VacuumCostLimit ) ;
vac_cost_delay = ( avForm - > vac_cost_delay > = 0 ) ?
avForm - > vac_cost_delay :
( ( autovacuum_vac_cost_delay > = 0 ) ?
autovacuum_vac_cost_delay : VacuumCostDelay ) ;
}
else
{
@ -653,6 +747,12 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
anl_scale_factor = autovacuum_anl_scale ;
anl_base_thresh = autovacuum_anl_thresh ;
vac_cost_limit = ( autovacuum_vac_cost_limit > = 0 ) ?
autovacuum_vac_cost_limit : VacuumCostLimit ;
vac_cost_delay = ( autovacuum_vac_cost_delay > = 0 ) ?
autovacuum_vac_cost_delay : VacuumCostDelay ;
}
vacthresh = ( float4 ) vac_base_thresh + vac_scale_factor * reltuples ;
@ -668,22 +768,33 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
RelationGetRelationName ( rel ) ,
vactuples , vacthresh , anltuples , anlthresh ) ;
Assert ( CurrentMemoryContext = = AutovacMemCxt ) ;
/* Determine if this table needs vacuum or analyze. */
if ( vactuples > vacthresh )
dovacuum = ( vactuples > vacthresh ) ;
doanalyze = ( anltuples > anlthresh ) ;
/* ANALYZE refuses to work with pg_statistics */
if ( relid = = StatisticRelationId )
doanalyze = false ;
if ( dovacuum | | doanalyze )
{
elog ( DEBUG2 , " will VACUUM ANALYZE %s " ,
autovac_table * tab ;
elog ( DEBUG2 , " will%s%s %s " ,
( dovacuum ? " VACUUM " : " " ) ,
( doanalyze ? " ANALYZE " : " " ) ,
RelationGetRelationName ( rel ) ) ;
* vacuum_tables = lappend_oid ( * vacuum_tables , relid ) ;
}
else if ( anltuples > anlthresh )
{
/* ANALYZE refuses to work with pg_statistics */
if ( relid ! = StatisticRelationId )
{
elog ( DEBUG2 , " will ANALYZE %s " ,
RelationGetRelationName ( rel ) ) ;
* analyze_tables = lappend_oid ( * analyze_tables , relid ) ;
}
tab = ( autovac_table * ) palloc ( sizeof ( autovac_table ) ) ;
tab - > relid = relid ;
tab - > dovacuum = dovacuum ;
tab - > doanalyze = doanalyze ;
tab - > vacuum_cost_limit = vac_cost_limit ;
tab - > vacuum_cost_delay = vac_cost_delay ;
* vacuum_tables = lappend ( * vacuum_tables , tab ) ;
}
RelationClose ( rel ) ;
@ -691,14 +802,22 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry,
/*
* autovacuum_do_vac_analyze
* Vacuum or analyze a list of tables ; or all tables if relids = NIL
*
* We must be in AutovacMemCxt when this routine is called .
* Vacuum and / or analyze a list of tables ; or all tables if relids = NIL
*/
static void
autovacuum_do_vac_analyze ( List * relids , bool dovacuum )
autovacuum_do_vac_analyze ( List * relids , bool dovacuum , bool doanalyze ,
bool freeze )
{
VacuumStmt * vacstmt = makeNode ( VacuumStmt ) ;
VacuumStmt * vacstmt ;
MemoryContext old_cxt ;
/*
* The node must survive transaction boundaries , so make sure we create it
* in a long - lived context
*/
old_cxt = MemoryContextSwitchTo ( AutovacMemCxt ) ;
vacstmt = makeNode ( VacuumStmt ) ;
/*
* Point QueryContext to the autovac memory context to fake out the
@ -710,13 +829,16 @@ autovacuum_do_vac_analyze(List *relids, bool dovacuum)
/* Set up command parameters */
vacstmt - > vacuum = dovacuum ;
vacstmt - > full = false ;
vacstmt - > analyze = tru e;
vacstmt - > freeze = fals e;
vacstmt - > analyze = doanalyz e;
vacstmt - > freeze = freez e;
vacstmt - > verbose = false ;
vacstmt - > relation = NULL ; /* all tables, or not used if relids != NIL */
vacstmt - > va_cols = NIL ;
vacuum ( vacstmt , relids ) ;
pfree ( vacstmt ) ;
MemoryContextSwitchTo ( old_cxt ) ;
}
/*