Hi,
I have written a code for quick indextuple deletion from an relation by heap tuple TID. The code relate to "Retail IndexTuple deletion" enhancement of btree index on postgresql wiki [1].
Briefly, it includes three steps:
1. Key generation for index tuple searching.
2. Index relation search for tuple with the heap tuple TID.
3. Deletion of the tuple from the index relation.

Now, index relation cleaning performs by vacuum which scan all index relation for dead entries sequentially, tuple-by-tuple. This simplistic and safe method can be significantly surpasses in the cases, than a number of dead entries is not large by retail deletions which uses index scans for search dead entries. Also, it can be used by distributed systems for reduce cost of a global index vacuum.

Patch '0001-retail-indextuple-deletion' introduce new function amtargetdelete() in access method interface. Patch '0002-quick-vacuum-strategy' implements this function for an alternative strategy of lazy index vacuum, called 'Quick Vacuum'.

The code demands hold DEAD tuple storage until scan key will be created. In this version I add 'target_index_deletion_factor' option. If it more than 0, heap_page_prune() uses ItemIdMarkDead() instead of ItemIdSetDead() function for set DEAD flag and hold the tuple storage. Next step is developing background worker which will collect pairs (tid, scankey) of DEAD tuples from heap_page_prune() function.

Here are test description and some execution time measurements results showing the benefit of this patches:

Test:
-----
create table test_range(id serial primary key, value integer);
insert into test_range (value) select random()*1e7/10^N from generate_series(1, 1e7);
DELETE FROM test_range WHERE value=1;
VACUUM test_range;

Results:
--------

| n | t1, s  | t2, s  | speedup |
|---|---------------------------|
| 0 | 0.00003| 0.4476 | 1748.4  |
| 1 | 0.00006| 0.5367 | 855.99  |
| 2 | 0.0004 | 0.9804 | 233.99  |
| 3 | 0.0048 | 1.6493 | 34.576  |
| 4 | 0.5600 | 2.4854 | 4.4382  |
| 5 | 3.3300 | 3.9555 | 1.2012  |
| 6 | 17.700 | 5.6000 | 0.3164  |
|---|---------------------------|
in the table, t1 - measured execution time of lazy_vacuum_index() function by Quick-Vacuum strategy; t2 - measured execution time of lazy_vacuum_index() function by Lazy-Vacuum strategy;

Note, guaranteed allowable time of index scans (used for quick deletion) will be achieved by storing equal-key index tuples in physical TID order [2] with patch [3].

[1] https://wiki.postgresql.org/wiki/Key_normalization#Retail_IndexTuple_deletion [2] https://wiki.postgresql.org/wiki/Key_normalization#Making_all_items_in_the_index_unique_by_treating_heap_TID_as_an_implicit_last_attribute [3] https://www.postgresql.org/message-id/CAH2-WzkVb0Kom%3DR%2B88fDFb%3DJSxZMFvbHVC6Mn9LJ2n%3DX%3DkS-Uw%40mail.gmail.com

--
Andrey Lepikhov
Postgres Professional:
https://postgrespro.com
The Russian Postgres Company
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6b2b9e3..96f1d47 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -126,6 +126,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = blbuild;
 	amroutine->ambuildempty = blbuildempty;
 	amroutine->aminsert = blinsert;
+	amroutine->amtargetdelete = NULL;
 	amroutine->ambulkdelete = blbulkdelete;
 	amroutine->amvacuumcleanup = blvacuumcleanup;
 	amroutine->amcanreturn = NULL;
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index e95fbbc..a0e06bd 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -103,6 +103,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = brinbuild;
 	amroutine->ambuildempty = brinbuildempty;
 	amroutine->aminsert = brininsert;
+	amroutine->amtargetdelete = NULL;
 	amroutine->ambulkdelete = brinbulkdelete;
 	amroutine->amvacuumcleanup = brinvacuumcleanup;
 	amroutine->amcanreturn = NULL;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 0a32182..acf14e7 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -58,6 +58,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = ginbuild;
 	amroutine->ambuildempty = ginbuildempty;
 	amroutine->aminsert = gininsert;
+	amroutine->amtargetdelete = NULL;
 	amroutine->ambulkdelete = ginbulkdelete;
 	amroutine->amvacuumcleanup = ginvacuumcleanup;
 	amroutine->amcanreturn = NULL;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8a42eff..d7a53d2 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -80,6 +80,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = gistbuild;
 	amroutine->ambuildempty = gistbuildempty;
 	amroutine->aminsert = gistinsert;
+	amroutine->amtargetdelete = NULL;
 	amroutine->ambulkdelete = gistbulkdelete;
 	amroutine->amvacuumcleanup = gistvacuumcleanup;
 	amroutine->amcanreturn = gistcanreturn;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0002df3..5fb32d6 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -76,6 +76,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = hashbuild;
 	amroutine->ambuildempty = hashbuildempty;
 	amroutine->aminsert = hashinsert;
+	amroutine->amtargetdelete = NULL;
 	amroutine->ambulkdelete = hashbulkdelete;
 	amroutine->amvacuumcleanup = hashvacuumcleanup;
 	amroutine->amcanreturn = NULL;
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 22b5cc9..a25d844 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -730,6 +730,23 @@ index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap)
 	return ntids;
 }
 
+IndexBulkDeleteResult *
+index_target_delete(IndexVacuumInfo *info,
+					IndexBulkDeleteResult *stats,
+					Relation hrel,
+					Datum *values,
+					bool *isnull,
+					ItemPointer htid)
+{
+	Relation indexRelation = info->index;
+
+	RELATION_CHECKS;
+
+	CHECK_REL_PROCEDURE(amtargetdelete);
+
+	return indexRelation->rd_amroutine->amtargetdelete(info, stats, hrel, values, isnull, htid);
+}
+
 /* ----------------
  *		index_bulk_delete - do mass deletion of index entries
  *
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 27a3032..dd87a05 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -34,7 +34,9 @@
 #include "storage/smgr.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/syscache.h"
 
 
 /* Working state needed by btvacuumpage */
@@ -127,6 +129,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = btbuild;
 	amroutine->ambuildempty = btbuildempty;
 	amroutine->aminsert = btinsert;
+	amroutine->amtargetdelete = bttargetdelete;
 	amroutine->ambulkdelete = btbulkdelete;
 	amroutine->amvacuumcleanup = btvacuumcleanup;
 	amroutine->amcanreturn = btcanreturn;
@@ -884,6 +887,195 @@ btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	return stats;
 }
 
+static void
+cleanup_block(IndexVacuumInfo *info,
+		   IndexBulkDeleteResult *stats, BlockNumber blkno)
+{
+	BTVacState	vstate;
+
+	vstate.info = info;
+	vstate.stats = stats;
+	vstate.callback = NULL;
+	vstate.callback_state = NULL;
+	vstate.cycleid = 0;
+	vstate.lastBlockVacuumed = BTREE_METAPAGE;	/* Initialise at first block */
+	vstate.lastBlockLocked = BTREE_METAPAGE;
+	vstate.totFreePages = 0;
+	vstate.oldestBtpoXact = InvalidTransactionId;
+
+	/* Create a temporary memory context to run _bt_pagedel in */
+	vstate.pagedelcontext = AllocSetContextCreate(CurrentMemoryContext,
+												  "_bt_pagedel",
+												  ALLOCSET_DEFAULT_SIZES);
+	btvacuumpage(&vstate, blkno, blkno);
+
+	if (vstate.totFreePages > 0)
+		IndexFreeSpaceMapVacuum(info->index);
+
+	stats->pages_free += vstate.totFreePages;
+
+	MemoryContextDelete(vstate.pagedelcontext);
+}
+
+/*
+ * Build scan key for an index relation by a heap tuple TID
+ */
+static int
+build_scan_key(ScanKey skey, Relation rel, Relation irel, Datum *values, bool *nulls)
+{
+	TupleDesc	desc = RelationGetDescr(rel);
+	HeapTuple	tuple;
+	Datum		indclassDatum;
+	int			attoff;
+	oidvector*	opclass;
+	int2vector*	indkey = &irel->rd_index->indkey;
+	bool 		isnull;
+
+	tuple = heap_form_tuple(desc, values, nulls);
+	indclassDatum = SysCacheGetAttr(INDEXRELID, irel->rd_indextuple,
+										Anum_pg_index_indclass, &isnull);
+
+	opclass = (oidvector *) DatumGetPointer(indclassDatum);
+	for (attoff = 0; attoff < IndexRelationGetNumberOfKeyAttributes(irel); attoff++)
+	{
+		Oid				operator;
+		Oid				opfamily;
+		RegProcedure	regop;
+		int				pkattno = attoff + 1;
+		int				mainattno = indkey->values[attoff];
+		Oid				optype = get_opclass_input_type(opclass->values[attoff]);
+		Datum			scanvalue;
+		StrategyNumber	strategy;
+
+		if (mainattno > 0)
+			scanvalue = heap_getattr(tuple, mainattno, desc, &isnull);
+		else
+			return -1;
+
+		/*
+		 * Load the operator info.  We need this to get the equality operator
+		 * function for the scan key.
+		 */
+		opfamily = get_opclass_family(opclass->values[attoff]);
+
+		if (irel->rd_exclstrats != NULL)
+			strategy = irel->rd_exclstrats[attoff];
+		else
+			strategy = BTEqualStrategyNumber;
+
+		operator = get_opfamily_member(opfamily, optype, optype, strategy);
+		if (!OidIsValid(operator))
+			return -1;
+
+		regop = get_opcode(operator);
+
+		/* Initialize the scan key. */
+		ScanKeyInit(&skey[attoff],
+				pkattno,
+				strategy,
+				regop,
+				scanvalue);
+
+		/* Check for null value. */
+		if (isnull)
+			skey[attoff].sk_flags |= SK_ISNULL;
+
+		/* Use index collation */
+		if (irel->rd_indcollation != NULL)
+			skey[attoff].sk_collation = irel->rd_indcollation[attoff];
+
+		/* Use index procedure */
+		if (irel->rd_exclprocs != NULL)
+			fmgr_info(irel->rd_exclprocs[attoff], &(skey[attoff].sk_func));
+	}
+
+	return 0;
+}
+
+IndexBulkDeleteResult*
+bttargetdelete(IndexVacuumInfo *info,
+			   IndexBulkDeleteResult *stats,
+			   Relation hrel,
+			   Datum *values,
+			   bool *isnull,
+			   ItemPointer htid)
+{
+	Relation		irel = info->index;
+	ScanKeyData		skey[INDEX_MAX_KEYS];
+	SnapshotData	snap;
+	IndexScanDesc	scan;
+	ItemPointer		tid;
+	bool			found = false;
+	OffsetNumber	offnum;
+	BlockNumber		blkno;
+
+	if (stats == NULL)
+	{
+		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
+		stats->estimated_count = false;
+		stats->num_index_tuples = 0;
+		stats->pages_deleted = 0;
+	}
+
+	if (build_scan_key(skey, hrel, irel, values, isnull) != 0)
+	{
+		pfree(stats);
+		return NULL;
+	}
+
+	InitDirtySnapshot(snap);
+	scan = index_beginscan(hrel, irel, &snap, IndexRelationGetNumberOfKeyAttributes(irel), 0);
+	index_rescan(scan, skey, IndexRelationGetNumberOfKeyAttributes(irel), NULL, 0);
+
+	/* Pass along index entries array with equal scan key value */
+	while ((!found) && ((tid = index_getnext_tid(scan, ForwardScanDirection)) != NULL))
+	{
+		if (ItemPointerEquals(tid, htid))
+		{
+			BTScanPos	idx_pos = (BTScanPos) &(((BTScanOpaque) scan->opaque)->currPos);
+			blkno = idx_pos->currPage;
+			offnum = idx_pos->items[idx_pos->itemIndex].indexOffset;
+			found = true;
+		}
+	}
+	index_endscan(scan);
+
+	/* Delete index tuple */
+	if (found)
+	{
+		Buffer		buf;
+		Page		page;
+		bool		needLock;
+		int			npages;
+		IndexTuple	itup;
+
+		needLock = !RELATION_IS_LOCAL(irel);
+		if (needLock)
+			LockRelationForExtension(irel, ExclusiveLock);
+			npages = RelationGetNumberOfBlocks(irel);
+		if (needLock)
+			UnlockRelationForExtension(irel, ExclusiveLock);
+		if (blkno >= npages)
+			return stats;
+
+		buf = ReadBuffer(irel, blkno);
+		page = BufferGetPage(buf);
+		LockBufferForCleanup(buf);
+
+		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
+
+		if (ItemPointerEquals(htid, &(itup->t_tid)))
+		{
+			_bt_delitems_vacuum(irel, buf, &offnum, 1, blkno);
+			stats->tuples_removed++;
+			stats->num_pages = npages;
+		}
+		_bt_relbuf(irel, buf);
+		cleanup_block(info, stats, blkno);
+	}
+	return stats;
+}
+
 /*
  * Post-VACUUM cleanup.
  *
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 4a9b5da..3257281 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -56,6 +56,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = spgbuild;
 	amroutine->ambuildempty = spgbuildempty;
 	amroutine->aminsert = spginsert;
+	amroutine->amtargetdelete = NULL;
 	amroutine->ambulkdelete = spgbulkdelete;
 	amroutine->amvacuumcleanup = spgvacuumcleanup;
 	amroutine->amcanreturn = spgcanreturn;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 14526a6..411bb43 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -76,6 +76,13 @@ typedef bool (*aminsert_function) (Relation indexRelation,
 								   IndexUniqueCheck checkUnique,
 								   struct IndexInfo *indexInfo);
 
+/* target delete */
+typedef IndexBulkDeleteResult *(*amtargetdelete_function) (IndexVacuumInfo *info,
+														   IndexBulkDeleteResult *stats,
+														   Relation hrel,
+														   Datum *values,
+														   bool *isnull,
+														   ItemPointer htid);
 /* bulk delete */
 typedef IndexBulkDeleteResult *(*ambulkdelete_function) (IndexVacuumInfo *info,
 														 IndexBulkDeleteResult *stats,
@@ -207,6 +214,7 @@ typedef struct IndexAmRoutine
 	ambuild_function ambuild;
 	ambuildempty_function ambuildempty;
 	aminsert_function aminsert;
+	amtargetdelete_function amtargetdelete;
 	ambulkdelete_function ambulkdelete;
 	amvacuumcleanup_function amvacuumcleanup;
 	amcanreturn_function amcanreturn;	/* can be NULL */
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 24c720b..cdcc6db 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -163,6 +163,12 @@ extern HeapTuple index_fetch_heap(IndexScanDesc scan);
 extern HeapTuple index_getnext(IndexScanDesc scan, ScanDirection direction);
 extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap);
 
+extern IndexBulkDeleteResult *index_target_delete(IndexVacuumInfo *info,
+												IndexBulkDeleteResult *stats,
+												Relation hrel,
+												Datum *values,
+												bool *isnull,
+												ItemPointer htid);
 extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info,
 				  IndexBulkDeleteResult *stats,
 				  IndexBulkDeleteCallback callback,
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 04ecb4c..0de5a24 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -508,6 +508,12 @@ extern void btparallelrescan(IndexScanDesc scan);
 extern void btendscan(IndexScanDesc scan);
 extern void btmarkpos(IndexScanDesc scan);
 extern void btrestrpos(IndexScanDesc scan);
+extern IndexBulkDeleteResult *bttargetdelete(IndexVacuumInfo *info,
+		   IndexBulkDeleteResult *stats,
+		   Relation hrel,
+		   Datum *values,
+		   bool *isnull,
+		   ItemPointer htid);
 extern IndexBulkDeleteResult *btbulkdelete(IndexVacuumInfo *info,
 			 IndexBulkDeleteResult *stats,
 			 IndexBulkDeleteCallback callback,
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index c2f5343..d394a7a 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -43,6 +43,9 @@ typedef struct
 	bool		marked[MaxHeapTuplesPerPage + 1];
 } PruneState;
 
+/* Parameter for target deletion strategy in lazy vacuum */
+double target_index_deletion_factor = 0.01;
+
 /* Local functions */
 static int heap_prune_chain(Relation relation, Buffer buffer,
 				 OffsetNumber rootoffnum,
@@ -580,8 +583,10 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 */
 		for (i = 1; (i < nchain) && (chainitems[i - 1] != latestdead); i++)
 		{
-			heap_prune_record_unused(prstate, chainitems[i]);
 			ndeleted++;
+			if (chainitems[i] == latestdead)
+				continue;
+			heap_prune_record_unused(prstate, chainitems[i]);
 		}
 
 		/*
@@ -598,9 +603,24 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 * redirect the root to the correct chain member.
 		 */
 		if (i >= nchain)
+		{
+			if (rootoffnum != latestdead)
+			{
+				if (ItemIdIsNormal(rootlp))
+					heap_prune_record_unused(prstate, latestdead);
+				else
+				{
+					ItemIdSetDeadRedirect(rootlp, latestdead);
+					heap_prune_record_dead(prstate, latestdead);
+				}
+			}
 			heap_prune_record_dead(prstate, rootoffnum);
-		else
+		}
+		else {
+			if (rootoffnum != latestdead)
+				heap_prune_record_unused(prstate, latestdead);
 			heap_prune_record_redirect(prstate, rootoffnum, chainitems[i]);
+		}
 	}
 	else if (nchain < 2 && ItemIdIsRedirected(rootlp))
 	{
@@ -706,7 +726,10 @@ heap_page_prune_execute(Buffer buffer,
 		OffsetNumber off = *offnum++;
 		ItemId		lp = PageGetItemId(page, off);
 
-		ItemIdSetDead(lp);
+		if (target_index_deletion_factor > 0)
+			ItemIdMarkDead(lp);
+		else
+			ItemIdSetDead(lp);
 	}
 
 	/* Update all now-unused line pointers */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 5649a70..04127a4 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -137,9 +137,9 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	Relation	rel;
 } LVRelStats;
 
-
 /* A few variables that don't seem worth passing around as parameters */
 static int	elevel = -1;
 
@@ -519,6 +519,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 	vacrelstats->tupcount_pages = 0;
 	vacrelstats->nonempty_pages = 0;
 	vacrelstats->latestRemovedXid = InvalidTransactionId;
+	vacrelstats->rel = onerel;
 
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
@@ -1671,6 +1672,47 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup)
 	return false;
 }
 
+/*
+ * Get tuple from heap for a scan key building. If tuples in HOT chain
+ * have not a storage, return NULL.
+ */
+static HeapTuple get_tuple_by_tid(Relation rel, ItemPointer tid)
+{
+	Buffer			buffer;
+	Page			page;
+	OffsetNumber	offnum;
+	ItemId			lp;
+	HeapTuple		tuple;
+
+	buffer = ReadBufferExtended(rel, MAIN_FORKNUM, ItemPointerGetBlockNumber(tid), RBM_NORMAL, NULL);
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+	page = (Page) BufferGetPage(buffer);
+	offnum = ItemPointerGetOffsetNumber(tid);
+	lp = PageGetItemId(page, offnum);
+
+	while (!ItemIdHasStorage(lp))
+	{
+		offnum = ItemIdGetRedirect(lp);
+		lp = PageGetItemId(page, offnum);
+
+		if (!ItemIdIsUsed(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			return NULL;
+		}
+	}
+
+	tuple = palloc(sizeof(HeapTupleData));
+	ItemPointerSet(&(tuple->t_self), BufferGetBlockNumber(buffer), offnum);
+	tuple->t_tableOid = RelationGetRelid(rel);
+	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tuple->t_len = ItemIdGetLength(lp);
+	UnlockReleaseBuffer(buffer);
+	return tuple;
+}
+
+#include "access/nbtree.h"
 
 /*
  *	lazy_vacuum_index() -- vacuum one index relation.
@@ -1684,6 +1726,7 @@ lazy_vacuum_index(Relation indrel,
 				  LVRelStats *vacrelstats)
 {
 	IndexVacuumInfo ivinfo;
+	bool use_target_strategy = true;//(vacrelstats->num_dead_tuples/vacrelstats->old_live_tuples < target_index_deletion_factor);
 	PGRUsage	ru0;
 
 	pg_rusage_init(&ru0);
@@ -1696,9 +1739,33 @@ lazy_vacuum_index(Relation indrel,
 	ivinfo.num_heap_tuples = vacrelstats->old_live_tuples;
 	ivinfo.strategy = vac_strategy;
 
-	/* Do bulk deletion */
-	*stats = index_bulk_delete(&ivinfo, *stats,
-							   lazy_tid_reaped, (void *) vacrelstats);
+	if ((use_target_strategy) && (indrel->rd_amroutine->amtargetdelete != NULL))
+	{
+		int			tnum;
+		TupleDesc	desc = RelationGetDescr(vacrelstats->rel);
+		Datum*		values = (Datum *) palloc(desc->natts * sizeof(Datum));
+		bool*		isnull = (bool *) palloc(desc->natts * sizeof(bool));
+
+		for (tnum = 0; tnum < vacrelstats->num_dead_tuples; tnum++)
+		{
+			HeapTuple tuple = get_tuple_by_tid(vacrelstats->rel, &(vacrelstats->dead_tuples[tnum]));
+
+			if (tuple == NULL)
+				continue;
+
+			heap_deform_tuple(tuple, desc, values, isnull);
+			*stats = index_target_delete(&ivinfo, *stats, vacrelstats->rel, values, isnull, &(vacrelstats->dead_tuples[tnum]));
+			if (*stats == NULL)
+				break;
+		}
+		pfree(values);
+		pfree(isnull);
+	}
+
+	if ((!use_target_strategy) || (*stats == NULL))
+		/* Do bulk deletion */
+		*stats = index_bulk_delete(&ivinfo, *stats,
+									lazy_tid_reaped, (void *) vacrelstats);
 
 	ereport(elevel,
 			(errmsg("scanned index \"%s\" to remove %d row versions",
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index fa3c8a7..3f6c83b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3236,7 +3236,15 @@ static struct config_real ConfigureNamesReal[] =
 		0.1, 0.0, 100.0,
 		NULL, NULL, NULL
 	},
-
+	{
+		{"target_index_deletion_factor", PGC_SIGHUP, AUTOVACUUM,
+			gettext_noop("Maximum number of vacuumed tuples as a fraction of reltuples where we can use target index vacuum strategy."),
+			NULL
+		},
+		&target_index_deletion_factor,
+		0.01, 0.0, 1.0,
+		NULL, NULL, NULL
+	},
 	{
 		{"checkpoint_completion_target", PGC_SIGHUP, WAL_CHECKPOINTS,
 			gettext_noop("Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval."),
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index ca5cad7..c5a7bc8 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -100,6 +100,7 @@ extern Relation heap_openrv_extended(const RangeVar *relation,
 typedef struct HeapScanDescData *HeapScanDesc;
 typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 
+extern double target_index_deletion_factor;
 /*
  * HeapScanIsValid
  *		True iff the heap scan is valid.
diff --git a/src/include/storage/itemid.h b/src/include/storage/itemid.h
index 60570cc..9ea9269 100644
--- a/src/include/storage/itemid.h
+++ b/src/include/storage/itemid.h
@@ -112,6 +112,11 @@ typedef uint16 ItemLength;
 #define ItemIdIsDead(itemId) \
 	((itemId)->lp_flags == LP_DEAD)
 
+#define ItemIdIsDeadRedirection(itemId) \
+	( ((itemId)->lp_flags == 3) && \
+	  ((itemId)->lp_len == 0) && \
+	  ((itemId)->lp_off != 0) )
+
 /*
  * ItemIdHasStorage
  *		True iff item identifier has associated storage.
@@ -155,6 +160,12 @@ typedef uint16 ItemLength;
 	(itemId)->lp_len = 0 \
 )
 
+#define ItemIdSetDeadRedirect(itemId, link) \
+( \
+	(itemId)->lp_off = (link), \
+	(itemId)->lp_len = 0 \
+)
+
 /*
  * ItemIdSetDead
  *		Set the item identifier to be DEAD, with no storage.

Reply via email to