From 89da9180a688e746c6f64d2c9ca6cf2faf593f11 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Mon, 9 Nov 2020 12:59:30 -0800
Subject: [PATCH v8 3/5] Pass down "logically unchanged index" hint.

Add an executor aminsert() hinting mechanism that informs index AMs that
the incoming index tuple (the tuple that accompanies the hint) is not
being inserted because of a logical change to indexed columns (from an
UPDATE or INSERT statement).  This "logically unchanged" hint indicates
that the incoming item must be a duplicate of some existing item in the
index (a physical tuple that represents an obsolescent version of the
logical row affected by an UPDATE).  When this happens the new tuple is
only needed so that the index will have a separate entry for the latest
logical row (it's latest version/HOT chain).

An aminsert() call which gets the hint is fundamentally different to any
aminsert() call needed for an INSERT statement.  It's also fundamentally
difference to an aminsert() call needed for an UPDATE statement where
the index is logically changed by the UPDATE.  Recognizing this
difference can allow cleanup of garbage tuples in index access methods
to work better.  Cleanup can intelligently target tuples that are likely
to be garbage, without wasting too many cycles on less promising tuples.

This commit is infrastructure for an upcoming commit that will teach
nbtree to perform bottom-up index deletion.  No index AM applies the
hint at this point.  It is not useful on its own.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-Wz=CEKFa74EScx_hFVshCOn6AA5T-ajFASTdzipdkLTNQQ@mail.gmail.com
---
 src/include/access/amapi.h                  |  1 +
 src/include/access/brin_internal.h          |  1 +
 src/include/access/genam.h                  |  1 +
 src/include/access/gin_private.h            |  1 +
 src/include/access/gist_private.h           |  1 +
 src/include/access/hash.h                   |  1 +
 src/include/access/heapam.h                 |  3 +-
 src/include/access/nbtree.h                 |  1 +
 src/include/access/spgist.h                 |  1 +
 src/include/access/tableam.h                | 11 ++++--
 src/include/executor/executor.h             |  3 +-
 src/backend/access/brin/brin.c              |  1 +
 src/backend/access/common/toast_internals.c |  2 +-
 src/backend/access/gin/gininsert.c          |  1 +
 src/backend/access/gist/gist.c              |  1 +
 src/backend/access/hash/hash.c              |  1 +
 src/backend/access/heap/heapam.c            | 12 ++++--
 src/backend/access/heap/heapam_handler.c    |  6 ++-
 src/backend/access/index/indexam.c          |  4 +-
 src/backend/access/nbtree/nbtree.c          |  1 +
 src/backend/access/spgist/spginsert.c       |  1 +
 src/backend/access/table/tableam.c          |  2 +-
 src/backend/catalog/indexing.c              |  1 +
 src/backend/commands/constraint.c           |  2 +-
 src/backend/commands/copy.c                 |  5 ++-
 src/backend/executor/execIndexing.c         | 43 ++++++++++++++++++++-
 src/backend/executor/execReplication.c      |  4 +-
 src/backend/executor/nodeModifyTable.c      | 14 +++++--
 contrib/bloom/blinsert.c                    |  1 +
 contrib/bloom/bloom.h                       |  1 +
 doc/src/sgml/indexam.sgml                   | 13 +++++++
 31 files changed, 118 insertions(+), 23 deletions(-)

diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 85b4766016..50422c9195 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -110,6 +110,7 @@ typedef bool (*aminsert_function) (Relation indexRelation,
 								   ItemPointer heap_tid,
 								   Relation heapRelation,
 								   IndexUniqueCheck checkUnique,
+								   bool indexunchanged,
 								   struct IndexInfo *indexInfo);
 
 /* bulk delete */
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 9ffc9100c0..5016b8035f 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -91,6 +91,7 @@ extern void brinbuildempty(Relation index);
 extern bool brininsert(Relation idxRel, Datum *values, bool *nulls,
 					   ItemPointer heaptid, Relation heapRel,
 					   IndexUniqueCheck checkUnique,
+					   bool indexunchanged,
 					   struct IndexInfo *indexInfo);
 extern IndexScanDesc brinbeginscan(Relation r, int nkeys, int norderbys);
 extern int64 bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 68d90f5141..43bf62fbcc 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -143,6 +143,7 @@ extern bool index_insert(Relation indexRelation,
 						 ItemPointer heap_t_ctid,
 						 Relation heapRelation,
 						 IndexUniqueCheck checkUnique,
+						 bool indexunchanged,
 						 struct IndexInfo *indexInfo);
 
 extern IndexScanDesc index_beginscan(Relation heapRelation,
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index 5cb2f72e4c..99541b82e8 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -116,6 +116,7 @@ extern void ginbuildempty(Relation index);
 extern bool gininsert(Relation index, Datum *values, bool *isnull,
 					  ItemPointer ht_ctid, Relation heapRel,
 					  IndexUniqueCheck checkUnique,
+					  bool indexunchanged,
 					  struct IndexInfo *indexInfo);
 extern void ginEntryInsert(GinState *ginstate,
 						   OffsetNumber attnum, Datum key, GinNullCategory category,
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index b68c01a5f2..6adccb26a0 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -403,6 +403,7 @@ extern void gistbuildempty(Relation index);
 extern bool gistinsert(Relation r, Datum *values, bool *isnull,
 					   ItemPointer ht_ctid, Relation heapRel,
 					   IndexUniqueCheck checkUnique,
+					   bool indexunchanged,
 					   struct IndexInfo *indexInfo);
 extern MemoryContext createTempGistContext(void);
 extern GISTSTATE *initGISTstate(Relation index);
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index bab4d9f1b0..d832dd03de 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -364,6 +364,7 @@ extern void hashbuildempty(Relation index);
 extern bool hashinsert(Relation rel, Datum *values, bool *isnull,
 					   ItemPointer ht_ctid, Relation heapRel,
 					   IndexUniqueCheck checkUnique,
+					   bool indexunchanged,
 					   struct IndexInfo *indexInfo);
 extern bool hashgettuple(IndexScanDesc scan, ScanDirection dir);
 extern int64 hashgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 92b19dba32..d950083a7d 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -148,7 +148,8 @@ extern void heap_abort_speculative(Relation relation, ItemPointer tid);
 extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
-							 struct TM_FailureData *tmfd, LockTupleMode *lockmode);
+							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
+							 Bitmapset **modified_attrs_hint);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_update,
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index e8fecc6026..d1b3e0ba6a 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -996,6 +996,7 @@ extern void btbuildempty(Relation index);
 extern bool btinsert(Relation rel, Datum *values, bool *isnull,
 					 ItemPointer ht_ctid, Relation heapRel,
 					 IndexUniqueCheck checkUnique,
+					 bool indexunchanged,
 					 struct IndexInfo *indexInfo);
 extern IndexScanDesc btbeginscan(Relation rel, int nkeys, int norderbys);
 extern Size btestimateparallelscan(void);
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index 9f2ccc1730..3a4a570d8c 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -199,6 +199,7 @@ extern void spgbuildempty(Relation index);
 extern bool spginsert(Relation index, Datum *values, bool *isnull,
 					  ItemPointer ht_ctid, Relation heapRel,
 					  IndexUniqueCheck checkUnique,
+					  bool indexunchanged,
 					  struct IndexInfo *indexInfo);
 
 /* spgscan.c */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 5cd698c885..f5f778680a 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -465,7 +465,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 bool *update_indexes);
+								 bool *update_indexes,
+								 Bitmapset **modified_attrs_hint);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1372,6 +1373,9 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  *	lockmode - filled with lock mode acquired on tuple
  *  update_indexes - in success cases this is set to true if new index entries
  *		are required for this tuple
+ *	modified_attrs_hint - which attributes were logically modified by the
+ *		update.  Passed down to index AMs as a hint from the executor.
+ *		Enables table_index_delete_check() optimization.
  *
  * Normal, successful return value is TM_Ok, which means we did actually
  * update it.  Failure return codes are TM_SelfModified, TM_Updated, and
@@ -1391,12 +1395,13 @@ static inline TM_Result
 table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
 				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
 				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
-				   bool *update_indexes)
+				   bool *update_indexes, Bitmapset **modified_attrs_hint)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
 										 wait, tmfd,
-										 lockmode, update_indexes);
+										 lockmode, update_indexes,
+										 modified_attrs_hint);
 }
 
 /*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 0c48d2a519..6e6e56583b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -582,7 +582,8 @@ extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool noDupErr,
-								   bool *specConflict, List *arbiterIndexes);
+								   bool *specConflict, List *arbiterIndexes,
+								   Bitmapset *modified_attrs_hint);
 extern bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo,
 									  TupleTableSlot *slot,
 									  EState *estate, ItemPointer conflictTid,
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 1f72562c60..d295c592be 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -151,6 +151,7 @@ bool
 brininsert(Relation idxRel, Datum *values, bool *nulls,
 		   ItemPointer heaptid, Relation heapRel,
 		   IndexUniqueCheck checkUnique,
+		   bool indexunchanged,
 		   IndexInfo *indexInfo)
 {
 	BlockNumber pagesPerRange;
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 25a81e5ec6..e206e67756 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -328,7 +328,7 @@ toast_save_datum(Relation rel, Datum value,
 							 toastrel,
 							 toastidxs[i]->rd_index->indisunique ?
 							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
-							 NULL);
+							 false, NULL);
 		}
 
 		/*
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 77433dc8a4..6a1e8560a3 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -488,6 +488,7 @@ bool
 gininsert(Relation index, Datum *values, bool *isnull,
 		  ItemPointer ht_ctid, Relation heapRel,
 		  IndexUniqueCheck checkUnique,
+		  bool indexunchanged,
 		  IndexInfo *indexInfo)
 {
 	GinState   *ginstate = (GinState *) indexInfo->ii_AmCache;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 25b42e38f2..09b585547b 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -156,6 +156,7 @@ bool
 gistinsert(Relation r, Datum *values, bool *isnull,
 		   ItemPointer ht_ctid, Relation heapRel,
 		   IndexUniqueCheck checkUnique,
+		   bool indexunchanged,
 		   IndexInfo *indexInfo)
 {
 	GISTSTATE  *giststate = (GISTSTATE *) indexInfo->ii_AmCache;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf446c..2ff72bd9f9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -247,6 +247,7 @@ bool
 hashinsert(Relation rel, Datum *values, bool *isnull,
 		   ItemPointer ht_ctid, Relation heapRel,
 		   IndexUniqueCheck checkUnique,
+		   bool indexunchanged,
 		   IndexInfo *indexInfo)
 {
 	Datum		index_values[1];
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1b2f70499e..082aa7b687 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2892,7 +2892,8 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode)
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Bitmapset **modified_attrs_hint)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -3758,10 +3759,15 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
+	/* Save for no logical changes hint when non-HOT update performed */
+	if (!use_hot_update && modified_attrs_hint)
+		*modified_attrs_hint = modified_attrs;
+	else
+		bms_free(modified_attrs);
+
 	bms_free(hot_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
-	bms_free(modified_attrs);
 	bms_free(interesting_attrs);
 
 	return TM_Ok;
@@ -3891,7 +3897,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, &lockmode);
+						 &tmfd, &lockmode, NULL);
 	switch (result)
 	{
 		case TM_SelfModified:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index a08c494034..0dab0ae634 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -314,7 +314,8 @@ static TM_Result
 heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
 					bool wait, TM_FailureData *tmfd,
-					LockTupleMode *lockmode, bool *update_indexes)
+					LockTupleMode *lockmode, bool *update_indexes,
+					Bitmapset **modified_attrs_hint)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -325,7 +326,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	tuple->t_tableOid = slot->tts_tableOid;
 
 	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode);
+						 tmfd, lockmode, modified_attrs_hint);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
@@ -1931,6 +1932,7 @@ heapam_index_validate_scan(Relation heapRelation,
 						 heapRelation,
 						 indexInfo->ii_Unique ?
 						 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+						 false,
 						 indexInfo);
 
 			state->tups_inserted += 1;
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 3fb8688f8f..0d7fc965e0 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -179,6 +179,7 @@ index_insert(Relation indexRelation,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
 			 IndexUniqueCheck checkUnique,
+			 bool indexunchanged,
 			 IndexInfo *indexInfo)
 {
 	RELATION_CHECKS;
@@ -191,7 +192,8 @@ index_insert(Relation indexRelation,
 
 	return indexRelation->rd_indam->aminsert(indexRelation, values, isnull,
 											 heap_t_ctid, heapRelation,
-											 checkUnique, indexInfo);
+											 checkUnique, indexunchanged,
+											 indexInfo);
 }
 
 /*
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c4f22f1c69..8eeb7bb64e 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -199,6 +199,7 @@ bool
 btinsert(Relation rel, Datum *values, bool *isnull,
 		 ItemPointer ht_ctid, Relation heapRel,
 		 IndexUniqueCheck checkUnique,
+		 bool indexunchanged,
 		 IndexInfo *indexInfo)
 {
 	bool		result;
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index e4508a2b92..8e55f3d711 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -207,6 +207,7 @@ bool
 spginsert(Relation index, Datum *values, bool *isnull,
 		  ItemPointer ht_ctid, Relation heapRel,
 		  IndexUniqueCheck checkUnique,
+		  bool indexunchanged,
 		  IndexInfo *indexInfo)
 {
 	SpGistState spgstate;
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e19bdd246a..35975efbc1 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -356,7 +356,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode, update_indexes, NULL);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 538f6a06b8..3e577dd183 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -162,6 +162,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 					 heapRelation,
 					 index->rd_index->indisunique ?
 					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+					 false,
 					 indexInfo);
 	}
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index fc19307bf2..79bd12b7de 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -175,7 +175,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 		 */
 		index_insert(indexRel, values, isnull, &checktid,
 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
-					 indexInfo);
+					 false, indexInfo);
 	}
 	else
 	{
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 115860a9d4..514d8f0cce 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2523,7 +2523,7 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo,
 			recheckIndexes =
 				ExecInsertIndexTuples(resultRelInfo,
 									  buffer->slots[i], estate, false, NULL,
-									  NIL);
+									  NIL, NULL);
 			ExecARInsertTriggers(estate, resultRelInfo,
 								 slots[i], recheckIndexes,
 								 cstate->transition_capture);
@@ -3268,7 +3268,8 @@ CopyFrom(CopyState cstate)
 																   estate,
 																   false,
 																   NULL,
-																   NIL);
+																   NIL,
+																   NULL);
 					}
 
 					/* AFTER ROW INSERT Triggers */
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index c6b5bcba7b..e6e8232cca 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -275,7 +275,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
-					  List *arbiterIndexes)
+					  List *arbiterIndexes,
+					  Bitmapset *modified_attrs_hint)
 {
 	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
@@ -287,6 +288,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	ExprContext *econtext;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
+	bool		indexunchanged;
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -389,6 +391,44 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		else
 			checkUnique = UNIQUE_CHECK_PARTIAL;
 
+		/*
+		 * We may have to hint to index AM that this is a logically unchanged
+		 * index tuple.  This happens when we're an UPDATE inserting a
+		 * duplicate tuple just to represent the successor version (though
+		 * only for the subset of indexes where that's actually true).
+		 */
+		if (!modified_attrs_hint)
+			indexunchanged = false; /* Probably from an INSERT */
+		else
+		{
+			indexunchanged = true;	/* Assume no logical changes for now */
+
+			for (int attr = 0; attr < indexInfo->ii_NumIndexAttrs; attr++)
+			{
+				int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
+
+				/*
+				 * XXX: With expression indexes we always ignore logical
+				 * changes affecting index (i.e. we don't bother noticing
+				 * changes affected only indexed expressions, passing down
+				 * "indexunchanged = true" hint in more cases as a result).
+				 *
+				 * It would make more sense if we made the opposite assumption
+				 * (though we probably shouldn't make any assumptions at all).
+				 */
+				if (keycol <= 0)
+					continue;
+
+				if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
+								  modified_attrs_hint))
+				{
+					/* Changed column -- don't hint for this index */
+					indexunchanged = false;
+					break;
+				}
+			}
+		}
+
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
 						 values,	/* array of index Datums */
@@ -396,6 +436,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 						 tupleid,	/* tid of heap tuple */
 						 heapRelation,	/* heap relation */
 						 checkUnique,	/* type of uniqueness check to do */
+						 indexunchanged,	/* UPDATE without logical change? */
 						 indexInfo);	/* index AM may need this */
 
 		/*
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 01d26881e7..e97d05b448 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -445,7 +445,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 		if (resultRelInfo->ri_NumIndices > 0)
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, false, NULL,
-												   NIL);
+												   NIL, NULL);
 
 		/* AFTER ROW INSERT Triggers */
 		ExecARInsertTriggers(estate, resultRelInfo, slot,
@@ -513,7 +513,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (resultRelInfo->ri_NumIndices > 0 && update_indexes)
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, false, NULL,
-												   NIL);
+												   NIL, NULL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7228..d76e371595 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -601,7 +601,7 @@ ExecInsert(ModifyTableState *mtstate,
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   &specConflict,
-												   arbiterIndexes);
+												   arbiterIndexes, NULL);
 
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
@@ -640,7 +640,7 @@ ExecInsert(ModifyTableState *mtstate,
 			if (resultRelInfo->ri_NumIndices > 0)
 				recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 													   slot, estate, false,
-													   NULL, NIL);
+													   NULL, NIL, NULL);
 		}
 	}
 
@@ -1219,6 +1219,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	TM_Result	result;
 	TM_FailureData tmfd;
 	List	   *recheckIndexes = NIL;
+	Bitmapset  *modified_attrs_hint = NULL;
 
 	/*
 	 * abort the operation if not running transactions
@@ -1382,7 +1383,8 @@ lreplace:;
 									estate->es_snapshot,
 									estate->es_crosscheck_snapshot,
 									true /* wait for commit */ ,
-									&tmfd, &lockmode, &update_indexes);
+									&tmfd, &lockmode, &update_indexes,
+									&modified_attrs_hint);
 
 		switch (result)
 		{
@@ -1513,9 +1515,13 @@ lreplace:;
 
 		/* insert index entries for tuple if necessary */
 		if (resultRelInfo->ri_NumIndices > 0 && update_indexes)
+		{
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, false,
-												   NULL, NIL);
+												   NULL, NIL,
+												   modified_attrs_hint);
+			bms_free(modified_attrs_hint);
+		}
 	}
 
 	if (canSetTag)
diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index 6d3fd5c432..be94f2eae8 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -198,6 +198,7 @@ bool
 blinsert(Relation index, Datum *values, bool *isnull,
 		 ItemPointer ht_ctid, Relation heapRel,
 		 IndexUniqueCheck checkUnique,
+		 bool indexunchanged,
 		 IndexInfo *indexInfo)
 {
 	BloomState	blstate;
diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h
index 23aa7ac441..7c7a0b47df 100644
--- a/contrib/bloom/bloom.h
+++ b/contrib/bloom/bloom.h
@@ -192,6 +192,7 @@ extern bool blvalidate(Oid opclassoid);
 extern bool blinsert(Relation index, Datum *values, bool *isnull,
 					 ItemPointer ht_ctid, Relation heapRel,
 					 IndexUniqueCheck checkUnique,
+					 bool indexunchanged,
 					 struct IndexInfo *indexInfo);
 extern IndexScanDesc blbeginscan(Relation r, int nkeys, int norderbys);
 extern int64 blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index 80473e0f1a..5f7c9a1d54 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -280,6 +280,7 @@ aminsert (Relation indexRelation,
           ItemPointer heap_tid,
           Relation heapRelation,
           IndexUniqueCheck checkUnique,
+          bool indexunchanged,
           IndexInfo *indexInfo);
 </programlisting>
    Insert a new tuple into an existing index.  The <literal>values</literal> and
@@ -295,6 +296,18 @@ aminsert (Relation indexRelation,
    look into the heap to verify tuple liveness).
   </para>
 
+  <para>
+   The <literal>indexunchanged</literal> boolean value gives a hint
+   about the nature of the tuple to be indexed. When it is true,
+   the tuple is a duplicate of some existing tuple in the index. The
+   new tuple is a logically unchanged successor MVCC tuple version.  This
+   happens when an <command>UPDATE</command> takes place that does not
+   modify any columns covered by the index, but nevertheless requires a
+   new version in the index. The index AM may use this hint to decide
+   to apply bottom-up index deletion in parts of the index where many
+   versions of the same logical row accumulate.
+  </para>
+
   <para>
    The function's Boolean result value is significant only when
    <literal>checkUnique</literal> is <literal>UNIQUE_CHECK_PARTIAL</literal>.
-- 
2.25.1

