Skip to content

Commit ebe2c4d

Browse files
committed
Prune dead unindexed TOAST tuples directly to LP_UNUSED
In a TOAST relation, Direct TOAST chunks are addressed directly by physical TID and have chunk_id IS NULL, meaning they are excluded from the TOAST table's index (which is partial on WHERE chunk_id IS NOT NULL). Consequently, when opportunistic page pruning or VACUUM encounters dead Direct TOAST chunks, they do not require index vacuuming to clean up index pointers. Special-case these unindexed TOAST tuples in pruneheap.c: 1. heap_tuple_header_is_unindexed_toast(): Check whether a tuple header has NULL attributes with attribute 0 (chunk_id) being NULL. 2. heap_prune_is_unindexed_toast_tuple(): Verify that the relation is a TOAST relation (relkind == RELKIND_TOASTVALUE) and the tuple header is unindexed. 3. heap_prune_record_dead_or_unused(): When pruning a dead normal tuple, if it is an unindexed TOAST tuple, mark it LP_UNUSED immediately via heap_prune_record_unused() rather than LP_DEAD, freeing the line pointer and reclaiming page space without waiting for a two-pass index vacuum. 4. heap_page_prune_execute(): Relax the assertion for items marked LP_UNUSED when ndead > 0 so that unindexed TOAST tuples are allowed alongside heap-only tuples.
1 parent 41696ab commit ebe2c4d

1 file changed

Lines changed: 51 additions & 8 deletions

File tree

‎src/backend/access/heap/pruneheap.c‎

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,23 +1794,63 @@ heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum,
17941794
prstate->ndeleted++;
17951795
}
17961796

1797+
/*
1798+
* Check if a tuple header belongs to an unindexed Direct TOAST tuple.
1799+
* In a TOAST table, chunk_id is the first attribute (attnum 1). Direct TOAST
1800+
* chunks leave chunk_id NULL since they are accessed directly by TID rather
1801+
* than via the TOAST index.
1802+
*/
1803+
static inline bool
1804+
heap_tuple_header_is_unindexed_toast(HeapTupleHeader htup)
1805+
{
1806+
return (htup != NULL &&
1807+
(htup->t_infomask & HEAP_HASNULL) != 0 &&
1808+
att_isnull(0, htup->t_bits));
1809+
}
1810+
1811+
static inline bool
1812+
heap_prune_is_unindexed_toast_tuple(PruneState *prstate, HeapTupleHeader htup)
1813+
{
1814+
if (prstate->relation->rd_rel->relkind == RELKIND_TOASTVALUE &&
1815+
heap_tuple_header_is_unindexed_toast(htup))
1816+
return true;
1817+
1818+
return false;
1819+
}
1820+
17971821
/*
17981822
* Depending on whether or not the caller set mark_unused_now to true, record that a
17991823
* line pointer should be marked LP_DEAD or LP_UNUSED. There are other cases in
18001824
* which we will mark line pointers LP_UNUSED, but we will not mark line
18011825
* pointers LP_DEAD if mark_unused_now is true.
1826+
*
1827+
* For TOAST relations, tuples without an index entry (chunk_id is NULL) can also
1828+
* be marked LP_UNUSED immediately without waiting for index vacuuming.
18021829
*/
18031830
static void
18041831
heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum,
18051832
bool was_normal)
18061833
{
1834+
bool is_unindexed_toast = false;
1835+
1836+
if (!prstate->mark_unused_now && was_normal)
1837+
{
1838+
ItemId lp = PageGetItemId(prstate->page, offnum);
1839+
HeapTupleHeader htup;
1840+
1841+
Assert(ItemIdHasStorage(lp) && ItemIdIsNormal(lp));
1842+
htup = (HeapTupleHeader) PageGetItem(prstate->page, lp);
1843+
1844+
is_unindexed_toast = heap_prune_is_unindexed_toast_tuple(prstate, htup);
1845+
}
1846+
18071847
/*
1808-
* If the caller set mark_unused_now to true, we can remove dead tuples
1809-
* during pruning instead of marking their line pointers dead. Set this
1810-
* tuple's line pointer LP_UNUSED. We hint that this option is less
1811-
* likely.
1848+
* If the caller set mark_unused_now to true, or if this is an unindexed
1849+
* TOAST tuple, we can remove dead tuples during pruning instead of
1850+
* marking their line pointers dead. Set this tuple's line pointer
1851+
* LP_UNUSED. We hint that this option is less likely.
18121852
*/
1813-
if (unlikely(prstate->mark_unused_now))
1853+
if (unlikely(prstate->mark_unused_now) || is_unindexed_toast)
18141854
heap_prune_record_unused(prstate, offnum, was_normal);
18151855
else
18161856
heap_prune_record_dead(prstate, offnum, was_normal);
@@ -1819,7 +1859,8 @@ heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum,
18191859
* It's incorrect for the page to be set all-visible if it contains dead
18201860
* items. Fix that on the heap page and check the VM for corruption as
18211861
* well. Do that here rather than in heap_prune_record_dead() so we also
1822-
* cover tuples that are directly marked LP_UNUSED via mark_unused_now.
1862+
* cover tuples that are directly marked LP_UNUSED via mark_unused_now or
1863+
* unindexed TOAST tuples.
18231864
*/
18241865
if (PageIsAllVisible(prstate->page))
18251866
heap_page_fix_vm_corruption(prstate, offnum, VM_CORRUPT_LPDEAD);
@@ -2217,13 +2258,15 @@ heap_page_prune_execute(Buffer buffer, bool lp_truncate_only,
22172258
* items to be made LP_UNUSED instead. This is only possible if
22182259
* the relation has no indexes. If there are any dead items, then
22192260
* mark_unused_now was not true and every item being marked
2220-
* LP_UNUSED must refer to a heap-only tuple.
2261+
* LP_UNUSED must refer to a heap-only tuple or an unindexed
2262+
* TOAST tuple.
22212263
*/
22222264
if (ndead > 0)
22232265
{
22242266
Assert(ItemIdHasStorage(lp) && ItemIdIsNormal(lp));
22252267
htup = (HeapTupleHeader) PageGetItem(page, lp);
2226-
Assert(HeapTupleHeaderIsHeapOnly(htup));
2268+
Assert(HeapTupleHeaderIsHeapOnly(htup) ||
2269+
heap_tuple_header_is_unindexed_toast(htup));
22272270
}
22282271
else
22292272
Assert(ItemIdIsUsed(lp));

0 commit comments

Comments
 (0)