@ -48,6 +48,7 @@
# include "commands/vacuum.h"
# include "commands/vacuum.h"
# include "miscadmin.h"
# include "miscadmin.h"
# include "pgstat.h"
# include "pgstat.h"
# include "portability/instr_time.h"
# include "postmaster/autovacuum.h"
# include "postmaster/autovacuum.h"
# include "storage/bufmgr.h"
# include "storage/bufmgr.h"
# include "storage/freespace.h"
# include "storage/freespace.h"
@ -69,6 +70,17 @@
# define REL_TRUNCATE_MINIMUM 1000
# define REL_TRUNCATE_MINIMUM 1000
# define REL_TRUNCATE_FRACTION 16
# define REL_TRUNCATE_FRACTION 16
/*
* Timing parameters for truncate locking heuristics .
*
* These were not exposed as user tunable GUC values because it didn ' t seem
* that the potential for improvement was great enough to merit the cost of
* supporting them .
*/
# define AUTOVACUUM_TRUNCATE_LOCK_CHECK_INTERVAL 20 /* ms */
# define AUTOVACUUM_TRUNCATE_LOCK_WAIT_INTERVAL 50 /* ms */
# define AUTOVACUUM_TRUNCATE_LOCK_TIMEOUT 5000 /* ms */
/*
/*
* Guesstimation of number of dead tuples per page . This is used to
* Guesstimation of number of dead tuples per page . This is used to
* provide an upper limit to memory allocated when vacuuming small
* provide an upper limit to memory allocated when vacuuming small
@ -103,6 +115,7 @@ typedef struct LVRelStats
ItemPointer dead_tuples ; /* array of ItemPointerData */
ItemPointer dead_tuples ; /* array of ItemPointerData */
int num_index_scans ;
int num_index_scans ;
TransactionId latestRemovedXid ;
TransactionId latestRemovedXid ;
bool lock_waiter_detected ;
} LVRelStats ;
} LVRelStats ;
@ -193,6 +206,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
vacrelstats - > old_rel_pages = onerel - > rd_rel - > relpages ;
vacrelstats - > old_rel_pages = onerel - > rd_rel - > relpages ;
vacrelstats - > old_rel_tuples = onerel - > rd_rel - > reltuples ;
vacrelstats - > old_rel_tuples = onerel - > rd_rel - > reltuples ;
vacrelstats - > num_index_scans = 0 ;
vacrelstats - > num_index_scans = 0 ;
vacrelstats - > pages_removed = 0 ;
vacrelstats - > lock_waiter_detected = false ;
/* Open all indexes of the relation */
/* Open all indexes of the relation */
vac_open_indexes ( onerel , RowExclusiveLock , & nindexes , & Irel ) ;
vac_open_indexes ( onerel , RowExclusiveLock , & nindexes , & Irel ) ;
@ -259,10 +274,17 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
vacrelstats - > hasindex ,
vacrelstats - > hasindex ,
new_frozen_xid ) ;
new_frozen_xid ) ;
/* report results to the stats collector, too */
/*
pgstat_report_vacuum ( RelationGetRelid ( onerel ) ,
* Report results to the stats collector , too . An early terminated
onerel - > rd_rel - > relisshared ,
* lazy_truncate_heap attempt suppresses the message and also cancels the
new_rel_tuples ) ;
* execution of ANALYZE , if that was ordered .
*/
if ( ! vacrelstats - > lock_waiter_detected )
pgstat_report_vacuum ( RelationGetRelid ( onerel ) ,
onerel - > rd_rel - > relisshared ,
new_rel_tuples ) ;
else
vacstmt - > options & = ~ VACOPT_ANALYZE ;
/* and log the action if appropriate */
/* and log the action if appropriate */
if ( IsAutoVacuumWorkerProcess ( ) & & Log_autovacuum_min_duration > = 0 )
if ( IsAutoVacuumWorkerProcess ( ) & & Log_autovacuum_min_duration > = 0 )
@ -1257,80 +1279,124 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats)
BlockNumber old_rel_pages = vacrelstats - > rel_pages ;
BlockNumber old_rel_pages = vacrelstats - > rel_pages ;
BlockNumber new_rel_pages ;
BlockNumber new_rel_pages ;
PGRUsage ru0 ;
PGRUsage ru0 ;
int lock_retry ;
pg_rusage_init ( & ru0 ) ;
pg_rusage_init ( & ru0 ) ;
/*
/*
* We need full exclusive lock on the relation in order to do truncation .
* Loop until no more truncating can be done .
* If we can ' t get it , give up rather than waiting - - - we don ' t want to
* block other backends , and we don ' t want to deadlock ( which is quite
* possible considering we already hold a lower - grade lock ) .
*/
if ( ! ConditionalLockRelation ( onerel , AccessExclusiveLock ) )
return ;
/*
* Now that we have exclusive lock , look to see if the rel has grown
* whilst we were vacuuming with non - exclusive lock . If so , give up ; the
* newly added pages presumably contain non - deletable tuples .
*/
*/
new_rel_pages = RelationGetNumberOfBlocks ( onerel ) ;
do
if ( new_rel_pages ! = old_rel_pages )
{
{
/*
/*
* Note : we intentionally don ' t update vacrelstats - > rel_pages with the
* We need full exclusive lock on the relation in order to do
* new rel size here . If we did , it would amount to assuming that th e
* truncation . If we can ' t get it , give up rather than waiting - - - we
* new pages are empty , which is unlikely . Leaving the numbers alone
* don ' t want to block other backends , and we don ' t want to deadlock
* amounts to assuming that the new pages have the same tuple density
* ( which is quite possible considering we already hold a lower - grade
* as existing ones , which is less unlikely .
* lock ) .
*/
*/
UnlockRelation ( onerel , AccessExclusiveLock ) ;
vacrelstats - > lock_waiter_detected = false ;
return ;
lock_retry = 0 ;
}
while ( true )
{
if ( ConditionalLockRelation ( onerel , AccessExclusiveLock ) )
break ;
/*
/*
* Scan backwards from the end to verify that the end pages actually
* Check for interrupts while trying to ( re - ) acquire the exclusive
* contain no tuples . This is * necessary * , not optional , because other
* lock .
* backends could have added tuples to these pages whilst we were
*/
* vacuuming .
CHECK_FOR_INTERRUPTS ( ) ;
*/
new_rel_pages = count_nondeletable_pages ( onerel , vacrelstats ) ;
if ( new_rel_pages > = old_rel_pages )
if ( + + lock_retry > ( AUTOVACUUM_TRUNCATE_LOCK_TIMEOUT /
{
AUTOVACUUM_TRUNCATE_LOCK_WAIT_INTERVAL ) )
/* can't do anything after all */
{
UnlockRelation ( onerel , AccessExclusiveLock ) ;
/*
return ;
* We failed to establish the lock in the specified number of
}
* retries . This means we give up truncating . Suppress the
* ANALYZE step . Doing an ANALYZE at this point will reset the
* dead_tuple_count in the stats collector , so we will not get
* called by the autovacuum launcher again to do the truncate .
*/
vacrelstats - > lock_waiter_detected = true ;
ereport ( LOG ,
( errmsg ( " automatic vacuum of table \" %s.%s.%s \" : "
" cannot (re)acquire exclusive "
" lock for truncate scan " ,
get_database_name ( MyDatabaseId ) ,
get_namespace_name ( RelationGetNamespace ( onerel ) ) ,
RelationGetRelationName ( onerel ) ) ) ) ;
return ;
}
/*
pg_usleep ( AUTOVACUUM_TRUNCATE_LOCK_WAIT_INTERVAL ) ;
* Okay to truncate .
}
*/
RelationTruncate ( onerel , new_rel_pages ) ;
/*
/*
* We can release the exclusive lock as soon as we have truncated . Other
* Now that we have exclusive lock , look to see if the rel has grown
* backends can ' t safely access the relation until they have processed the
* whilst we were vacuuming with non - exclusive lock . If so , give up ;
* smgr invalidation that smgrtruncate sent out . . . but that should happen
* the newly added pages presumably contain non - deletable tuples .
* as part of standard invalidation processing once they acquire lock on
*/
* the relation .
new_rel_pages = RelationGetNumberOfBlocks ( onerel ) ;
*/
if ( new_rel_pages ! = old_rel_pages )
UnlockRelation ( onerel , AccessExclusiveLock ) ;
{
/*
* Note : we intentionally don ' t update vacrelstats - > rel_pages with
* the new rel size here . If we did , it would amount to assuming
* that the new pages are empty , which is unlikely . Leaving the
* numbers alone amounts to assuming that the new pages have the
* same tuple density as existing ones , which is less unlikely .
*/
UnlockRelation ( onerel , AccessExclusiveLock ) ;
return ;
}
/*
/*
* Update statistics . Here , it * is * correct to adjust rel_pages without
* Scan backwards from the end to verify that the end pages actually
* also touching reltuples , since the tuple count wasn ' t changed by the
* contain no tuples . This is * necessary * , not optional , becaus e
* truncation .
* other backends could have added tuples to these pages whilst we
*/
* were vacuuming .
vacrelstats - > rel_pages = new_rel_pages ;
*/
vacrelstats - > pages_removed = old_rel_pages - new_rel_pages ;
new_rel_pages = count_nondeletable_pages ( onerel , vacrelstats ) ;
ereport ( elevel ,
if ( new_rel_pages > = old_rel_pages )
( errmsg ( " \" %s \" : truncated %u to %u pages " ,
{
RelationGetRelationName ( onerel ) ,
/* can't do anything after all */
old_rel_pages , new_rel_pages ) ,
UnlockRelation ( onerel , AccessExclusiveLock ) ;
errdetail ( " %s. " ,
return ;
pg_rusage_show ( & ru0 ) ) ) ) ;
}
/*
* Okay to truncate .
*/
RelationTruncate ( onerel , new_rel_pages ) ;
/*
* We can release the exclusive lock as soon as we have truncated .
* Other backends can ' t safely access the relation until they have
* processed the smgr invalidation that smgrtruncate sent out . . . but
* that should happen as part of standard invalidation processing once
* they acquire lock on the relation .
*/
UnlockRelation ( onerel , AccessExclusiveLock ) ;
/*
* Update statistics . Here , it * is * correct to adjust rel_pages
* without also touching reltuples , since the tuple count wasn ' t
* changed by the truncation .
*/
vacrelstats - > pages_removed + = old_rel_pages - new_rel_pages ;
vacrelstats - > rel_pages = new_rel_pages ;
ereport ( elevel ,
( errmsg ( " \" %s \" : truncated %u to %u pages " ,
RelationGetRelationName ( onerel ) ,
old_rel_pages , new_rel_pages ) ,
errdetail ( " %s. " ,
pg_rusage_show ( & ru0 ) ) ) ) ;
old_rel_pages = new_rel_pages ;
} while ( new_rel_pages > vacrelstats - > nonempty_pages & &
vacrelstats - > lock_waiter_detected ) ;
}
}
/*
/*
@ -1342,6 +1408,12 @@ static BlockNumber
count_nondeletable_pages ( Relation onerel , LVRelStats * vacrelstats )
count_nondeletable_pages ( Relation onerel , LVRelStats * vacrelstats )
{
{
BlockNumber blkno ;
BlockNumber blkno ;
instr_time starttime ;
instr_time currenttime ;
instr_time elapsed ;
/* Initialize the starttime if we check for conflicting lock requests */
INSTR_TIME_SET_CURRENT ( starttime ) ;
/* Strange coding of loop control is needed because blkno is unsigned */
/* Strange coding of loop control is needed because blkno is unsigned */
blkno = vacrelstats - > rel_pages ;
blkno = vacrelstats - > rel_pages ;
@ -1353,6 +1425,36 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
maxoff ;
maxoff ;
bool hastup ;
bool hastup ;
/*
* Check if another process requests a lock on our relation . We are
* holding an AccessExclusiveLock here , so they will be waiting . We
* only do this in autovacuum_truncate_lock_check millisecond
* intervals , and we only check if that interval has elapsed once
* every 32 blocks to keep the number of system calls and actual
* shared lock table lookups to a minimum .
*/
if ( ( blkno % 32 ) = = 0 )
{
INSTR_TIME_SET_CURRENT ( currenttime ) ;
elapsed = currenttime ;
INSTR_TIME_SUBTRACT ( elapsed , starttime ) ;
if ( ( INSTR_TIME_GET_MICROSEC ( elapsed ) / 1000 )
> = AUTOVACUUM_TRUNCATE_LOCK_CHECK_INTERVAL )
{
if ( LockHasWaitersRelation ( onerel , AccessExclusiveLock ) )
{
ereport ( elevel ,
( errmsg ( " \" %s \" : suspending truncate "
" due to conflicting lock request " ,
RelationGetRelationName ( onerel ) ) ) ) ;
vacrelstats - > lock_waiter_detected = true ;
return blkno ;
}
starttime = currenttime ;
}
}
/*
/*
* We don ' t insert a vacuum delay point here , because we have an
* We don ' t insert a vacuum delay point here , because we have an
* exclusive lock on the table which we want to hold for as short a
* exclusive lock on the table which we want to hold for as short a