@@ -250,8 +250,17 @@ PageAddItemExtended(Page page,
250
250
/* if no free slot, we'll put it at limit (1st open slot) */
251
251
if (PageHasFreeLinePointers (phdr ))
252
252
{
253
- /* Look for "recyclable" (unused) ItemId */
254
- for (offsetNumber = 1 ; offsetNumber < limit ; offsetNumber ++ )
253
+ /*
254
+ * Scan line pointer array to locate a "recyclable" (unused)
255
+ * ItemId.
256
+ *
257
+ * Always use earlier items first. PageTruncateLinePointerArray
258
+ * can only truncate unused items when they appear as a contiguous
259
+ * group at the end of the line pointer array.
260
+ */
261
+ for (offsetNumber = FirstOffsetNumber ;
262
+ offsetNumber < limit ; /* limit is maxoff+1 */
263
+ offsetNumber ++ )
255
264
{
256
265
itemId = PageGetItemId (phdr , offsetNumber );
257
266
@@ -675,11 +684,23 @@ compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorte
675
684
/*
676
685
* PageRepairFragmentation
677
686
*
678
- * Frees fragmented space on a page.
679
- * It doesn't remove unused line pointers! Please don't change this.
687
+ * Frees fragmented space on a heap page following pruning.
680
688
*
681
689
* This routine is usable for heap pages only, but see PageIndexMultiDelete.
682
690
*
691
+ * Never removes unused line pointers. PageTruncateLinePointerArray can
692
+ * safely remove some unused line pointers. It ought to be safe for this
693
+ * routine to free unused line pointers in roughly the same way, but it's not
694
+ * clear that that would be beneficial.
695
+ *
696
+ * PageTruncateLinePointerArray is only called during VACUUM's second pass
697
+ * over the heap. Any unused line pointers that it sees are likely to have
698
+ * been set to LP_UNUSED (from LP_DEAD) immediately before the time it is
699
+ * called. On the other hand, many tables have the vast majority of all
700
+ * required pruning performed opportunistically (not during VACUUM). And so
701
+ * there is, in general, a good chance that even large groups of unused line
702
+ * pointers that we see here will be recycled quickly.
703
+ *
683
704
* Caller had better have a super-exclusive lock on page's buffer. As a side
684
705
* effect the page's PD_HAS_FREE_LINES hint bit will be set or unset as
685
706
* needed.
@@ -784,6 +805,89 @@ PageRepairFragmentation(Page page)
784
805
PageClearHasFreeLinePointers (page );
785
806
}
786
807
808
+ /*
809
+ * PageTruncateLinePointerArray
810
+ *
811
+ * Removes unused line pointers at the end of the line pointer array.
812
+ *
813
+ * This routine is usable for heap pages only. It is called by VACUUM during
814
+ * its second pass over the heap. We expect at least one LP_UNUSED line
815
+ * pointer on the page (if VACUUM didn't have an LP_DEAD item on the page that
816
+ * it just set to LP_UNUSED then it should not call here).
817
+ *
818
+ * We avoid truncating the line pointer array to 0 items, if necessary by
819
+ * leaving behind a single remaining LP_UNUSED item. This is a little
820
+ * arbitrary, but it seems like a good idea to avoid leaving a PageIsEmpty()
821
+ * page behind.
822
+ *
823
+ * Caller can have either an exclusive lock or a super-exclusive lock on
824
+ * page's buffer. The page's PD_HAS_FREE_LINES hint bit will be set or unset
825
+ * based on whether or not we leave behind any remaining LP_UNUSED items.
826
+ */
827
+ void
828
+ PageTruncateLinePointerArray (Page page )
829
+ {
830
+ PageHeader phdr = (PageHeader ) page ;
831
+ bool countdone = false,
832
+ sethint = false;
833
+ int nunusedend = 0 ;
834
+
835
+ /* Scan line pointer array back-to-front */
836
+ for (int i = PageGetMaxOffsetNumber (page ); i >= FirstOffsetNumber ; i -- )
837
+ {
838
+ ItemId lp = PageGetItemId (page , i );
839
+
840
+ if (!countdone && i > FirstOffsetNumber )
841
+ {
842
+ /*
843
+ * Still determining which line pointers from the end of the array
844
+ * will be truncated away. Either count another line pointer as
845
+ * safe to truncate, or notice that it's not safe to truncate
846
+ * additional line pointers (stop counting line pointers).
847
+ */
848
+ if (!ItemIdIsUsed (lp ))
849
+ nunusedend ++ ;
850
+ else
851
+ countdone = true;
852
+ }
853
+ else
854
+ {
855
+ /*
856
+ * Once we've stopped counting we still need to figure out if
857
+ * there are any remaining LP_UNUSED line pointers somewhere more
858
+ * towards the front of the array.
859
+ */
860
+ if (!ItemIdIsUsed (lp ))
861
+ {
862
+ /*
863
+ * This is an unused line pointer that we won't be truncating
864
+ * away -- so there is at least one. Set hint on page.
865
+ */
866
+ sethint = true;
867
+ break ;
868
+ }
869
+ }
870
+ }
871
+
872
+ if (nunusedend > 0 )
873
+ {
874
+ phdr -> pd_lower -= sizeof (ItemIdData ) * nunusedend ;
875
+
876
+ #ifdef CLOBBER_FREED_MEMORY
877
+ memset ((char * ) page + phdr -> pd_lower , 0x7F ,
878
+ sizeof (ItemIdData ) * nunusedend );
879
+ #endif
880
+ }
881
+ else
882
+ Assert (sethint );
883
+
884
+ /* Set hint bit for PageAddItemExtended */
885
+ if (sethint )
886
+ PageSetHasFreeLinePointers (page );
887
+ else
888
+ PageClearHasFreeLinePointers (page );
889
+ }
890
+
787
891
/*
788
892
* PageGetFreeSpace
789
893
* Returns the size of the free (allocatable) space on a page,
0 commit comments