@ -463,8 +463,15 @@ static void dead_items_add(LVRelState *vacrel, BlockNumber blkno, OffsetNumber *
int num_offsets ) ;
int num_offsets ) ;
static void dead_items_reset ( LVRelState * vacrel ) ;
static void dead_items_reset ( LVRelState * vacrel ) ;
static void dead_items_cleanup ( LVRelState * vacrel ) ;
static void dead_items_cleanup ( LVRelState * vacrel ) ;
# ifdef USE_ASSERT_CHECKING
static bool heap_page_is_all_visible ( LVRelState * vacrel , Buffer buf ,
static bool heap_page_is_all_visible ( LVRelState * vacrel , Buffer buf ,
TransactionId * visibility_cutoff_xid , bool * all_frozen ) ;
TransactionId * visibility_cutoff_xid , bool * all_frozen ) ;
# endif
static bool heap_page_would_be_all_visible ( LVRelState * vacrel , Buffer buf ,
OffsetNumber * deadoffsets ,
int ndeadoffsets ,
bool * all_frozen ,
TransactionId * visibility_cutoff_xid ) ;
static void update_relstats_all_indexes ( LVRelState * vacrel ) ;
static void update_relstats_all_indexes ( LVRelState * vacrel ) ;
static void vacuum_error_callback ( void * arg ) ;
static void vacuum_error_callback ( void * arg ) ;
static void update_vacuum_error_info ( LVRelState * vacrel ,
static void update_vacuum_error_info ( LVRelState * vacrel ,
@ -2848,8 +2855,10 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
OffsetNumber unused [ MaxHeapTuplesPerPage ] ;
OffsetNumber unused [ MaxHeapTuplesPerPage ] ;
int nunused = 0 ;
int nunused = 0 ;
TransactionId visibility_cutoff_xid ;
TransactionId visibility_cutoff_xid ;
TransactionId conflict_xid = InvalidTransactionId ;
bool all_frozen ;
bool all_frozen ;
LVSavedErrInfo saved_err_info ;
LVSavedErrInfo saved_err_info ;
uint8 vmflags = 0 ;
Assert ( vacrel - > do_index_vacuuming ) ;
Assert ( vacrel - > do_index_vacuuming ) ;
@ -2860,6 +2869,33 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
VACUUM_ERRCB_PHASE_VACUUM_HEAP , blkno ,
VACUUM_ERRCB_PHASE_VACUUM_HEAP , blkno ,
InvalidOffsetNumber ) ;
InvalidOffsetNumber ) ;
/*
* Before marking dead items unused , check whether the page will become
* all - visible once that change is applied . This lets us reap the tuples
* and mark the page all - visible within the same critical section ,
* enabling both changes to be emitted in a single WAL record . Since the
* visibility checks may perform I / O and allocate memory , they must be
* done outside the critical section .
*/
if ( heap_page_would_be_all_visible ( vacrel , buffer ,
deadoffsets , num_offsets ,
& all_frozen , & visibility_cutoff_xid ) )
{
vmflags | = VISIBILITYMAP_ALL_VISIBLE ;
if ( all_frozen )
{
vmflags | = VISIBILITYMAP_ALL_FROZEN ;
Assert ( ! TransactionIdIsValid ( visibility_cutoff_xid ) ) ;
}
/*
* Take the lock on the vmbuffer before entering a critical section .
* The heap page lock must also be held while updating the VM to
* ensure consistency .
*/
LockBuffer ( vmbuffer , BUFFER_LOCK_EXCLUSIVE ) ;
}
START_CRIT_SECTION ( ) ;
START_CRIT_SECTION ( ) ;
for ( int i = 0 ; i < num_offsets ; i + + )
for ( int i = 0 ; i < num_offsets ; i + + )
@ -2879,6 +2915,19 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
/* Attempt to truncate line pointer array now */
/* Attempt to truncate line pointer array now */
PageTruncateLinePointerArray ( page ) ;
PageTruncateLinePointerArray ( page ) ;
if ( ( vmflags & VISIBILITYMAP_VALID_BITS ) ! = 0 )
{
/*
* The page is guaranteed to have had dead line pointers , so we always
* set PD_ALL_VISIBLE .
*/
PageSetAllVisible ( page ) ;
visibilitymap_set_vmbits ( blkno ,
vmbuffer , vmflags ,
vacrel - > rel - > rd_locator ) ;
conflict_xid = visibility_cutoff_xid ;
}
/*
/*
* Mark buffer dirty before we write WAL .
* Mark buffer dirty before we write WAL .
*/
*/
@ -2888,7 +2937,9 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
if ( RelationNeedsWAL ( vacrel - > rel ) )
if ( RelationNeedsWAL ( vacrel - > rel ) )
{
{
log_heap_prune_and_freeze ( vacrel - > rel , buffer ,
log_heap_prune_and_freeze ( vacrel - > rel , buffer ,
InvalidTransactionId ,
vmflags ! = 0 ? vmbuffer : InvalidBuffer ,
vmflags ,
conflict_xid ,
false , /* no cleanup lock required */
false , /* no cleanup lock required */
PRUNE_VACUUM_CLEANUP ,
PRUNE_VACUUM_CLEANUP ,
NULL , 0 , /* frozen */
NULL , 0 , /* frozen */
@ -2897,39 +2948,12 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
unused , nunused ) ;
unused , nunused ) ;
}
}
/*
* End critical section , so we safely can do visibility tests ( which
* possibly need to perform IO and allocate memory ! ) . If we crash now the
* page ( including the corresponding vm bit ) might not be marked all
* visible , but that ' s fine . A later vacuum will fix that .
*/
END_CRIT_SECTION ( ) ;
END_CRIT_SECTION ( ) ;
/*
if ( ( vmflags & VISIBILITYMAP_ALL_VISIBLE ) ! = 0 )
* Now that we have removed the LP_DEAD items from the page , once again
* check if the page has become all - visible . The page is already marked
* dirty , exclusively locked , and , if needed , a full page image has been
* emitted .
*/
Assert ( ! PageIsAllVisible ( page ) ) ;
if ( heap_page_is_all_visible ( vacrel , buffer , & visibility_cutoff_xid ,
& all_frozen ) )
{
{
uint8 flags = VISIBILITYMAP_ALL_VISIBLE ;
if ( all_frozen )
{
Assert ( ! TransactionIdIsValid ( visibility_cutoff_xid ) ) ;
flags | = VISIBILITYMAP_ALL_FROZEN ;
}
PageSetAllVisible ( page ) ;
visibilitymap_set ( vacrel - > rel , blkno , buffer ,
InvalidXLogRecPtr ,
vmbuffer , visibility_cutoff_xid ,
flags ) ;
/* Count the newly set VM page for logging */
/* Count the newly set VM page for logging */
LockBuffer ( vmbuffer , BUFFER_LOCK_UNLOCK ) ;
vacrel - > vm_new_visible_pages + + ;
vacrel - > vm_new_visible_pages + + ;
if ( all_frozen )
if ( all_frozen )
vacrel - > vm_new_visible_frozen_pages + + ;
vacrel - > vm_new_visible_frozen_pages + + ;
@ -3597,31 +3621,80 @@ dead_items_cleanup(LVRelState *vacrel)
vacrel - > pvs = NULL ;
vacrel - > pvs = NULL ;
}
}
# ifdef USE_ASSERT_CHECKING
/*
/*
* Check if every tuple in the given page is visible to all current and future
* Wrapper for heap_page_would_be_all_visible ( ) which can be used for callers
* transactions . Also return the visibility_cutoff_xid which is the highest
* that expect no LP_DEAD on the page . Currently assert - only , but there is no
* xmin amongst the visible tuples . Set * all_frozen to true if every tuple
* reason not to use it outside of asserts .
* on this page is frozen .
*
* This is a stripped down version of lazy_scan_prune ( ) . If you change
* anything here , make sure that everything stays in sync . Note that an
* assertion calls us to verify that everybody still agrees . Be sure to avoid
* introducing new side - effects here .
*/
*/
static bool
static bool
heap_page_is_all_visible ( LVRelState * vacrel , Buffer buf ,
heap_page_is_all_visible ( LVRelState * vacrel , Buffer buf ,
TransactionId * visibility_cutoff_xid ,
TransactionId * visibility_cutoff_xid ,
bool * all_frozen )
bool * all_frozen )
{
{
return heap_page_would_be_all_visible ( vacrel , buf ,
NULL , 0 ,
all_frozen ,
visibility_cutoff_xid ) ;
}
# endif
/*
* Check whether the heap page in buf is all - visible except for the dead
* tuples referenced in the deadoffsets array .
*
* Vacuum uses this to check if a page would become all - visible after reaping
* known dead tuples . This function does not remove the dead items .
*
* This cannot be called in a critical section , as the visibility checks may
* perform IO and allocate memory .
*
* Returns true if the page is all - visible other than the provided
* deadoffsets and false otherwise .
*
* Output parameters :
*
* - * all_frozen : true if every tuple on the page is frozen
* - * visibility_cutoff_xid : newest xmin ; valid only if page is all - visible
*
* Callers looking to verify that the page is already all - visible can call
* heap_page_is_all_visible ( ) .
*
* This logic is closely related to heap_prune_record_unchanged_lp_normal ( ) .
* If you modify this function , ensure consistency with that code . An
* assertion cross - checks that both remain in agreement . Do not introduce new
* side - effects .
*/
static bool
heap_page_would_be_all_visible ( LVRelState * vacrel , Buffer buf ,
OffsetNumber * deadoffsets ,
int ndeadoffsets ,
bool * all_frozen ,
TransactionId * visibility_cutoff_xid )
{
Page page = BufferGetPage ( buf ) ;
Page page = BufferGetPage ( buf ) ;
BlockNumber blockno = BufferGetBlockNumber ( buf ) ;
BlockNumber blockno = BufferGetBlockNumber ( buf ) ;
OffsetNumber offnum ,
OffsetNumber offnum ,
maxoff ;
maxoff ;
bool all_visible = true ;
bool all_visible = true ;
int matched_dead_count = 0 ;
* visibility_cutoff_xid = InvalidTransactionId ;
* visibility_cutoff_xid = InvalidTransactionId ;
* all_frozen = true ;
* all_frozen = true ;
Assert ( ndeadoffsets = = 0 | | deadoffsets ) ;
# ifdef USE_ASSERT_CHECKING
/* Confirm input deadoffsets[] is strictly sorted */
if ( ndeadoffsets > 1 )
{
for ( int i = 1 ; i < ndeadoffsets ; i + + )
Assert ( deadoffsets [ i - 1 ] < deadoffsets [ i ] ) ;
}
# endif
maxoff = PageGetMaxOffsetNumber ( page ) ;
maxoff = PageGetMaxOffsetNumber ( page ) ;
for ( offnum = FirstOffsetNumber ;
for ( offnum = FirstOffsetNumber ;
offnum < = maxoff & & all_visible ;
offnum < = maxoff & & all_visible ;
@ -3649,9 +3722,15 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
*/
*/
if ( ItemIdIsDead ( itemid ) )
if ( ItemIdIsDead ( itemid ) )
{
{
all_visible = false ;
if ( ! deadoffsets | |
* all_frozen = false ;
matched_dead_count > = ndeadoffsets | |
break ;
deadoffsets [ matched_dead_count ] ! = offnum )
{
* all_frozen = all_visible = false ;
break ;
}
matched_dead_count + + ;
continue ;
}
}
Assert ( ItemIdIsNormal ( itemid ) ) ;
Assert ( ItemIdIsNormal ( itemid ) ) ;
@ -3660,6 +3739,9 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
tuple . t_len = ItemIdGetLength ( itemid ) ;
tuple . t_len = ItemIdGetLength ( itemid ) ;
tuple . t_tableOid = RelationGetRelid ( vacrel - > rel ) ;
tuple . t_tableOid = RelationGetRelid ( vacrel - > rel ) ;
/* Visibility checks may do IO or allocate memory */
Assert ( CritSectionCount = = 0 ) ;
switch ( HeapTupleSatisfiesVacuum ( & tuple , vacrel - > cutoffs . OldestXmin ,
switch ( HeapTupleSatisfiesVacuum ( & tuple , vacrel - > cutoffs . OldestXmin ,
buf ) )
buf ) )
{
{