diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 17afe1ea4c..28924f30ee 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -32,6 +32,7 @@
 #include "postgres.h"
 
 #include "access/bufmask.h"
+#include "access/amapi.h"
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
@@ -52,7 +53,11 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/index.h"
+#include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
+#include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "port/pg_bitutils.h"
@@ -3850,9 +3855,163 @@ l2:
 		 * Since the new tuple is going into the same page, we might be able
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
+		 *
+		 * If the basic check based on cached data says we can't use HOT and
+		 * there are partial indexes, we try doing a more expensive check
+		 * ignoring partial indexes with mismatching predicate.
 		 */
 		if (!bms_overlap(modified_attrs, hot_attrs))
 			use_hot_update = true;
+		else
+		{
+			bool has_partial_indexes = RelationHasPartialIndex(relation);
+
+			if (has_partial_indexes)
+			{
+				/* get HOT-blocking attributes for non-partial indexes */
+				hot_attrs = RelationGetIndexAttrBitmap2(relation, INDEX_ATTR_BITMAP_HOT_BLOCKING);
+
+				/* if non-partial indexes allow HOT, inspect partial indexes
+				 * with matching predicate */
+				if (!bms_overlap(modified_attrs, hot_attrs))
+				{
+					ListCell *lc;
+					List *indexoidlist = RelationGetIndexList(relation);
+
+					/* assume we can do HOT unless we find partial index that
+					 * prevents it */
+					use_hot_update = true;
+
+					foreach(lc, indexoidlist)
+					{
+						Oid			indexOid = lfirst_oid(lc);
+						Relation	indexDesc;
+						IndexInfo  *indexInfo;
+
+						/*
+						 * XXX There's a number of things we need to optimize here.
+						 * We probably already have indexes open, and we should not
+						 * build the IndexInfo over and over.
+						 */
+						Bitmapset  *hotblockingattrs = NULL;   /* columns with HOT blocking indexes */
+
+						indexDesc = index_open(indexOid, RowExclusiveLock);
+
+						if (indexDesc == NULL)
+							continue;
+
+						indexInfo = BuildIndexInfo(indexDesc);
+
+						/* If the index is marked as read-only, ignore it */
+						if (!indexInfo->ii_ReadyForInserts)
+							continue;
+
+						/* Check for partial index */
+						if (indexInfo->ii_Predicate != NIL)
+						{
+							ExprState  *predicate;
+							EState *estate = CreateExecutorState();
+							ExprContext *econtext;
+							TupleTableSlot *singleslot = NULL;
+							bool r;
+
+							estate = CreateExecutorState();
+							econtext = GetPerTupleExprContext(estate);
+
+							singleslot = MakeSingleTupleTableSlot(RelationGetDescr(relation),
+																  &TTSOpsHeapTuple);
+
+							ExecStoreHeapTuple(newtup, singleslot, false);
+							econtext->ecxt_scantuple = singleslot;
+
+							/*
+							 * If predicate state not set up yet, create it (in the estate's
+							 * per-query context)
+							 */
+							predicate = indexInfo->ii_PredicateState;
+							if (predicate == NULL)
+							{
+								predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+								indexInfo->ii_PredicateState = predicate;
+							}
+
+							/* Skip this index-update if the predicate isn't satisfied */
+							r = ExecQual(predicate, econtext);
+
+							ExecDropSingleTupleTableSlot(singleslot);
+
+							if (!r)
+								goto cleanup;
+						}
+						else
+							/* ignore non-partial indexes, we've already checked them */
+							goto cleanup;
+
+						/*
+						 * extract attnums for this index
+						 *
+						 * XXX This needs more work, depending on whether the attributes in
+						 * the predicate were updated (and maybe whether if the predicate
+						 * itself changed from false to true).
+						 *
+						 * Consider for example an index
+						 *
+						 * CREATE INDEX ON t (a) WHERE b <= 1;
+						 *
+						 * and an update that changes 'b' from 2 to 1. In that case we have
+						 * to update the index, disabling HOT. But if 'b' does not change,
+						 * or if it changes without affecting the predicate evaluation, we
+						 * may need to inspect the indexed columns.
+						 * 
+						 * For now we simply skip this, it needs more work.
+						 */
+						if (false)
+						{
+							int		i;
+							Node   *indexExpressions;
+
+							/* Collect simple attribute references */
+							for (i = 0; i < indexDesc->rd_index->indnatts; i++)
+							{
+								int			attrnum = indexDesc->rd_index->indkey.values[i];
+
+								/*
+								 * Since we have covering indexes with non-key columns, we must
+								 * handle them accurately here. non-key columns must be added into
+								 * indexattrs, since they are in index, and HOT-update shouldn't
+								 * miss them. Obviously, non-key columns couldn't be referenced by
+								 * foreign key or identity key. Hence we do not include them into
+								 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
+								 */
+								if (attrnum != 0)
+								{
+									if (indexDesc->rd_indam->amhotblocking)
+										hotblockingattrs = bms_add_member(hotblockingattrs,
+																	 attrnum - FirstLowInvalidHeapAttributeNumber);
+								}
+							}
+
+							/* Extract index expressions. */
+							indexExpressions = (Node *) RelationGetIndexExpressions2(relation);
+
+							/* Collect all attributes used in expressions, too */
+							if (indexDesc->rd_indam->amhotblocking)
+								pull_varattnos(indexExpressions, 1, &hotblockingattrs);
+						}
+
+						// ignore the inspection of attnums for now
+						// if (bms_overlap(modified_attrs, hotblockingattrs))
+							use_hot_update = false;
+
+cleanup:
+						index_close(indexDesc, RowExclusiveLock);
+
+						if (!use_hot_update)
+							break;
+					}
+				}
+			}
+		}
 	}
 	else
 	{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e1ea079e9e..a50f8dd075 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4958,6 +4958,42 @@ RelationGetIndexExpressions(Relation relation)
 	return result;
 }
 
+/*
+ * RelationGetIndexExpressions2 -- get the index expressions for an index
+ *
+ * Same as RelationGetIndexExpressions, but without const eval and funcoid.
+ * No caching.
+ */
+List *
+RelationGetIndexExpressions2(Relation relation)
+{
+	List	   *result;
+	Datum		exprsDatum;
+	bool		isnull;
+	char	   *exprsString;
+
+	/* Quick exit if there is nothing to do. */
+	if (relation->rd_indextuple == NULL ||
+		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
+		return NIL;
+
+	/*
+	 * We build the tree we intend to return in the caller's context. After
+	 * successfully completing the work, we copy it into the relcache entry.
+	 * This avoids problems if we get some sort of error partway through.
+	 */
+	exprsDatum = heap_getattr(relation->rd_indextuple,
+							  Anum_pg_index_indexprs,
+							  GetPgIndexDescriptor(),
+							  &isnull);
+	Assert(!isnull);
+	exprsString = TextDatumGetCString(exprsDatum);
+	result = (List *) stringToNode(exprsString);
+	pfree(exprsString);
+
+	return result;
+}
+
 /*
  * RelationGetDummyIndexExpressions -- get dummy expressions for an index
  *
@@ -5338,6 +5374,293 @@ restart:
 	}
 }
 
+/*
+ * Determine if the relation has any partial indexes.
+ *
+ * XXX Should be cached in relcache, probably.
+ */
+bool
+RelationHasPartialIndex(Relation relation)
+{
+	ListCell   *l;
+	List	   *indexoidlist,
+			   *newindexoidlist;
+	bool		result;
+
+	/* Fast path if definitely no indexes */
+	if (!RelationGetForm(relation)->relhasindex)
+		return NULL;
+
+	/*
+	 * Get cached list of index OIDs. If we have to start over, we do so here.
+	 */
+restart:
+	indexoidlist = RelationGetIndexList(relation);
+
+	/* Fall out if no indexes (but relhasindex was set) */
+	if (indexoidlist == NIL)
+		return NULL;
+
+	/* Assume there are no partial indexes. */
+	result = false;
+
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	indexDesc;
+		bool		isnull;
+
+		indexDesc = index_open(indexOid, AccessShareLock);
+
+		/*
+		 * Extract index predicate.
+		 *
+		 * Note: Don't use RelationGetIndexPredicate(), because that might
+		 * run constant expressions evaluation, which needs a snapshot, which
+		 * we might not have here.  (Also, it's probably more sound to
+		 * collect the bitmaps before any transformations that might
+		 * eliminate columns, but the practical impact of this is limited.)
+		 */
+		heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indpred,
+					 GetPgIndexDescriptor(), &isnull);
+		if (!isnull)
+			result = true;
+
+		index_close(indexDesc, AccessShareLock);
+
+		/* We found at least one partial index. */
+		if (result)
+			break;
+	}
+
+	/*
+	 * During one of the index_opens in the above loop, we might have received
+	 * a relcache flush event on this relcache entry, which might have been
+	 * signaling a change in the rel's index list.  If so, we'd better start
+	 * over to ensure we deliver up-to-date attribute bitmaps.
+	 */
+	newindexoidlist = RelationGetIndexList(relation);
+	if (equal(indexoidlist, newindexoidlist))
+	{
+		/* Still the same index set, so proceed */
+		list_free(newindexoidlist);
+		list_free(indexoidlist);
+	}
+	else
+	{
+		/* Gotta do it over ... might as well not leak memory */
+		list_free(newindexoidlist);
+		list_free(indexoidlist);
+
+		goto restart;
+	}
+
+	return result;
+}
+
+/*
+ * same as RelationGetIndexAttrBitmap, but skips partial indexes and
+ * does not use/update relcache
+ *
+ * XXX This leaks the lists, we should release them at the end.
+ */
+Bitmapset *
+RelationGetIndexAttrBitmap2(Relation relation, IndexAttrBitmapKind attrKind)
+{
+	Bitmapset  *uindexattrs;	/* columns in unique indexes */
+	Bitmapset  *pkindexattrs;	/* columns in the primary index */
+	Bitmapset  *idindexattrs;	/* columns in the replica identity */
+	Bitmapset  *hotblockingattrs;   /* columns with HOT blocking indexes */
+	List	   *indexoidlist;
+	List	   *newindexoidlist;
+	Oid			relpkindex;
+	Oid			relreplindex;
+	ListCell   *l;
+
+	/* Fast path if definitely no indexes */
+	if (!RelationGetForm(relation)->relhasindex)
+		return NULL;
+
+	/*
+	 * Get cached list of index OIDs. If we have to start over, we do so here.
+	 */
+restart:
+	indexoidlist = RelationGetIndexList(relation);
+
+	/* Fall out if no indexes (but relhasindex was set) */
+	if (indexoidlist == NIL)
+		return NULL;
+
+	/*
+	 * Copy the rd_pkindex and rd_replidindex values computed by
+	 * RelationGetIndexList before proceeding.  This is needed because a
+	 * relcache flush could occur inside index_open below, resetting the
+	 * fields managed by RelationGetIndexList.  We need to do the work with
+	 * stable values of these fields.
+	 */
+	relpkindex = relation->rd_pkindex;
+	relreplindex = relation->rd_replidindex;
+
+	/*
+	 * For each index, add referenced attributes to appropriate bitmaps.
+	 *
+	 * Note: we consider all indexes returned by RelationGetIndexList, even if
+	 * they are not indisready or indisvalid.  This is important because an
+	 * index for which CREATE INDEX CONCURRENTLY has just started must be
+	 * included in HOT-safety decisions (see README.HOT).  If a DROP INDEX
+	 * CONCURRENTLY is far enough along that we should ignore the index, it
+	 * won't be returned at all by RelationGetIndexList.
+	 */
+	uindexattrs = NULL;
+	pkindexattrs = NULL;
+	idindexattrs = NULL;
+	hotblockingattrs = NULL;
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	indexDesc;
+		Datum		datum;
+		bool		isnull;
+		Node	   *indexExpressions;
+		Node	   *indexPredicate;
+		int			i;
+		bool		isKey;		/* candidate key */
+		bool		isPK;		/* primary key */
+		bool		isIDKey;	/* replica identity index */
+
+		indexDesc = index_open(indexOid, AccessShareLock);
+
+		/*
+		 * Extract index expressions and index predicate.  Note: Don't use
+		 * RelationGetIndexExpressions()/RelationGetIndexPredicate(), because
+		 * those might run constant expressions evaluation, which needs a
+		 * snapshot, which we might not have here.  (Also, it's probably more
+		 * sound to collect the bitmaps before any transformations that might
+		 * eliminate columns, but the practical impact of this is limited.)
+		 */
+
+		datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indexprs,
+							 GetPgIndexDescriptor(), &isnull);
+		if (!isnull)
+			indexExpressions = stringToNode(TextDatumGetCString(datum));
+		else
+			indexExpressions = NULL;
+
+		datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indpred,
+							 GetPgIndexDescriptor(), &isnull);
+		if (!isnull)
+			indexPredicate = stringToNode(TextDatumGetCString(datum));
+		else
+			indexPredicate = NULL;
+
+		/* skip partial indexes */
+		if (indexPredicate)
+			goto skip_index;
+
+		/* Can this index be referenced by a foreign key? */
+		isKey = indexDesc->rd_index->indisunique &&
+			indexExpressions == NULL &&
+			indexPredicate == NULL;
+
+		/* Is this a primary key? */
+		isPK = (indexOid == relpkindex);
+
+		/* Is this index the configured (or default) replica identity? */
+		isIDKey = (indexOid == relreplindex);
+
+		/* Collect simple attribute references */
+		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
+		{
+			int			attrnum = indexDesc->rd_index->indkey.values[i];
+
+			/*
+			 * Since we have covering indexes with non-key columns, we must
+			 * handle them accurately here. non-key columns must be added into
+			 * indexattrs, since they are in index, and HOT-update shouldn't
+			 * miss them. Obviously, non-key columns couldn't be referenced by
+			 * foreign key or identity key. Hence we do not include them into
+			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
+			 */
+			if (attrnum != 0)
+			{
+				if (indexDesc->rd_indam->amhotblocking)
+					hotblockingattrs = bms_add_member(hotblockingattrs,
+												 attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isKey && i < indexDesc->rd_index->indnkeyatts)
+					uindexattrs = bms_add_member(uindexattrs,
+												 attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isPK && i < indexDesc->rd_index->indnkeyatts)
+					pkindexattrs = bms_add_member(pkindexattrs,
+												  attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
+					idindexattrs = bms_add_member(idindexattrs,
+												  attrnum - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+
+		/* Collect all attributes used in expressions, too */
+		if (indexDesc->rd_indam->amhotblocking)
+			pull_varattnos(indexExpressions, 1, &hotblockingattrs);
+
+		/*
+		 * Collect all attributes in the index predicate, too. We have to ignore
+		 * amhotblocking flag, because the row might become indexable, in which
+		 * case we have to add it to the index.
+		 */
+		pull_varattnos(indexPredicate, 1, &hotblockingattrs);
+
+skip_index:
+		index_close(indexDesc, AccessShareLock);
+	}
+
+	/*
+	 * During one of the index_opens in the above loop, we might have received
+	 * a relcache flush event on this relcache entry, which might have been
+	 * signaling a change in the rel's index list.  If so, we'd better start
+	 * over to ensure we deliver up-to-date attribute bitmaps.
+	 */
+	newindexoidlist = RelationGetIndexList(relation);
+	if (equal(indexoidlist, newindexoidlist) &&
+		relpkindex == relation->rd_pkindex &&
+		relreplindex == relation->rd_replidindex)
+	{
+		/* Still the same index set, so proceed */
+		list_free(newindexoidlist);
+		list_free(indexoidlist);
+	}
+	else
+	{
+		/* Gotta do it over ... might as well not leak memory */
+		list_free(newindexoidlist);
+		list_free(indexoidlist);
+		bms_free(uindexattrs);
+		bms_free(pkindexattrs);
+		bms_free(idindexattrs);
+		bms_free(hotblockingattrs);
+
+		goto restart;
+	}
+
+	/* We return our original working copy for caller to play with */
+	switch (attrKind)
+	{
+		case INDEX_ATTR_BITMAP_KEY:
+			return uindexattrs;
+		case INDEX_ATTR_BITMAP_PRIMARY_KEY:
+			return pkindexattrs;
+		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
+			return idindexattrs;
+		case INDEX_ATTR_BITMAP_HOT_BLOCKING:
+			return hotblockingattrs;
+		default:
+			elog(ERROR, "unknown attrKind %u", attrKind);
+			return NULL;
+	}
+}
+
 /*
  * RelationGetIdentityKeyBitmap -- get a bitmap of replica identity attribute
  * numbers
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 82316bba54..352532d430 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -64,6 +64,13 @@ typedef enum IndexAttrBitmapKind
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
 											 IndexAttrBitmapKind attrKind);
 
+extern Bitmapset *RelationGetIndexAttrBitmap2(Relation relation,
+											  IndexAttrBitmapKind attrKind);
+
+extern bool RelationHasPartialIndex(Relation relation);
+
+extern List *RelationGetIndexExpressions2(Relation relation);
+
 extern Bitmapset *RelationGetIdentityKeyBitmap(Relation relation);
 
 extern void RelationGetExclusionInfo(Relation indexRelation,
