Expanding HOT updates for expression and partial indexes

Started by Burd, Greg11 months ago41 messages
#1Burd, Greg
gregburd@amazon.com
2 attachment(s)

Attached find a patch that expands the cases where heap-only tuple (HOT) updates are possible without changing the basic semantics of HOT. This is accomplished by examining expression indexes for changes to determine if indexes require updating or not. A similar approach is taken for partial indexes, the predicate is evaluated and, in some cases, HOT updates are allowed. Even with this patch if any index is changed, all indexes are updated. Only in cases where none are modified will this patch allow the HOT path. Previously, an expression index on a modified column would disqualify the update from the HOT path in the heap access manager. This patch is functional, includes new tests, and passes check-world however I’m sure it will require more work after community review.

Why is this important? A growing number of Postgres users work with JSONB data. Indexes on fields within JSONB columns use expressions preventing all updates on data within JSONB columns from the HOT path. This is unnecessary and a major drawback when using Postgres.

This is not a new idea; indeed, this patch grew out of Surjective functional indexes [1]/messages/by-id/4d9928ee-a9e6-15f9-9c82-5981f13ffca6@postgrespro.ru which was applied [2]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8 and then reverted [3]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256 after it was discovered that it caused a bug and had other quality issues. This patch is a new approach that hopefully address the identified bug and most of the other concerns raised in that and other email threads on the subject. I’m also aware of PHOT [4]/messages/by-id/2ECBBCA0-4D8D-4841-8872-4A5BBDC063D2@amazon.com and WARM [5]/messages/by-id/CABOikdMop5Rb_RnS2xFdAXMZGSqcJ-P-BY2ruMd+buUkJ4iDPw@mail.gmail.com which allow for updating some, but not all indexes while remaining on the HOT update path, this patch does not attempt to accomplish that.

Attached you’ll find two slightly different approaches. The first (v3) patch is slightly less intrusive than the second (v4), but both apply to master (59d6c03956193f622c069a4ab985bade27384ac4). Tests have been added (heap_hot_updates.sql) that exercise this new feature set. Of the two patches I personally prefer v4 as it cleans up the summarizing index logic and removes TU_UpdateIndexes. This opens the door to future improvements by providing a way to pass a bitmap of modified indexes along to be addressed by something similar to the PHOT/WARM logic.

I have a few concerns with the patch, things I’d greatly appreciate your thoughts on:

First, I pass an EState along the update path to enable running the checks in heapam, this works but leaves me feeling as if I violated separation of concerns. If there is a better way to do this let me know or if you think the cost of creating one in the execIndexing.c ExecIndexesRequiringUpdates() is okay that’s another possibility.

Second, I’m sure that creating the rd_indexinfolist should be improved/changed and likely cached via relcache.c as this is likely part of the performance overhead mentioned below.

Third, there is overhead to this patch, it is no longer a single simple bitmap test to choose HOT or not in heap_update(). Sometimes this patch will perform expensive additional checks and ultimately not go down the HOT path, new overhead with no benefit. Some expressions are more expensive than others to evaluate, there is no logic to adjust for that. The Surjective patch/email thread had quite a bit of discussion on this without resolution. I’ve chosen to add a GUC that optionally avoids the expression evaluation. I’m open to ideas here as well, addition of another GUC or removal of the one I’ve added. I’ve tried to avoid rechecking indexes for changes when possible.

Fourth, I’d like to know which version the community prefers (v3 or v4). I think v4 moves the code in a direction that is cleaner overall, but you may disagree. I realize that the way I use the modified_indexes bitmapset is a tad overloaded (NULL means all indexes should be updated, otherwise only update the indexes in the set which may be all/some/none of the indexes) and that may violate the principal of least surprise but I feel that it is better than the TU_UpdateIndexes enum in the code today.

I’ve run two performance tests against this; a very synthetic workload that updates only non-indexed fields in a JSONB document that is small enough not to be TOASTed, and one that measures TPC-C-like workload using a MongoDB API mapped into JSONB.

The synthetic workload randomly updated non-indexed fields within the JSONB documents as fast as possible from 50 client connections. The test started pre-loaded with 10,000,000 documents within a single column with 50 expression indexes (BTREE) into fields in those documents. This test showed a dramatic increase in throughput (28-110%), reduction in per-operation latency (22-52%), and lower storage requirements all while CPU remained within 1% of the unpatched server. The tests ran for 2hrs during which time we observed about a 20-30% reduction in IOPs. On the unpatched server there were no HOT updates, on the patched server with default fillfactor HOT updates made up at least 30% and at times much more as the random-access pattern and pruneheap opened space on pages for HOT updates.

The TPC-C-like workload [7]https://github.com/mongodb-labs/py-tpcc ran [8]python3 tpcc.py —no-load —duration 3600 —warehouses 2000 —clients 50 —stop-on-error —config gpure.config mongodb first with —no-execute then [8]python3 tpcc.py —no-load —duration 3600 —warehouses 2000 —clients 50 —stop-on-error —config gpure.config mongodb with —no-load. This test showed HOT updates for CUSTOMER (7%), DISTRICT (48%), and WAREHOUSE (99%) but zero for STOCK and ORDERS. When compared to a non-patched server the performance with this patch was 7.8% slower than without. This was clearly not the result I expected. I believe that the lower performance may in part be due to how I build and maintain the rd_indexinfolist and the overhead of executing expressions on indexes repeatedly only to find that the update still doesn’t qualify for the HOT path. I’d be very happy to hear thoughts on how I might reduce this gap if you have suggestions.

I hope to further develop this patch into a final form acceptable to this community.

best regards,
-greg

[1]: /messages/by-id/4d9928ee-a9e6-15f9-9c82-5981f13ffca6@postgrespro.ru
[2]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
[3]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256
[4]: /messages/by-id/2ECBBCA0-4D8D-4841-8872-4A5BBDC063D2@amazon.com
[5]: /messages/by-id/CABOikdMop5Rb_RnS2xFdAXMZGSqcJ-P-BY2ruMd+buUkJ4iDPw@mail.gmail.com
[6]: /messages/by-id/CABOikdMNy6yowA+wTGK9RVd8iw+CzqHeQSGpW7Yka_4RSZ_LOQ@mail.gmail.com
[7]: https://github.com/mongodb-labs/py-tpcc
[8]: python3 tpcc.py —no-load —duration 3600 —warehouses 2000 —clients 50 —stop-on-error —config gpure.config mongodb

Attachments:

v3-0001-Expand-HOT-update-path-to-include-expression-and-.patchapplication/octet-stream; name=v3-0001-Expand-HOT-update-path-to-include-expression-and-.patchDownload
From ef8afdfe5c7d2f8e23cf8a20773eae35a9823d73 Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v3] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/config.sgml                      |  16 +
 src/backend/access/heap/heapam.c              |  42 ++-
 src/backend/access/heap/heapam_handler.c      |   6 +-
 src/backend/access/table/tableam.c            |   2 +-
 src/backend/catalog/index.c                   |  15 +
 src/backend/executor/execIndexing.c           | 226 +++++++++++++
 src/backend/executor/nodeModifyTable.c        |   3 +-
 src/backend/utils/misc/guc_tables.c           |  13 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/access/heapam.h                   |   3 +-
 src/include/access/tableam.h                  |  10 +-
 src/include/executor/executor.h               |   6 +
 src/include/nodes/execnodes.h                 |   8 +
 src/include/optimizer/optimizer.h             |   1 +
 src/include/utils/rel.h                       |   4 +
 src/include/utils/relcache.h                  |   1 +
 .../regress/expected/heap_hot_updates.out     | 318 ++++++++++++++++++
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 208 ++++++++++++
 20 files changed, 867 insertions(+), 24 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a782f10998..5b756e7754 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5242,6 +5242,22 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-expression-checks" xreflabel="enable_expression_checks">
+      <term><varname>enable_expression_checks</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_expression_checks</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Enables or disables the ability to determine if indexes with
+        expressions partial indexes have changed allowing for the heap-only
+        tuple (HOT) optimzation on update when using the heap access method.
+        The default is <literal>on</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-enable-gathermerge" xreflabel="enable_gathermerge">
       <term><varname>enable_gathermerge</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ea0a12b39a..35e47191ea 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3164,7 +3164,7 @@ TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -3928,25 +3928,41 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		bool		only_summarizing = false;
+		Bitmapset  *modified_indexes;
+
 		/*
 		 * 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.
+		 * to do a HOT update.
 		 */
 		if (!bms_overlap(modified_attrs, hot_attrs))
-		{
 			use_hot_update = true;
-
+		else
+		{
 			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
+			 * Some of the columns used in HOT-blocking indexes were updated,
+			 * but there are still cases where we can do a HOT update.  Check
+			 * if the columns that are used in the HOT-blocking indexes were
+			 * updated, and if not, we can do a HOT update.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
-				summarized_update = true;
+			modified_indexes =
+				ExecIndexesRequiringUpdates(relation, modified_attrs,
+											estate, &oldtup, newtup,
+											&only_summarizing);
+			if (bms_is_empty(modified_indexes) || only_summarizing)
+				use_hot_update = true;
+			bms_free(modified_indexes);
 		}
+
+		/*
+		 * If none of the columns that are used in hot-blocking indexes were
+		 * updated, we can apply HOT, but we do still need to check if we need
+		 * to update the summarizing indexes, and update those indexes if the
+		 * columns were updated, or we may fail to detect e.g. value bound
+		 * changes in BRIN minmax indexes.
+		 */
+		if (use_hot_update && bms_overlap(modified_attrs, sum_attrs))
+			summarized_update = true;
 	}
 	else
 	{
@@ -4413,7 +4429,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, update_indexes);
+						 &tmfd, &lockmode, update_indexes, 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 a4003cf59e..836aac364e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -312,8 +312,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+					TU_UpdateIndexes *update_indexes, EState *estate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -324,7 +324,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, update_indexes);
+						 tmfd, lockmode, update_indexes, estate);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e18a8f8250..9feca8a296 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -345,7 +345,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/index.c b/src/backend/catalog/index.c
index 7377912b41..b0e670c1f7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2455,7 +2455,20 @@ BuildIndexInfo(Relation index)
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
+	{
 		ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   indexStruct->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* collect attributes used in the expression, if one is present */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate, if one is present */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
@@ -2466,6 +2479,8 @@ BuildIndexInfo(Relation index)
 								 &ii->ii_ExclusionStrats);
 	}
 
+	ii->ii_OpClassDataTypes = index->rd_opcintype;
+
 	return ii;
 }
 
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 7c87f012c3..fa3dec7377 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -117,6 +117,8 @@
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -146,6 +148,11 @@ static bool index_expression_changed_walker(Node *node,
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
+/*
+ * GUC parameters
+ */
+bool		enable_expression_checks = true;
+
 /* ----------------------------------------------------------------
  *		ExecOpenIndices
  *
@@ -168,6 +175,7 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 	IndexInfo **indexInfoArray;
 
 	resultRelInfo->ri_NumIndices = 0;
+	resultRelation->rd_indexinfolist = NIL;
 
 	/* fast path if no indexes */
 	if (!RelationGetForm(resultRelation)->relhasindex)
@@ -210,6 +218,10 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		/* extract index key information from the index's pg_index info */
 		ii = BuildIndexInfo(indexDesc);
 
+		/* used when determining if indexed values changed during update */
+		resultRelation->rd_indexinfolist =
+			lappend(resultRelation->rd_indexinfolist, ii);
+
 		/*
 		 * If the indexes are to be used for speculative insertion or conflict
 		 * detection in logical replication, add extra information required by
@@ -1166,3 +1178,217 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine which indexes must be invoked during ExecIndexInsertTuple().
+ *
+ * That will include: any index on a column where there is an attribute that was
+ * modified, any expression index where the expression's value updated, any
+ * index used in a constraint, and any summarizing index.
+ */
+Bitmapset *
+ExecIndexesRequiringUpdates(Relation relation,
+							Bitmapset *modified_attrs,
+							EState *estate,
+							HeapTuple old_tuple,
+							HeapTuple new_tuple,
+							bool *only_summarizing)
+{
+	ListCell   *lc;
+	List	   *indexinfolist = relation->rd_indexinfolist;
+	ExprContext *econtext = NULL;
+	int			num_summarizing = 0;
+	TupleDesc	tupledesc;
+	TupleTableSlot *old_tts,
+			   *new_tts;
+	Bitmapset  *result = NULL;
+
+	/*
+	 * Examine each index on this relation relative to the changes between old
+	 * and new tuples.
+	 */
+	foreach(lc, indexinfolist)
+	{
+		IndexInfo  *indexInfo = (IndexInfo *) lfirst(lc);
+
+		/*
+		 * Summarizing indexes don't prevent HOT updates, but do require that
+		 * they are updated so we include it in the set.
+		 */
+		if (indexInfo->ii_Summarizing)
+		{
+			num_summarizing++;
+			result = bms_add_member(result, foreach_current_index(lc));
+			continue;
+		}
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (enable_expression_checks && estate != NULL &&
+			bms_overlap(indexInfo->ii_PredicateAttrs, modified_attrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = bms_add_member(result, foreach_current_index(lc));
+				continue;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modified_attrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			bool		changed = false;
+
+			/*
+			 * Assume the index is changed when we don't have an estate
+			 * context to use or the GUC is disabled.
+			 */
+			if (!enable_expression_checks || estate == NULL)
+			{
+				result = bms_add_member(result, foreach_current_index(lc));
+				continue;
+			}
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			indexInfo->ii_ExpressionsState = NIL;
+
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+
+			for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+			{
+				if (old_isnull[i] != new_isnull[i])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[i])
+				{
+					int16		elmlen;
+					bool		elmbyval;
+					Oid			opcintyp = indexInfo->ii_OpClassDataTypes[i];
+
+					get_typlenbyval(opcintyp, &elmlen, &elmbyval);
+					if (!datum_image_eq(old_values[i], new_values[i],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			if (changed)
+				result = bms_add_member(result, foreach_current_index(lc));
+
+			/* Shortcut index_unchanged_by_update(), we know the answer. */
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+			continue;
+		}
+
+		/*
+		 * If the index references modified attributes then it needs to be
+		 * updated.
+		 */
+		if (bms_overlap(indexInfo->ii_IndexAttrs, modified_attrs))
+			result = bms_add_member(result, foreach_current_index(lc));
+	}
+
+	if (econtext)
+	{
+		ExecDropSingleTupleTableSlot(old_tts);
+		ExecDropSingleTupleTableSlot(new_tts);
+	}
+
+	*only_summarizing = (list_length(indexinfolist) > 0 &&
+						 num_summarizing == list_length(indexinfolist));
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index bc82e035ba..89218019c3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2266,7 +2266,8 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&updateCxt->updateIndexes,
+								estate);
 
 	return result;
 }
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 38cb9e970d..1f3eb0caa1 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1007,6 +1007,19 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_expression_checks", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables the ability to examine expression and partial indexes "
+						 "for changes during updates."),
+			gettext_noop("Evaluates the expressions on the index to determine if "
+						 "the indexed values changed.  This allows for heap-only "
+						 "tuple (HOT) updates when indexes have not changed."),
+			GUC_EXPLAIN
+		},
+		&enable_expression_checks,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 079efa1baa..61ab1bb59b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -408,6 +408,7 @@
 #enable_tidscan = on
 #enable_group_by_reordering = on
 #enable_distinct_reordering = on
+#enable_expression_checks = on
 
 # - Planner Cost Constants -
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f..e12da1934e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -339,7 +340,7 @@ extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 TU_UpdateIndexes *update_indexes, struct EState *estate);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 131c050c15..62ed6592b8 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -38,6 +38,7 @@ struct IndexInfo;
 struct SampleScanState;
 struct VacuumParams;
 struct ValidateIndexState;
+struct EState;
 
 /*
  * Bitmask values for the flags argument to the scan_begin callback.
@@ -550,7 +551,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 TU_UpdateIndexes *update_indexes,
+								 struct EState *estate);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1541,12 +1543,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 update_indexes, estate);
 }
 
 /*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c7db6defd3..f9c7a545af 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -660,6 +660,12 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern Bitmapset *ExecIndexesRequiringUpdates(Relation relation,
+											  Bitmapset *modified_attrs,
+											  EState *estate,
+											  HeapTuple old_tuple,
+											  HeapTuple new_tuple,
+											  bool *only_summarizing);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d0f2dca592..9fe6ca6ca5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -159,12 +159,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -183,6 +186,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -194,10 +198,13 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
@@ -217,6 +224,7 @@ typedef struct IndexInfo
 	int			ii_ParallelWorkers;
 	Oid			ii_Am;
 	void	   *ii_AmCache;
+	Oid		   *ii_OpClassDataTypes;
 	MemoryContext ii_Context;
 } IndexInfo;
 
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index bcf8ed645c..f98774e940 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -112,6 +112,7 @@ typedef enum
 extern PGDLLIMPORT int debug_parallel_query;
 extern PGDLLIMPORT bool parallel_leader_participation;
 extern PGDLLIMPORT bool enable_distinct_reordering;
+extern PGDLLIMPORT bool enable_expression_checks;
 
 extern struct PlannedStmt *planner(Query *parse, const char *query_string,
 								   int cursorOptions,
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 33d1e4a4e2..19b7e63b86 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -154,6 +154,10 @@ typedef struct RelationData
 	bool		rd_ispkdeferrable;	/* is rd_pkindex a deferrable PK? */
 	Oid			rd_replidindex; /* OID of replica identity index, if any */
 
+	/* list of IndexInfo for this relation */
+	List	   *rd_indexinfolist;	/* an ordered list of IndexInfo for
+									 * indexes on relation */
+
 	/* data managed by RelationGetStatExtList: */
 	List	   *rd_statlist;	/* list of OIDs of extended stats */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339..0cc28cb97e 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -63,6 +63,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 0000000000..177c6109f5
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,318 @@
+SHOW enable_expression_checks;
+ enable_expression_checks 
+--------------------------
+ on
+(1 row)
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable the GUC that allows us to skip expression checks.
+SET enable_expression_checks = 'off';
+-- The indexed attribute "name" with value "john" is unchanged, don't expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable the GUC so that we don't skip expression checks.
+SET enable_expression_checks = 'on';
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column _v and two indexes.  They are both expression
+-- indexes referencing the same column attribute (_v) but one is a partial
+-- index.
+CREATE TABLE public.ex (_v JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (_v) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (_v) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((_v->>'a'));
+CREATE INDEX idx_ex_b ON ex ((_v->>'b')) WHERE (_v->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 1) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+ _v 
+----
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 10) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+        _v         
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 1, 'b', 10) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 12) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+        _v         
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 1) WHERE (_v->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+ _v 
+----
+(0 rows)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation.
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            5 |                1 |                      20.00 |             0 |           0 |                            
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+DROP TABLE public.ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 rows, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 row, one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 352abc0bd4..56451bf783 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -151,6 +151,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_async_append            | on
  enable_bitmapscan              | on
  enable_distinct_reordering     | on
+ enable_expression_checks       | on
  enable_gathermerge             | on
  enable_group_by_reordering     | on
  enable_hashagg                 | on
@@ -171,7 +172,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(23 rows)
+(24 rows)
 
 -- There are always wait event descriptions for various types.  InjectionPoint
 -- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1edd9e45eb..34232fd337 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 0000000000..22c6eafeab
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,208 @@
+SHOW enable_expression_checks;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable the GUC that allows us to skip expression checks.
+SET enable_expression_checks = 'off';
+
+-- The indexed attribute "name" with value "john" is unchanged, don't expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable the GUC so that we don't skip expression checks.
+SET enable_expression_checks = 'on';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row, no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column _v and two indexes.  They are both expression
+-- indexes referencing the same column attribute (_v) but one is a partial
+-- index.
+CREATE TABLE public.ex (_v JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (_v) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (_v) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((_v->>'a'));
+CREATE INDEX idx_ex_b ON ex ((_v->>'b')) WHERE (_v->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 1) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 10) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 1, 'b', 10) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 12) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 1) WHERE (_v->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation.
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+DROP TABLE public.ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 rows, no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, a single new HOT update
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 row, one new HOT update
+
+DROP TABLE events;
-- 
2.42.0

v4-0001-Expand-HOT-update-path-to-include-expression-and-.patchapplication/octet-stream; name=v4-0001-Expand-HOT-update-path-to-include-expression-and-.patchDownload
From 5731abccd65e002c3a3ad47a40d982b7eb3bd209 Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v4] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/config.sgml                      |  16 +
 src/backend/access/heap/heapam.c              |  82 ++---
 src/backend/access/heap/heapam_handler.c      |  27 +-
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/catalog/index.c                   |  15 +
 src/backend/catalog/indexing.c                |  51 +--
 src/backend/executor/execIndexing.c           | 239 ++++++++++++-
 src/backend/executor/execReplication.c        |  10 +-
 src/backend/executor/nodeModifyTable.c        |  13 +-
 src/backend/utils/cache/relcache.c            |  34 +-
 src/backend/utils/misc/guc_tables.c           |  13 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/access/heapam.h                   |   5 +-
 src/include/access/tableam.h                  |  28 +-
 src/include/executor/executor.h               |   8 +-
 src/include/nodes/execnodes.h                 |   8 +
 src/include/optimizer/optimizer.h             |   1 +
 src/include/utils/rel.h                       |   5 +-
 src/include/utils/relcache.h                  |   1 +
 .../regress/expected/heap_hot_updates.out     | 318 ++++++++++++++++++
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 208 ++++++++++++
 23 files changed, 921 insertions(+), 175 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a782f10998..5b756e7754 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5242,6 +5242,22 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-expression-checks" xreflabel="enable_expression_checks">
+      <term><varname>enable_expression_checks</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_expression_checks</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Enables or disables the ability to determine if indexes with
+        expressions partial indexes have changed allowing for the heap-only
+        tuple (HOT) optimzation on update when using the heap access method.
+        The default is <literal>on</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-enable-gathermerge" xreflabel="enable_gathermerge">
       <term><varname>enable_gathermerge</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ea0a12b39a..0124ba4e5b 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3164,12 +3164,11 @@ TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			Bitmapset **modified_indexes, struct EState *estate)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3192,7 +3191,6 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		use_hot_update = false;
-	bool		summarized_update = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
 	bool		all_visible_cleared_new = false;
@@ -3244,14 +3242,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 */
 	hot_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
 	interesting_attrs = NULL;
 	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
 
@@ -3307,10 +3302,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		tmfd->ctid = *otid;
 		tmfd->xmax = InvalidTransactionId;
 		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
 
 		bms_free(hot_attrs);
-		bms_free(sum_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3608,10 +3601,8 @@ l2:
 			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
-		*update_indexes = TU_None;
 
 		bms_free(hot_attrs);
-		bms_free(sum_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -3926,30 +3917,57 @@ l2:
 	 * one pin is held.
 	 */
 
-	if (newbuf == buffer)
+	if (modified_indexes && newbuf == buffer)
 	{
+		bool		only_summarizing = false;
+		int			num_indexes = list_length(relation->rd_indexinfolist);
+
 		/*
 		 * 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.
+		 * to do a HOT update.
 		 */
 		if (!bms_overlap(modified_attrs, hot_attrs))
 		{
+			/*
+			 * If no attributes were modified, we can be on the HOT path but
+			 * we need to signal with a non-NULL modified_indexes bitmap that
+			 * none of the indexes need to be updated, so add a non-existent
+			 * index which won't match later when creating index entries.
+			 */
+			*modified_indexes = bms_add_member(*modified_indexes, num_indexes);
 			use_hot_update = true;
-
+		}
+		else
+		{
 			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
+			 * Some of the columns used in HOT-blocking indexes were updated,
+			 * but there are still cases where we can do a HOT update.  Check
+			 * if the columns that are used in the HOT-blocking indexes were
+			 * updated, and if not, we can do a HOT update.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
-				summarized_update = true;
+			*modified_indexes =
+				ExecIndexesRequiringUpdates(relation, modified_attrs,
+											estate, &oldtup, newtup,
+											&only_summarizing);
+			if (bms_is_empty(*modified_indexes) || only_summarizing)
+			{
+				/* See earlier comment... */
+				*modified_indexes = bms_add_member(*modified_indexes, num_indexes);
+				use_hot_update = true;
+			}
+			else
+			{
+				bms_free(*modified_indexes);
+				*modified_indexes = NULL;
+			}
 		}
 	}
 	else
 	{
+		/* Signal that we're not on the HOT path by setting this to NULL */
+		if (modified_indexes)
+			*modified_indexes = NULL;
+
 		/* Set a hint that the old page could use prune/defrag */
 		PageSetFull(page);
 	}
@@ -4106,27 +4124,10 @@ l2:
 		heap_freetuple(heaptup);
 	}
 
-	/*
-	 * If it is a HOT update, the update may still need to update summarized
-	 * indexes, lest we fail to update those summaries and get incorrect
-	 * results (for example, minmax bounds of the block may change with this
-	 * update).
-	 */
-	if (use_hot_update)
-	{
-		if (summarized_update)
-			*update_indexes = TU_Summarizing;
-		else
-			*update_indexes = TU_None;
-	}
-	else
-		*update_indexes = TU_All;
-
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
-	bms_free(sum_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4403,8 +4404,7 @@ HeapDetermineColumnsInfo(Relation relation,
  * via ereport().
  */
 void
-simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-				   TU_UpdateIndexes *update_indexes)
+simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
@@ -4413,7 +4413,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, update_indexes);
+						 &tmfd, &lockmode, NULL, 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 a4003cf59e..e2ac9f486e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -312,8 +312,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+					Bitmapset **modified_indexes, EState *estate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -324,30 +324,9 @@ 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, update_indexes);
+						 tmfd, lockmode, modified_indexes, estate);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
-	/*
-	 * Decide whether new index entries are needed for the tuple
-	 *
-	 * Note: heap_update returns the tid (location) of the new tuple in the
-	 * t_self field.
-	 *
-	 * If the update is not HOT, we must update all indexes. If the update is
-	 * HOT, it could be that we updated summarized columns, so we either
-	 * update only summarized indexes, or none at all.
-	 */
-	if (result != TM_Ok)
-	{
-		Assert(*update_indexes == TU_None);
-		*update_indexes = TU_None;
-	}
-	else if (!HeapTupleIsHeapOnly(tuple))
-		Assert(*update_indexes == TU_All);
-	else
-		Assert((*update_indexes == TU_Summarizing) ||
-			   (*update_indexes == TU_None));
-
 	if (shouldFree)
 		pfree(tuple);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e18a8f8250..2b5fe33e43 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -334,8 +334,7 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
-						  Snapshot snapshot,
-						  TU_UpdateIndexes *update_indexes)
+						  Snapshot snapshot)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
@@ -345,7 +344,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode, NULL, NULL);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7377912b41..b0e670c1f7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2455,7 +2455,20 @@ BuildIndexInfo(Relation index)
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
+	{
 		ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   indexStruct->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* collect attributes used in the expression, if one is present */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate, if one is present */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
@@ -2466,6 +2479,8 @@ BuildIndexInfo(Relation index)
 								 &ii->ii_ExclusionStrats);
 	}
 
+	ii->ii_OpClassDataTypes = index->rd_opcintype;
+
 	return ii;
 }
 
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc8..3fdebba3f0 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -72,8 +72,7 @@ CatalogCloseIndexes(CatalogIndexState indstate)
  * This is effectively a cut-down version of ExecInsertIndexTuples.
  */
 static void
-CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
-				   TU_UpdateIndexes updateIndexes)
+CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 {
 	int			i;
 	int			numIndexes;
@@ -83,20 +82,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	IndexInfo **indexInfoArray;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	bool		onlySummarized = (updateIndexes == TU_Summarizing);
 
-	/*
-	 * HOT update does not require index inserts. But with asserts enabled we
-	 * want to check that it'd be legal to currently insert into the
-	 * table/index.
-	 */
-#ifndef USE_ASSERT_CHECKING
-	if (HeapTupleIsHeapOnly(heapTuple) && !onlySummarized)
-		return;
-#endif
-
-	/* When only updating summarized indexes, the tuple has to be HOT. */
-	Assert((!onlySummarized) || HeapTupleIsHeapOnly(heapTuple));
+	/* The tuple never be HOT. */
+	Assert(!HeapTupleIsHeapOnly(heapTuple));
 
 	/*
 	 * Get information from the state structure.  Fall out if nothing to do.
@@ -138,22 +126,6 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 		Assert(index->rd_index->indimmediate);
 		Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
 
-		/* see earlier check above */
-#ifdef USE_ASSERT_CHECKING
-		if (HeapTupleIsHeapOnly(heapTuple) && !onlySummarized)
-		{
-			Assert(!ReindexIsProcessingIndex(RelationGetRelid(index)));
-			continue;
-		}
-#endif							/* USE_ASSERT_CHECKING */
-
-		/*
-		 * Skip insertions into non-summarizing indexes if we only need to
-		 * update summarizing indexes.
-		 */
-		if (onlySummarized && !indexInfo->ii_Summarizing)
-			continue;
-
 		/*
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
@@ -240,7 +212,7 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup)
 
 	simple_heap_insert(heapRel, tup);
 
-	CatalogIndexInsert(indstate, tup, TU_All);
+	CatalogIndexInsert(indstate, tup);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -260,7 +232,7 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 
 	simple_heap_insert(heapRel, tup);
 
-	CatalogIndexInsert(indstate, tup, TU_All);
+	CatalogIndexInsert(indstate, tup);
 }
 
 /*
@@ -291,7 +263,7 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
 
 		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
 		tuple->t_tableOid = slot[i]->tts_tableOid;
-		CatalogIndexInsert(indstate, tuple, TU_All);
+		CatalogIndexInsert(indstate, tuple);
 
 		if (should_free)
 			heap_freetuple(tuple);
@@ -313,15 +285,14 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
-	TU_UpdateIndexes updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -337,13 +308,11 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	TU_UpdateIndexes updateIndexes = TU_All;
-
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup);
 }
 
 /*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 7c87f012c3..3cbbf54970 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -117,6 +117,8 @@
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -146,6 +148,11 @@ static bool index_expression_changed_walker(Node *node,
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
+/*
+ * GUC parameters
+ */
+bool		enable_expression_checks = true;
+
 /* ----------------------------------------------------------------
  *		ExecOpenIndices
  *
@@ -168,6 +175,7 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 	IndexInfo **indexInfoArray;
 
 	resultRelInfo->ri_NumIndices = 0;
+	resultRelation->rd_indexinfolist = NIL;
 
 	/* fast path if no indexes */
 	if (!RelationGetForm(resultRelation)->relhasindex)
@@ -210,6 +218,10 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		/* extract index key information from the index's pg_index info */
 		ii = BuildIndexInfo(indexDesc);
 
+		/* used when determining if indexed values changed during update */
+		resultRelation->rd_indexinfolist =
+			lappend(resultRelation->rd_indexinfolist, ii);
+
 		/*
 		 * If the indexes are to be used for speculative insertion or conflict
 		 * detection in logical replication, add extra information required by
@@ -307,7 +319,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 					  bool noDupErr,
 					  bool *specConflict,
 					  List *arbiterIndexes,
-					  bool onlySummarizing)
+					  Bitmapset *modified_indexes)
 {
 	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
@@ -364,10 +376,12 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			continue;
 
 		/*
-		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * If modified_indexes is NULL, we're not in a HOT update, so we
+		 * should update all indexes.  If it's not NULL, we should only update
+		 * the listed indexes.  This list will include any summarizing indexes
+		 * that require updates on the HOT path as well.
 		 */
-		if (onlySummarizing && !indexInfo->ii_Summarizing)
+		if (update && modified_indexes && !bms_is_member(i, modified_indexes))
 			continue;
 
 		/* Check for partial index */
@@ -1089,6 +1103,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1166,3 +1183,217 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine which indexes must be invoked during ExecIndexInsertTuple().
+ *
+ * That will include: any index on a column where there is an attribute that was
+ * modified, any expression index where the expression's value updated, any
+ * index used in a constraint, and any summarizing index.
+ */
+Bitmapset *
+ExecIndexesRequiringUpdates(Relation relation,
+							Bitmapset *modified_attrs,
+							EState *estate,
+							HeapTuple old_tuple,
+							HeapTuple new_tuple,
+							bool *only_summarizing)
+{
+	ListCell   *lc;
+	List	   *indexinfolist = relation->rd_indexinfolist;
+	ExprContext *econtext = NULL;
+	int			num_summarizing = 0;
+	TupleDesc	tupledesc;
+	TupleTableSlot *old_tts,
+			   *new_tts;
+	Bitmapset  *result = NULL;
+
+	/*
+	 * Examine each index on this relation relative to the changes between old
+	 * and new tuples.
+	 */
+	foreach(lc, indexinfolist)
+	{
+		IndexInfo  *indexInfo = (IndexInfo *) lfirst(lc);
+
+		/*
+		 * Summarizing indexes don't prevent HOT updates, but do require that
+		 * they are updated so we include it in the set.
+		 */
+		if (indexInfo->ii_Summarizing)
+		{
+			num_summarizing++;
+			result = bms_add_member(result, foreach_current_index(lc));
+			continue;
+		}
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (enable_expression_checks && estate != NULL &&
+			bms_overlap(indexInfo->ii_PredicateAttrs, modified_attrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = bms_add_member(result, foreach_current_index(lc));
+				continue;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modified_attrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			bool		changed = false;
+
+			/*
+			 * Assume the index is changed when we don't have an estate
+			 * context to use or the GUC is disabled.
+			 */
+			if (!enable_expression_checks || estate == NULL)
+			{
+				result = bms_add_member(result, foreach_current_index(lc));
+				continue;
+			}
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			indexInfo->ii_ExpressionsState = NIL;
+
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+
+			for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+			{
+				if (old_isnull[i] != new_isnull[i])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[i])
+				{
+					int16		elmlen;
+					bool		elmbyval;
+					Oid			opcintyp = indexInfo->ii_OpClassDataTypes[i];
+
+					get_typlenbyval(opcintyp, &elmlen, &elmbyval);
+					if (!datum_image_eq(old_values[i], new_values[i],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			if (changed)
+				result = bms_add_member(result, foreach_current_index(lc));
+
+			/* Shortcut index_unchanged_by_update(), we know the answer. */
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+			continue;
+		}
+
+		/*
+		 * If the index references modified attributes then it needs to be
+		 * updated.
+		 */
+		if (bms_overlap(indexInfo->ii_IndexAttrs, modified_attrs))
+			result = bms_add_member(result, foreach_current_index(lc));
+	}
+
+	if (econtext)
+	{
+		ExecDropSingleTupleTableSlot(old_tts);
+		ExecDropSingleTupleTableSlot(new_tts);
+	}
+
+	*only_summarizing = (list_length(indexinfolist) > 0 &&
+						 num_summarizing == bms_num_members(result));
+
+	return result;
+}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 3985e84d3a..bbafb4fe0c 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -671,9 +671,9 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
 		List	   *conflictindexes;
 		bool		conflict = false;
+		Bitmapset  *modified_indexes = NULL;
 
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
@@ -687,17 +687,16 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
-		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
-								  &update_indexes);
+		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
 
-		if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
+		if (resultRelInfo->ri_NumIndices > 0)
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   conflictindexes ? true : false,
 												   &conflict, conflictindexes,
-												   (update_indexes == TU_Summarizing));
+												   &modified_indexes);
 
 		/*
 		 * Refer to the comments above the call to CheckAndReportConflict() in
@@ -715,6 +714,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 							 recheckIndexes, NULL, false);
 
 		list_free(recheckIndexes);
+		bms_free(modified_indexes);
 	}
 }
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index bc82e035ba..f99bf66177 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -120,8 +120,12 @@ typedef struct ModifyTableContext
  */
 typedef struct UpdateContext
 {
+	Bitmapset  *modifiedIndexes;	/* Either NULL indicating that all indexes
+									 * should be updated or a bitmap of
+									 * IndexInfo array positions for the
+									 * subset of modified indexes requiring
+									 * updates. */
 	bool		crossPartUpdate;	/* was it a cross-partition update? */
-	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
 
 	/*
 	 * Lock mode to acquire on the latest tuple version before performing
@@ -2266,7 +2270,8 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&updateCxt->modifiedIndexes,
+								estate);
 
 	return result;
 }
@@ -2286,12 +2291,12 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	List	   *recheckIndexes = NIL;
 
 	/* insert index entries for tuple if necessary */
-	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
+	if (resultRelInfo->ri_NumIndices > 0)
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   updateCxt->modifiedIndexes);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 43219a9629..f9a21a604a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2433,7 +2433,6 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_pkattr);
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
-	bms_free(relation->rd_summarizedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5226,7 +5225,6 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
-	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5247,8 +5245,6 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_idattr);
 			case INDEX_ATTR_BITMAP_HOT_BLOCKING:
 				return bms_copy(relation->rd_hotblockingattr);
-			case INDEX_ATTR_BITMAP_SUMMARIZED:
-				return bms_copy(relation->rd_summarizedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5292,7 +5288,6 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
-	summarizedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5305,7 +5300,6 @@ restart:
 		bool		isKey;		/* candidate key */
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
-		Bitmapset **attrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5343,16 +5337,6 @@ restart:
 		/* Is this index the configured (or default) replica identity? */
 		isIDKey = (indexOid == relreplindex);
 
-		/*
-		 * If the index is summarizing, it doesn't block HOT updates, but we
-		 * may still need to update it (if the attributes were modified). So
-		 * decide which bitmap we'll update in the following loop.
-		 */
-		if (indexDesc->rd_indam->amsummarizing)
-			attrs = &summarizedattrs;
-		else
-			attrs = &hotblockingattrs;
-
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
@@ -5374,8 +5358,8 @@ restart:
 			 */
 			if (attrnum != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				hotblockingattrs = bms_add_member(hotblockingattrs,
+												  attrnum - FirstLowInvalidHeapAttributeNumber);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
 					uindexattrs = bms_add_member(uindexattrs,
@@ -5392,12 +5376,14 @@ restart:
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, &hotblockingattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, &hotblockingattrs);
+
+		if (indexDesc->rd_index)
 
-		index_close(indexDesc, AccessShareLock);
+			index_close(indexDesc, AccessShareLock);
 	}
 
 	/*
@@ -5424,7 +5410,6 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
-		bms_free(summarizedattrs);
 
 		goto restart;
 	}
@@ -5439,8 +5424,6 @@ restart:
 	relation->rd_idattr = NULL;
 	bms_free(relation->rd_hotblockingattr);
 	relation->rd_hotblockingattr = NULL;
-	bms_free(relation->rd_summarizedattr);
-	relation->rd_summarizedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5454,7 +5437,6 @@ restart:
 	relation->rd_pkattr = bms_copy(pkindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
-	relation->rd_summarizedattr = bms_copy(summarizedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5469,8 +5451,6 @@ restart:
 			return idindexattrs;
 		case INDEX_ATTR_BITMAP_HOT_BLOCKING:
 			return hotblockingattrs;
-		case INDEX_ATTR_BITMAP_SUMMARIZED:
-			return summarizedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 38cb9e970d..1f3eb0caa1 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1007,6 +1007,19 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_expression_checks", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables the ability to examine expression and partial indexes "
+						 "for changes during updates."),
+			gettext_noop("Evaluates the expressions on the index to determine if "
+						 "the indexed values changed.  This allows for heap-only "
+						 "tuple (HOT) updates when indexes have not changed."),
+			GUC_EXPLAIN
+		},
+		&enable_expression_checks,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 079efa1baa..61ab1bb59b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -408,6 +408,7 @@
 #enable_tidscan = on
 #enable_group_by_reordering = on
 #enable_distinct_reordering = on
+#enable_expression_checks = on
 
 # - Planner Cost Constants -
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f..e12173eba4 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -339,7 +340,7 @@ extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 Bitmapset **modified_indexes, struct EState *estate);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -374,7 +375,7 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+							   HeapTuple tup);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 131c050c15..300226d44d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -38,6 +38,7 @@ struct IndexInfo;
 struct SampleScanState;
 struct VacuumParams;
 struct ValidateIndexState;
+struct EState;
 
 /*
  * Bitmask values for the flags argument to the scan_begin callback.
@@ -109,21 +110,6 @@ typedef enum TM_Result
 	TM_WouldBlock,
 } TM_Result;
 
-/*
- * Result codes for table_update(..., update_indexes*..).
- * Used to determine which indexes to update.
- */
-typedef enum TU_UpdateIndexes
-{
-	/* No indexed columns were updated (incl. TID addressing of tuple) */
-	TU_None,
-
-	/* A non-summarizing indexed column was updated, or the TID has changed */
-	TU_All,
-
-	/* Only summarized columns were updated, TID is unchanged */
-	TU_Summarizing,
-} TU_UpdateIndexes;
 
 /*
  * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail
@@ -550,7 +536,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 Bitmapset **modified_indexes,
+								 struct EState *estate);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1541,12 +1528,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   Bitmapset **modified_indexes, struct EState *estate)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 modified_indexes, estate);
 }
 
 /*
@@ -2075,8 +2062,7 @@ extern void simple_table_tuple_insert(Relation rel, TupleTableSlot *slot);
 extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
-									  TupleTableSlot *slot, Snapshot snapshot,
-									  TU_UpdateIndexes *update_indexes);
+									  TupleTableSlot *slot, Snapshot snapshot);
 
 
 /* ----------------------------------------------------------------------------
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c7db6defd3..2da448fca0 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -649,7 +649,7 @@ extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   bool update,
 								   bool noDupErr,
 								   bool *specConflict, List *arbiterIndexes,
-								   bool onlySummarizing);
+								   Bitmapset *modified_indexes);
 extern bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo,
 									  TupleTableSlot *slot,
 									  EState *estate, ItemPointer conflictTid,
@@ -660,6 +660,12 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern Bitmapset *ExecIndexesRequiringUpdates(Relation relation,
+											  Bitmapset *modified_attrs,
+											  EState *estate,
+											  HeapTuple old_tuple,
+											  HeapTuple new_tuple,
+											  bool *only_summarizing);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8ce4430af0..6417769a9a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -159,12 +159,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -183,6 +186,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -194,10 +198,13 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
@@ -217,6 +224,7 @@ typedef struct IndexInfo
 	int			ii_ParallelWorkers;
 	Oid			ii_Am;
 	void	   *ii_AmCache;
+	Oid		   *ii_OpClassDataTypes;
 	MemoryContext ii_Context;
 } IndexInfo;
 
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index bcf8ed645c..f98774e940 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -112,6 +112,7 @@ typedef enum
 extern PGDLLIMPORT int debug_parallel_query;
 extern PGDLLIMPORT bool parallel_leader_participation;
 extern PGDLLIMPORT bool enable_distinct_reordering;
+extern PGDLLIMPORT bool enable_expression_checks;
 
 extern struct PlannedStmt *planner(Query *parse, const char *query_string,
 								   int cursorOptions,
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 33d1e4a4e2..3294bcf054 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -154,6 +154,10 @@ typedef struct RelationData
 	bool		rd_ispkdeferrable;	/* is rd_pkindex a deferrable PK? */
 	Oid			rd_replidindex; /* OID of replica identity index, if any */
 
+	/* list of IndexInfo for this relation */
+	List	   *rd_indexinfolist;	/* an ordered list of IndexInfo for
+									 * indexes on relation */
+
 	/* data managed by RelationGetStatExtList: */
 	List	   *rd_statlist;	/* list of OIDs of extended stats */
 
@@ -163,7 +167,6 @@ typedef struct RelationData
 	Bitmapset  *rd_pkattr;		/* cols included in primary key */
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
-	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339..0cc28cb97e 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -63,6 +63,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 0000000000..177c6109f5
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,318 @@
+SHOW enable_expression_checks;
+ enable_expression_checks 
+--------------------------
+ on
+(1 row)
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable the GUC that allows us to skip expression checks.
+SET enable_expression_checks = 'off';
+-- The indexed attribute "name" with value "john" is unchanged, don't expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable the GUC so that we don't skip expression checks.
+SET enable_expression_checks = 'on';
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column _v and two indexes.  They are both expression
+-- indexes referencing the same column attribute (_v) but one is a partial
+-- index.
+CREATE TABLE public.ex (_v JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (_v) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (_v) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((_v->>'a'));
+CREATE INDEX idx_ex_b ON ex ((_v->>'b')) WHERE (_v->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 1) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+ _v 
+----
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 10) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+        _v         
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 1, 'b', 10) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 12) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+        _v         
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 1) WHERE (_v->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((_v ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+ _v 
+----
+(0 rows)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation.
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            5 |                1 |                      20.00 |             0 |           0 |                            
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+DROP TABLE public.ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 rows, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 row, one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 352abc0bd4..56451bf783 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -151,6 +151,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_async_append            | on
  enable_bitmapscan              | on
  enable_distinct_reordering     | on
+ enable_expression_checks       | on
  enable_gathermerge             | on
  enable_group_by_reordering     | on
  enable_hashagg                 | on
@@ -171,7 +172,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(23 rows)
+(24 rows)
 
 -- There are always wait event descriptions for various types.  InjectionPoint
 -- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1edd9e45eb..34232fd337 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 0000000000..22c6eafeab
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,208 @@
+SHOW enable_expression_checks;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable the GUC that allows us to skip expression checks.
+SET enable_expression_checks = 'off';
+
+-- The indexed attribute "name" with value "john" is unchanged, don't expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable the GUC so that we don't skip expression checks.
+SET enable_expression_checks = 'on';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row, no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column _v and two indexes.  They are both expression
+-- indexes referencing the same column attribute (_v) but one is a partial
+-- index.
+CREATE TABLE public.ex (_v JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (_v) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (_v) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((_v->>'a'));
+CREATE INDEX idx_ex_b ON ex ((_v->>'b')) WHERE (_v->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 1) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 0, 'b', 10) WHERE (_v->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 1, 'b', 10) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 12) WHERE (_v->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET _v = jsonb_build_object('a', 2, 'b', 1) WHERE (_v->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row, no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (_v->>'b')::numeric > 9 AND (_v->>'b')::numeric < 100;
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation.
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+DROP TABLE public.ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 rows, no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, a single new HOT update
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 row, no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 row, no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 row, one new HOT update
+
+DROP TABLE events;
-- 
2.42.0

#2Laurenz Albe
laurenz.albe@cybertec.at
In reply to: Burd, Greg (#1)
Re: Expanding HOT updates for expression and partial indexes

On Thu, 2025-02-06 at 22:24 +0000, Burd, Greg wrote:

Attached find a patch that expands the cases where heap-only tuple (HOT) updates are possible
without changing the basic semantics of HOT. This is accomplished by examining expression
indexes for changes to determine if indexes require updating or not. A similar approach is
taken for partial indexes, the predicate is evaluated and, in some cases, HOT updates are
allowed.

[...]

Third, there is overhead to this patch, it is no longer a single simple bitmap test to choose
HOT or not in heap_update(). Sometimes this patch will perform expensive additional checks
and ultimately not go down the HOT path, new overhead with no benefit. Some expressions are
more expensive than others to evaluate, there is no logic to adjust for that. The Surjective
patch/email thread had quite a bit of discussion on this without resolution. I’ve chosen to
add a GUC that optionally avoids the expression evaluation. I’m open to ideas here as well,
addition of another GUC or removal of the one I’ve added. I’ve tried to avoid rechecking
indexes for changes when possible.

I think that the goal of this patch is interesting and desirable.

The greatest concern for me is the performance impact. I think that a switch is warranted,
but I am not sure if it should be a GUC. Wouldn't it be better to have a reloption, so that
this can be configured per table? I am not sure if a global switch is necessary, but I am
not fundamentally against it.

Yours,
Laurenz Albe

#3Burd, Greg
gregburd@amazon.com
In reply to: Laurenz Albe (#2)
Re: Expanding HOT updates for expression and partial indexes

On Feb 9, 2025, at 1:14 AM, Laurenz Albe <laurenz.albe@cybertec.at> wrote:

I think that the goal of this patch is interesting and desirable.

Thanks for taking a look at it. Which version did you prefer, v3 or v4?

The greatest concern for me is the performance impact.

Agreed, I’m still looking for ways to minimize it (suggestions welcome).

I think that a switch is warranted, but I am not sure if it should be a GUC.
Wouldn't it be better to have a reloption, so that this can be configured per table?

I can remove the GUC in favor of a reloption, that makes sense to me.

Yours,
Laurenz Albe

best,

-greg

#4Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#1)
Re: Expanding HOT updates for expression and partial indexes

On Thu, 6 Feb 2025 at 23:24, Burd, Greg <gregburd@amazon.com> wrote:

Attached find a patch that expands the cases where heap-only tuple (HOT) updates are possible without changing the basic semantics of HOT. This is accomplished by examining expression indexes for changes to determine if indexes require updating or not. A similar approach is taken for partial indexes, the predicate is evaluated and, in some cases, HOT updates are allowed. Even with this patch if any index is changed, all indexes are updated. Only in cases where none are modified will this patch allow the HOT path.

So, effectively this disables the amsummarizing-based optimizations of
https://postgr.es/c/19d8e2308 ? That sounds like a bad degradation in
behaviour.

I’m also aware of PHOT [4] and WARM [5] which allow for updating some, but not all indexes while remaining on the HOT update path, this patch does not attempt to accomplish that.

[...] This opens the door to future improvements by providing a way to pass a bitmap of modified indexes along to be addressed by something similar to the PHOT/WARM logic.

<sidetrack>

I have serious doubts about the viability of any proposal working to
implement PHOT/WARM in PostgreSQL, as they seem to have an inherent
nature of fundamentally breaking the TID lifecycle:
We won't be able to clean up dead-to-everyone TIDs that were
PHOT-updated, because some index Y may still rely on it, and we can't
remove the TID from that same index Y because there is still a live
PHOT/WARM tuple later in the chain whose values for that index haven't
changed since that dead-to-everyone tuple, and thus this PHOT/WARM
tuple is the one pointed to by that index.
For HOT, this isn't much of an issue, because there is just one TID
that's impacted (and it only occupies a single LP slot, with
LP_REDIRECT). However, with PHOT/WARM, you'd relatively easily be able
to fill a page with TIDs (or even full tuples) you can't clean up with
VACUUM until the moment a the PHOT/WARM/HOT chain is broken (due to
UPDATE leaving the page or the final entry getting DELETE-d).

Unless we are somehow are able to replace the TIDs in indexes from
"intermediate dead PHOT" to "base TID"/"latest TID" (either of which
is probably also problematic for indexes that expect a TID to appear
exactly once in the index at any point in time) I don't think the
system is viable if we maintain only a single data structure to
contain all dead TIDs. If we had a datastore for dead items per index,
that'd be more likely to work, but it also would significantly
increase the memory overhead of vacuuming tables.

</sidetrack>

I have a few concerns with the patch, things I’d greatly appreciate your thoughts on:

First, I pass an EState along the update path to enable running the checks in heapam, this works but leaves me feeling as if I violated separation of concerns. If there is a better way to do this let me know or if you think the cost of creating one in the execIndexing.c ExecIndexesRequiringUpdates() is okay that’s another possibility.

I think that doesn't have to be bad.

Third, there is overhead to this patch, it is no longer a single simple bitmap test to choose HOT or not in heap_update().

Why can't it mostly be that simple in simple cases?

I mean, it's clear that "updated indexed column's value == non-HOT
update". And that to determine whether an updated *projected* column's
value (i.e., expression index column's value) was actually updated we
need to calculate the previous and current index value, thus execute
the projection twice. But why would we have significant additional
overhead if there are no expression indexes, or when we can know by
bitmap overlap that the only interesting cases are summarizing
indexes?

I would've implemented this with (1) two new bitmaps, one each for
normal and summarizing indexes, each containing which columns are
exclusively used in expression indexes (and which should thus be used
to trigger the (comparatively) expensive recalculation).

Then, I'd maintain a (cached) list of unique projections/expressions
found in indexes, so that 30 indexes on e.g.
((mycolumn::jsonb)->>'metadata') only extend to 1 check for
differences, rather than 30. The "new" output of these expression
evaluations would be stored to be used later as index datums, reducing
the number of per-expression evaluations down to 2 at most, rather
than 2+1 when the index needs an insertion but the expression itself
wasn't updated.

So, it'd be something like (pseudocode):

if (bms_overlap(updated_columns, hotblocking))
/* if columns only indexed through expressions were updated, do
expensive stuff. Otherwise, it's a normal non-HOT update. */
if (bms_subset_compare(updated_columns, hot_expression_columns) in
(BMS_EQUAL, BMS_SUBSET1))
expensive check for expression changes + populate index column data
else
normal_update
else if (bms_overlap(updated_columns, summarizing))
/* same as above for hotblocking, but now summarizing */
if (bms_subset_compare(updated_columns, sum_expression_columns) in
(BMS_EQUAL, BMS_SUBSET1))
expensive check for summarized expression changes + populate
summarized index column data
else
summarizing_update
else
hot_update

Note that it is relatively expensive to do check whether any one index
needs to be updated. It's generally cheaper to do all those checks at
once, where possible; using one or 2 more bitmaps would be sufficient.

Also note that this approach doesn't update specific summarizing
indexes, just all of them or none. I think that "update only
summarizing indexes that were updated" should be a separate patch from
"check if indexed expressions' values changed", potentially in the
patchset, but not as part of the main bulk.

Fourth, I’d like to know which version the community prefers (v3 or v4). I think v4 moves the code in a direction that is cleaner overall, but you may disagree. I realize that the way I use the modified_indexes bitmapset is a tad overloaded (NULL means all indexes should be updated, otherwise only update the indexes in the set which may be all/some/none of the indexes) and that may violate the principal of least surprise but I feel that it is better than the TU_UpdateIndexes enum in the code today.

I would be hesitant to let table AMs decide which indexes to update at
that precision. Note that this API would allow the AM to update only
(say) the PK index and no other indexes, which is not allowed to
happen if index consistentcy is required (which it is).

----->8-----

Do you have any documentation on the approaches used, and the specific
differences between v3 and v4? I don't see much of that in your
initial mail, and the patches themselves also don't show much of that
in their details. I'd like at least some documentation of the new
behaviour in src/backend/access/heap/README.HOT at some point before
this got marked as RFC in the commitfest app, though preferably sooner
rather than later.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#5Burd, Greg
gregburd@amazon.com
In reply to: Matthias van de Meent (#4)
Re: Expanding HOT updates for expression and partial indexes

Apologies for not being clear, this preserves the current behavior for summarizing indexes allowing for HOT updates while also updating the index. No degradation here that I’m aware of, indeed the tests that ensure that behavior are unchanged and pass.

-greg

Show quoted text

On Feb 10, 2025, at 12:17 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

So, effectively this disables the amsummarizing-based optimizations of
https://postgr.es/c/19d8e2308 ? That sounds like a bad degradation in
behaviour.

#6Burd, Greg
gregburd@amazon.com
In reply to: Matthias van de Meent (#4)
Re: Expanding HOT updates for expression and partial indexes

On Feb 10, 2025, at 12:17 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

I have a few concerns with the patch, things I’d greatly appreciate your thoughts on:

First, I pass an EState along the update path to enable running the checks in heapam, this works but leaves me feeling as if I violated separation of concerns. If there is a better way to do this let me know or if you think the cost of creating one in the execIndexing.c ExecIndexesRequiringUpdates() is okay that’s another possibility.

I think that doesn't have to be bad.

Meaning that the approach I’ve taken is okay with you?

Third, there is overhead to this patch, it is no longer a single simple bitmap test to choose HOT or not in heap_update().

Why can't it mostly be that simple in simple cases?

It can remain that simple in the cases you mention. In relcache the hot blocking attributes are nearly the same, only the summarizing attributes are removed. The first test then in heap_update() is for overlap with the modified set. When there is none, the update will proceed on the HOT path.

The presence of a summarizing index is determined in ExecIndexesRequiringUpdates() in execIndexing.c, so a slightly longer code path but not much new overhead.

I mean, it's clear that "updated indexed column's value == non-HOT
update". And that to determine whether an updated *projected* column's
value (i.e., expression index column's value) was actually updated we
need to calculate the previous and current index value, thus execute
the projection twice. But why would we have significant additional
overhead if there are no expression indexes, or when we can know by
bitmap overlap that the only interesting cases are summarizing
indexes?

You’re right, there’s not a lot of new overhead in that case except what happens in ExecIndexesRequiringUpdates() to scan over the list of IndexInfo. It is really only when there are many expressions/predicates requiring examination that there is any significant cost to this approach AFAICT (but if you see something please point it out).

I would've implemented this with (1) two new bitmaps, one each for
normal and summarizing indexes, each containing which columns are
exclusively used in expression indexes (and which should thus be used
to trigger the (comparatively) expensive recalculation).

That was one where I started, over time that became harder to work as the bitmaps contain the union of index attributes for the table not per-column. Now there is one bitmap to cover the broadest case and then a function to find the modified set of indexes where each is examined against bitmaps that contain only attributes specific to the index in question. This helped in cases where there were both expression and non-expression indexes on the same attribute.

Then, I'd maintain a (cached) list of unique projections/expressions
found in indexes, so that 30 indexes on e.g.
((mycolumn::jsonb)->>'metadata') only extend to 1 check for
differences, rather than 30.

An optimization to avoid rechecking isn’t a bad idea. I wonder how hard it would be to surface the field (->>’metadata’) from the index expression to track for redundancy, I’ll have to look into that.

The "new" output of these expression
evaluations would be stored to be used later as index datums, reducing
the number of per-expression evaluations down to 2 at most, rather
than 2+1 when the index needs an insertion but the expression itself
wasn't updated.

Not reforming the new index tuples is also an interesting optimization. I wonder how that can be passed from within heapam’s call into a function in execIndexing up into nodeModifiyTable and back down into execIndexing and on to the index access method? I’ll have to think about that, ideas welcome.

So, it'd be something like (pseudocode):

if (bms_overlap(updated_columns, hotblocking))
/* if columns only indexed through expressions were updated, do
expensive stuff. Otherwise, it's a normal non-HOT update. */
if (bms_subset_compare(updated_columns, hot_expression_columns) in
(BMS_EQUAL, BMS_SUBSET1))
expensive check for expression changes + populate index column data
else
normal_update
else if (bms_overlap(updated_columns, summarizing))
/* same as above for hotblocking, but now summarizing */
if (bms_subset_compare(updated_columns, sum_expression_columns) in
(BMS_EQUAL, BMS_SUBSET1))
expensive check for summarized expression changes + populate
summarized index column data
else
summarizing_update
else
hot_update

Note that it is relatively expensive to do check whether any one index
needs to be updated. It's generally cheaper to do all those checks at
once, where possible; using one or 2 more bitmaps would be sufficient.

Also note that this approach doesn't update specific summarizing
indexes, just all of them or none. I think that "update only
summarizing indexes that were updated" should be a separate patch from
"check if indexed expressions' values changed", potentially in the
patchset, but not as part of the main bulk.

Fourth, I’d like to know which version the community prefers (v3 or v4). I think v4 moves the code in a direction that is cleaner overall, but you may disagree. I realize that the way I use the modified_indexes bitmapset is a tad overloaded (NULL means all indexes should be updated, otherwise only update the indexes in the set which may be all/some/none of the indexes) and that may violate the principal of least surprise but I feel that it is better than the TU_UpdateIndexes enum in the code today.

I would be hesitant to let table AMs decide which indexes to update at
that precision. Note that this API would allow the AM to update only
(say) the PK index and no other indexes, which is not allowed to
happen if index consistentcy is required (which it is).

Interesting, thanks for the feedback. I’ll think on this a bit more and provide more detail with the next update.

----->8-----

Do you have any documentation on the approaches used, and the specific
differences between v3 and v4? I don't see much of that in your
initial mail, and the patches themselves also don't show much of that
in their details. I'd like at least some documentation of the new
behaviour in src/backend/access/heap/README.HOT at some point before
this got marked as RFC in the commitfest app, though preferably sooner
rather than later.

Good point, I should have updated README.HOT with the initial patchset. I’ll jump on that and update ASAP.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

thanks for the thoughtful reply.

-greg

#7Nathan Bossart
nathandbossart@gmail.com
In reply to: Matthias van de Meent (#4)
Re: Expanding HOT updates for expression and partial indexes

On Mon, Feb 10, 2025 at 06:17:42PM +0100, Matthias van de Meent wrote:

I have serious doubts about the viability of any proposal working to
implement PHOT/WARM in PostgreSQL, as they seem to have an inherent
nature of fundamentally breaking the TID lifecycle:
We won't be able to clean up dead-to-everyone TIDs that were
PHOT-updated, because some index Y may still rely on it, and we can't
remove the TID from that same index Y because there is still a live
PHOT/WARM tuple later in the chain whose values for that index haven't
changed since that dead-to-everyone tuple, and thus this PHOT/WARM
tuple is the one pointed to by that index.
For HOT, this isn't much of an issue, because there is just one TID
that's impacted (and it only occupies a single LP slot, with
LP_REDIRECT). However, with PHOT/WARM, you'd relatively easily be able
to fill a page with TIDs (or even full tuples) you can't clean up with
VACUUM until the moment a the PHOT/WARM/HOT chain is broken (due to
UPDATE leaving the page or the final entry getting DELETE-d).

Unless we are somehow are able to replace the TIDs in indexes from
"intermediate dead PHOT" to "base TID"/"latest TID" (either of which
is probably also problematic for indexes that expect a TID to appear
exactly once in the index at any point in time) I don't think the
system is viable if we maintain only a single data structure to
contain all dead TIDs. If we had a datastore for dead items per index,
that'd be more likely to work, but it also would significantly
increase the memory overhead of vacuuming tables.

I share your concerns, but I don't think things are as dire as you suggest.
For example, perhaps we put a limit on how long a PHOT chain can be, or
maybe we try to detect update patterns that don't work well with PHOT.
Another option could be to limit PHOT updates to only when the same set of
indexed columns are updated or when <50% of the indexed columns are
updated. These aren't fully fleshed-out ideas, of course, but I am at
least somewhat optimistic we could find appropriate trade-offs.

--
nathan

#8Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Nathan Bossart (#7)
Re: Expanding HOT updates for expression and partial indexes

On Tue, 11 Feb 2025 at 00:20, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Mon, Feb 10, 2025 at 06:17:42PM +0100, Matthias van de Meent wrote:

I have serious doubts about the viability of any proposal working to
implement PHOT/WARM in PostgreSQL, as they seem to have an inherent
nature of fundamentally breaking the TID lifecycle:
[... concerns]

I share your concerns, but I don't think things are as dire as you suggest.
For example, perhaps we put a limit on how long a PHOT chain can be, or
maybe we try to detect update patterns that don't work well with PHOT.
Another option could be to limit PHOT updates to only when the same set of
indexed columns are updated or when <50% of the indexed columns are
updated. These aren't fully fleshed-out ideas, of course, but I am at
least somewhat optimistic we could find appropriate trade-offs.

Yes, there are methods which could limit the overhead. But I'm not
sure there are cheap-enough designs which would make PHOT a
universally good choice (i.e. not tunable with guc/table option),
considering its significantly larger un-reclaimable storage overhead
vs HOT.

Kind regards,

Matthias van de Meent.

#9Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#6)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

On Mon, 10 Feb 2025 at 20:11, Burd, Greg <gregburd@amazon.com> wrote:

On Feb 10, 2025, at 12:17 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

I have a few concerns with the patch, things I’d greatly appreciate your thoughts on:

First, I pass an EState along the update path to enable running the checks in heapam, this works but leaves me feeling as if I violated separation of concerns. If there is a better way to do this let me know or if you think the cost of creating one in the execIndexing.c ExecIndexesRequiringUpdates() is okay that’s another possibility.

I think that doesn't have to be bad.

Meaning that the approach I’ve taken is okay with you?

If you mean "passing EState down through the AM so we can check if we
really need HOT updates", then Yes, that's OK. I don't think the basic
idea (executing the projections to check for differences) is bad per
se, however I do think we may need more design work on the exact shape
of how ExecIndexesRequiringUpdates() receives its information (which
includes the EState).

E.g. we could pass an opaquely typed pointer that's passed from the
executor state through the table_update method into this
ExecIndexesRequiringUpdates(). That opaque struct would then contain
the important information for index update state checking, so that the
AM can't realistically break things without bypassing the separation
of concerns, and doesn't have to know about any executor nodes.

Third, there is overhead to this patch, it is no longer a single simple bitmap test to choose HOT or not in heap_update().

Why can't it mostly be that simple in simple cases?

It can remain that simple in the cases you mention. In relcache the hot blocking attributes are nearly the same, only the summarizing attributes are removed. The first test then in heap_update() is for overlap with the modified set. When there is none, the update will proceed on the HOT path.

The presence of a summarizing index is determined in ExecIndexesRequiringUpdates() in execIndexing.c, so a slightly longer code path but not much new overhead.

Yes, but that's only determined at an index-by-index level, rather
than determined all at once, and that's real bad when you have
hundreds of indexes to go through (however unlikely it might be, I've
heard of cases where there are 1000s of indexes on one table). So, I
prefer limiting any O(n_indexes) operations to only the most critical
cases.

I mean, it's clear that "updated indexed column's value == non-HOT
update". And that to determine whether an updated *projected* column's
value (i.e., expression index column's value) was actually updated we
need to calculate the previous and current index value, thus execute
the projection twice. But why would we have significant additional
overhead if there are no expression indexes, or when we can know by
bitmap overlap that the only interesting cases are summarizing
indexes?

You’re right, there’s not a lot of new overhead in that case except what happens in ExecIndexesRequiringUpdates() to scan over the list of IndexInfo. It is really only when there are many expressions/predicates requiring examination that there is any significant cost to this approach AFAICT (but if you see something please point it out).

See the attached approach. Evaluation of the expressions only has to
happen if there are any HOT-blocking attributes which are exclusively
hot-blockingly indexed through expressions, so if the updated
attribute numbers are a subset of hotblockingexprattrs. (substitute
hotblocking with summarizing for the summarizing approach)

I would've implemented this with (1) two new bitmaps, one each for
normal and summarizing indexes, each containing which columns are
exclusively used in expression indexes (and which should thus be used
to trigger the (comparatively) expensive recalculation).

That was one where I started, over time that became harder to work as the bitmaps contain the union of index attributes for the table not per-column.

I think it's fairly easy to create, though.

Now there is one bitmap to cover the broadest case and then a function to find the modified set of indexes where each is examined against bitmaps that contain only attributes specific to the index in question. This helped in cases where there were both expression and non-expression indexes on the same attribute.

Fair, but do we care about one expression index on (attr1->>'data')'s
value *not* changing when an index on (attr1) exists and attr1 has
changed? That index on att1 would block HOT updates regardless of the
(lack of) changes to the (att1->>'data') index, so doing those
expensive calculations seems quite wasteful.

So, in my opinion, we should also keep track of those attributes only
included in expressions of indexes, and that's fairly easy: see
attached prototype.diff.txt (might need some work, the patch was
drafted on v16's codebase, but the idea is clear).

The resulting %exprattrs bitmap contains attributes that are used only
in expressions of those index types.

The "new" output of these expression
evaluations would be stored to be used later as index datums, reducing
the number of per-expression evaluations down to 2 at most, rather
than 2+1 when the index needs an insertion but the expression itself
wasn't updated.

Not reforming the new index tuples is also an interesting optimization. I wonder how that can be passed from within heapam’s call into a function in execIndexing up into nodeModifiyTable and back down into execIndexing and on to the index access method? I’ll have to think about that, ideas welcome.

Note that index tuple forming happens only in the index AM, it's the
Datum construction (i.e. projection from attributes/tuple to indexed
value) that I'd like to deduplicate. Though looking at the current
code, I don't think it's reasonable to have that as a requirement for
this work. It'd be a nice-to-have for sure, but not as requirement.

Do you have any documentation on the approaches used, and the specific
differences between v3 and v4? I don't see much of that in your
initial mail, and the patches themselves also don't show much of that
in their details. I'd like at least some documentation of the new
behaviour in src/backend/access/heap/README.HOT at some point before
this got marked as RFC in the commitfest app, though preferably sooner
rather than later.

Good point, I should have updated README.HOT with the initial patchset. I’ll jump on that and update ASAP.

Thanks in advance.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

Attachments:

prototype.diff.txttext/plain; charset=US-ASCII; name=prototype.diff.txtDownload
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5a57702e914..bf41e9f53d3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5181,8 +5181,10 @@ RelationGetIndexAttrBitmap(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 */
-	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *hotblockingattrs;		/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in expressions */
+	Bitmapset  *summarizedattrs;		/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5248,7 +5250,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5262,6 +5266,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5305,9 +5310,15 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
@@ -5348,10 +5359,10 @@ restart:
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5380,11 +5391,25 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {all columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
#10Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#5)
Re: Expanding HOT updates for expression and partial indexes

On Mon, 10 Feb 2025 at 19:15, Burd, Greg <gregburd@amazon.com> wrote:

Apologies for not being clear, this preserves the current behavior for summarizing indexes allowing for HOT updates while also updating the index. No degradation here that I’m aware of, indeed the tests that ensure that behavior are unchanged and pass.

Looking at the code again, while it does indeed preserve the current
behaviour, it doesn't actually improve the behavior for summarizing
indexes when that would be expected.

Example:

CREATE INDEX hotblocking ON mytab USING btree((att1->'data'));
CREATE INDEX summarizing ON mytab USING BRIN(att2);
UPDATE mytab SET att1 = att1 || '{"check": "mate"}';

In v3 (same code present in v4), I notice that in the above case we
hit the "indexed attribute updated" path (hotblocking indeed indexes
the updated attribute att1), go into ExecIndexesRequiringUpdates, and
mark index 'summarizing' as 'needs an update', even though no
attribute of that index has a new value. Then we notice that
att1->'data' hasn't changed, and so we don't need to update the
'hotblocking' index, but we do update the (unchanged) 'summarizing'
index.

This indicates that in practice (with this version of the patch) this
will improve the HOT applicability situation while summarizing indexes
don't really gain a benefit from this - they're always updated when
any indexed column is updated, even if we could detect that there were
no changes to any indexed values.

Actually, you could say we find ourselves in the counter-intuitive
situation that the addition of the 'hotblocking' index whose value
were not updated now caused index insertions into summarizing indexes.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#11Burd, Greg
gregburd@amazon.com
In reply to: Matthias van de Meent (#10)
Re: Expanding HOT updates for expression and partial indexes

Matthias,

Thanks for the in-depth review, you are correct and I appreciate you uncovering that oversight with summarizing indexes. I’ll add a test case and modify the logic to prevent updates to unchanged summarizing indexes by testing their attributes against the modified set while keeping the HOT optimization when only summarizing indexes are changed.

thanks for finding this,

-greg

Show quoted text

On Feb 11, 2025, at 4:40 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Mon, 10 Feb 2025 at 19:15, Burd, Greg <gregburd@amazon.com> wrote:

Apologies for not being clear, this preserves the current behavior for summarizing indexes allowing for HOT updates while also updating the index. No degradation here that I’m aware of, indeed the tests that ensure that behavior are unchanged and pass.

Looking at the code again, while it does indeed preserve the current
behaviour, it doesn't actually improve the behavior for summarizing
indexes when that would be expected.

Example:

CREATE INDEX hotblocking ON mytab USING btree((att1->'data'));
CREATE INDEX summarizing ON mytab USING BRIN(att2);
UPDATE mytab SET att1 = att1 || '{"check": "mate"}';

In v3 (same code present in v4), I notice that in the above case we
hit the "indexed attribute updated" path (hotblocking indeed indexes
the updated attribute att1), go into ExecIndexesRequiringUpdates, and
mark index 'summarizing' as 'needs an update', even though no
attribute of that index has a new value. Then we notice that
att1->'data' hasn't changed, and so we don't need to update the
'hotblocking' index, but we do update the (unchanged) 'summarizing'
index.

This indicates that in practice (with this version of the patch) this
will improve the HOT applicability situation while summarizing indexes
don't really gain a benefit from this - they're always updated when
any indexed column is updated, even if we could detect that there were
no changes to any indexed values.

Actually, you could say we find ourselves in the counter-intuitive
situation that the addition of the 'hotblocking' index whose value
were not updated now caused index insertions into summarizing indexes.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#12Burd, Greg
gregburd@amazon.com
In reply to: Matthias van de Meent (#9)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Attached find an updated patchset v5 that is an evolution of v4.

Changes v4 to v5 are:
* replaced GUC with table reloption called "expression_checks" (open to other name ideas)
* minimal documentation updates to README.HOT to address changes
* avoid, when possible, the expensive path that requires evaluating an estate using bitmaps
* determines the set of summarized indexes requiring updates, only updates those
* more tests in heap_hot_updates.sql (perhaps too many...)
* rebased to master, formatted, and make check-world passes

More comments in context below...

-greg

On Feb 11, 2025, at 4:18 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Mon, 10 Feb 2025 at 20:11, Burd, Greg <gregburd@amazon.com> wrote:

On Feb 10, 2025, at 12:17 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

I have a few concerns with the patch, things I’d greatly appreciate your thoughts on:

First, I pass an EState along the update path to enable running the checks in heapam, this works but leaves me feeling as if I violated separation of concerns. If there is a better way to do this let me know or if you think the cost of creating one in the execIndexing.c ExecIndexesRequiringUpdates() is okay that’s another possibility.

I think that doesn't have to be bad.

Meaning that the approach I’ve taken is okay with you?

If you mean "passing EState down through the AM so we can check if we
really need HOT updates", then Yes, that's OK. I don't think the basic
idea (executing the projections to check for differences) is bad per
se, however I do think we may need more design work on the exact shape
of how ExecIndexesRequiringUpdates() receives its information (which
includes the EState).

E.g. we could pass an opaquely typed pointer that's passed from the
executor state through the table_update method into this
ExecIndexesRequiringUpdates(). That opaque struct would then contain
the important information for index update state checking, so that the
AM can't realistically break things without bypassing the separation
of concerns, and doesn't have to know about any executor nodes.

I'm open to this idea and will attempt an implementation in v6, ideas welcome.

Third, there is overhead to this patch, it is no longer a single simple bitmap test to choose HOT or not in heap_update().

Why can't it mostly be that simple in simple cases?

It can remain that simple in the cases you mention. In relcache the hot blocking attributes are nearly the same, only the summarizing attributes are removed. The first test then in heap_update() is for overlap with the modified set. When there is none, the update will proceed on the HOT path.

The presence of a summarizing index is determined in ExecIndexesRequiringUpdates() in execIndexing.c, so a slightly longer code path but not much new overhead.

Yes, but that's only determined at an index-by-index level, rather
than determined all at once, and that's real bad when you have
hundreds of indexes to go through (however unlikely it might be, I've
heard of cases where there are 1000s of indexes on one table). So, I
prefer limiting any O(n_indexes) operations to only the most critical
cases.

This makes sense, and I agree that avoiding O(n_indexes) operations is a good goal when possible.

I mean, it's clear that "updated indexed column's value == non-HOT
update". And that to determine whether an updated *projected* column's
value (i.e., expression index column's value) was actually updated we
need to calculate the previous and current index value, thus execute
the projection twice. But why would we have significant additional
overhead if there are no expression indexes, or when we can know by
bitmap overlap that the only interesting cases are summarizing
indexes?

You’re right, there’s not a lot of new overhead in that case except what happens in ExecIndexesRequiringUpdates() to scan over the list of IndexInfo. It is really only when there are many expressions/predicates requiring examination that there is any significant cost to this approach AFAICT (but if you see something please point it out).

See the attached approach. Evaluation of the expressions only has to
happen if there are any HOT-blocking attributes which are exclusively
hot-blockingly indexed through expressions, so if the updated
attribute numbers are a subset of hotblockingexprattrs. (substitute
hotblocking with summarizing for the summarizing approach)

I believe I've incorporated the gist of your idea in this v5 patch, let me know if I missed something.

I would've implemented this with (1) two new bitmaps, one each for
normal and summarizing indexes, each containing which columns are
exclusively used in expression indexes (and which should thus be used
to trigger the (comparatively) expensive recalculation).

That was one where I started, over time that became harder to work as the bitmaps contain the union of index attributes for the table not per-column.

I think it's fairly easy to create, though.

Now there is one bitmap to cover the broadest case and then a function to find the modified set of indexes where each is examined against bitmaps that contain only attributes specific to the index in question. This helped in cases where there were both expression and non-expression indexes on the same attribute.

Fair, but do we care about one expression index on (attr1->>'data')'s
value *not* changing when an index on (attr1) exists and attr1 has
changed? That index on att1 would block HOT updates regardless of the
(lack of) changes to the (att1->>'data') index, so doing those
expensive calculations seems quite wasteful.

Agreed, when both a non-expression and an expression index exist on the same attribute then the expression checks are unnecessary and should be avoided. In this v5 patchset this case becomes two checks of bitmaps (first hot_attrs, then exclusively exp_attrs) before proceeding with a non-HOT update.

So, in my opinion, we should also keep track of those attributes only
included in expressions of indexes, and that's fairly easy: see
attached prototype.diff.txt (might need some work, the patch was
drafted on v16's codebase, but the idea is clear).

Thank you for your patch, I've included and expanded it.

The resulting %exprattrs bitmap contains attributes that are used only
in expressions of those index types.

The "new" output of these expression
evaluations would be stored to be used later as index datums, reducing
the number of per-expression evaluations down to 2 at most, rather
than 2+1 when the index needs an insertion but the expression itself
wasn't updated.

Not reforming the new index tuples is also an interesting optimization. I wonder how that can be passed from within heapam’s call into a function in execIndexing up into nodeModifiyTable and back down into execIndexing and on to the index access method? I’ll have to think about that, ideas welcome.

Note that index tuple forming happens only in the index AM, it's the
Datum construction (i.e. projection from attributes/tuple to indexed
value) that I'd like to deduplicate. Though looking at the current
code, I don't think it's reasonable to have that as a requirement for
this work. It'd be a nice-to-have for sure, but not as requirement.

Agreed that it's a nice-to-have, but not a priority.

Show quoted text

Do you have any documentation on the approaches used, and the specific
differences between v3 and v4? I don't see much of that in your
initial mail, and the patches themselves also don't show much of that
in their details. I'd like at least some documentation of the new
behaviour in src/backend/access/heap/README.HOT at some point before
this got marked as RFC in the commitfest app, though preferably sooner
rather than later.

Good point, I should have updated README.HOT with the initial patchset. I’ll jump on that and update ASAP.

Thanks in advance.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)
<prototype.diff.txt>

Attachments:

v5-0001-Expand-HOT-update-path-to-include-expression-and-.patchapplication/octet-stream; name=v5-0001-Expand-HOT-update-path-to-include-expression-and-.patchDownload
From 263bf11af1fd4d4cd30b5aa4a9a0692162141eb1 Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v5] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/ref/create_table.sgml            |  17 +
 doc/src/sgml/storage.sgml                     |  21 +
 src/backend/access/common/reloptions.c        |  12 +-
 src/backend/access/heap/README.HOT            |  43 +-
 src/backend/access/heap/heapam.c              | 115 ++--
 src/backend/access/heap/heapam_handler.c      |  27 +-
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/catalog/index.c                   |  15 +
 src/backend/catalog/indexing.c                |  51 +-
 src/backend/executor/execIndexing.c           | 238 ++++++-
 src/backend/executor/execReplication.c        |  10 +-
 src/backend/executor/nodeModifyTable.c        |  13 +-
 src/backend/utils/cache/relcache.c            |  58 +-
 src/bin/psql/tab-complete.in.c                |   2 +-
 src/include/access/heapam.h                   |   5 +-
 src/include/access/tableam.h                  |  28 +-
 src/include/executor/executor.h               |   8 +-
 src/include/nodes/execnodes.h                 |   8 +
 src/include/utils/rel.h                       |  14 +
 src/include/utils/relcache.h                  |   1 +
 .../regress/expected/heap_hot_updates.out     | 586 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 440 +++++++++++++
 23 files changed, 1550 insertions(+), 172 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 0a3e520f215..e29c9ea45a7 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1981,6 +1981,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..f40ada9c989 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  and index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determin this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 59fb53e7707..c081611926e 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1903,7 +1912,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..48aab81fdf0 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,18 +133,26 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results are used when check for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
@@ -152,9 +160,7 @@ all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
 indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+the summarizing indexes that have updated values.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fa7935a0ed3..a8bbfe1b2b5 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3164,12 +3164,13 @@ TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			Bitmapset **modified_indexes, struct EState *estate)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3192,7 +3193,6 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		use_hot_update = false;
-	bool		summarized_update = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
 	bool		all_visible_cleared_new = false;
@@ -3246,6 +3246,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3307,10 +3309,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		tmfd->ctid = *otid;
 		tmfd->xmax = InvalidTransactionId;
 		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3608,10 +3610,10 @@ l2:
 			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
-		*update_indexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -3926,30 +3928,91 @@ l2:
 	 * one pin is held.
 	 */
 
-	if (newbuf == buffer)
+	if (modified_indexes && newbuf == buffer)
 	{
+		bool		only_summarizing = false;
+		bool		already_checked = false;
+		int			num_indexes = list_length(relation->rd_indexinfolist);
+
 		/*
 		 * 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.
+		 * to do a HOT update.  As a reminder, hot_attrs includes attributes
+		 * used in expressions, but not attributes used by summarizing
+		 * indexes.
 		 */
 		if (!bms_overlap(modified_attrs, hot_attrs))
 		{
+			*modified_indexes = bms_add_member(*modified_indexes, num_indexes);
 			use_hot_update = true;
-
+		}
+		else
+		{
 			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
+			 * We may still be able to use the HOT update path if the reason
+			 * for the overlap is an unchanged expression.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
-				summarized_update = true;
+			if (bms_overlap(modified_attrs, exp_attrs))
+			{
+				*modified_indexes =
+					ExecIndexesRequiringUpdates(relation, modified_attrs,
+												estate, &oldtup, newtup,
+												&only_summarizing);
+				already_checked = true;
+				if (bms_is_empty(*modified_indexes) || only_summarizing)
+				{
+					/*
+					 * When no indexes were updated, or the only indexes
+					 * updated were summarizing indexes, we can use the HOT
+					 * path.  We ensure that the bitmapset isn't NULL by
+					 * adding in an index that can't match later when we
+					 * filter to determine which indexes to update and which
+					 * to skip in execIndexing.  We do that so as to signal
+					 * the need to filter which indexes are updated.
+					 */
+					*modified_indexes = bms_add_member(*modified_indexes, num_indexes);
+					use_hot_update = true;
+				}
+				else
+				{
+					/*
+					 * When any index requires updates, then all indexes must
+					 * be updated and this update cannot be HOT.  We signal
+					 * that by setting modified_indexes to NULL which
+					 * indicates on need to filter indexes out during
+					 * execIndexing.
+					 */
+					bms_free(*modified_indexes);
+					*modified_indexes = NULL;
+				}
+			}
+		}
+
+		/*
+		 * Summarizing indexes need not prevent a HOT update when their
+		 * attributes are modified like other index types, but their indexed
+		 * values do need to be updated. Let's find out which indexes need to
+		 * be updated.
+		 */
+		if (!already_checked && bms_overlap(modified_attrs, sum_attrs))
+		{
+			bms_free(*modified_indexes);
+			*modified_indexes =
+				ExecIndexesRequiringUpdates(relation, modified_attrs,
+											estate, &oldtup, newtup,
+											&only_summarizing);
+			if (only_summarizing)
+				use_hot_update = true;
 		}
 	}
 	else
 	{
+		/*
+		 * We're not able on the HOT update path. Setting modified_indexes to
+		 * NULL is the signal to update all indexes.
+		 */
+		if (modified_indexes)
+			*modified_indexes = NULL;
+
 		/* Set a hint that the old page could use prune/defrag */
 		PageSetFull(page);
 	}
@@ -4106,27 +4169,10 @@ l2:
 		heap_freetuple(heaptup);
 	}
 
-	/*
-	 * If it is a HOT update, the update may still need to update summarized
-	 * indexes, lest we fail to update those summaries and get incorrect
-	 * results (for example, minmax bounds of the block may change with this
-	 * update).
-	 */
-	if (use_hot_update)
-	{
-		if (summarized_update)
-			*update_indexes = TU_Summarizing;
-		else
-			*update_indexes = TU_None;
-	}
-	else
-		*update_indexes = TU_All;
-
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
-	bms_free(sum_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4403,8 +4449,7 @@ HeapDetermineColumnsInfo(Relation relation,
  * via ereport().
  */
 void
-simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-				   TU_UpdateIndexes *update_indexes)
+simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
@@ -4413,7 +4458,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, update_indexes);
+						 &tmfd, &lockmode, NULL, 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 c0bec014154..84f72b6ed33 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -312,8 +312,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+					Bitmapset **modified_indexes, EState *estate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -324,30 +324,9 @@ 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, update_indexes);
+						 tmfd, lockmode, modified_indexes, estate);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
-	/*
-	 * Decide whether new index entries are needed for the tuple
-	 *
-	 * Note: heap_update returns the tid (location) of the new tuple in the
-	 * t_self field.
-	 *
-	 * If the update is not HOT, we must update all indexes. If the update is
-	 * HOT, it could be that we updated summarized columns, so we either
-	 * update only summarized indexes, or none at all.
-	 */
-	if (result != TM_Ok)
-	{
-		Assert(*update_indexes == TU_None);
-		*update_indexes = TU_None;
-	}
-	else if (!HeapTupleIsHeapOnly(tuple))
-		Assert(*update_indexes == TU_All);
-	else
-		Assert((*update_indexes == TU_Summarizing) ||
-			   (*update_indexes == TU_None));
-
 	if (shouldFree)
 		pfree(tuple);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e18a8f8250f..2b5fe33e43b 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -334,8 +334,7 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
-						  Snapshot snapshot,
-						  TU_UpdateIndexes *update_indexes)
+						  Snapshot snapshot)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
@@ -345,7 +344,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode, NULL, NULL);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index cdabf780244..bf9d558a8c7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2455,7 +2455,20 @@ BuildIndexInfo(Relation index)
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
+	{
 		ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   indexStruct->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* collect attributes used in the expression, if one is present */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate, if one is present */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
@@ -2466,6 +2479,8 @@ BuildIndexInfo(Relation index)
 								 &ii->ii_ExclusionStrats);
 	}
 
+	ii->ii_OpClassDataTypes = index->rd_opcintype;
+
 	return ii;
 }
 
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc87..3fdebba3f07 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -72,8 +72,7 @@ CatalogCloseIndexes(CatalogIndexState indstate)
  * This is effectively a cut-down version of ExecInsertIndexTuples.
  */
 static void
-CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
-				   TU_UpdateIndexes updateIndexes)
+CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 {
 	int			i;
 	int			numIndexes;
@@ -83,20 +82,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	IndexInfo **indexInfoArray;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	bool		onlySummarized = (updateIndexes == TU_Summarizing);
 
-	/*
-	 * HOT update does not require index inserts. But with asserts enabled we
-	 * want to check that it'd be legal to currently insert into the
-	 * table/index.
-	 */
-#ifndef USE_ASSERT_CHECKING
-	if (HeapTupleIsHeapOnly(heapTuple) && !onlySummarized)
-		return;
-#endif
-
-	/* When only updating summarized indexes, the tuple has to be HOT. */
-	Assert((!onlySummarized) || HeapTupleIsHeapOnly(heapTuple));
+	/* The tuple never be HOT. */
+	Assert(!HeapTupleIsHeapOnly(heapTuple));
 
 	/*
 	 * Get information from the state structure.  Fall out if nothing to do.
@@ -138,22 +126,6 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 		Assert(index->rd_index->indimmediate);
 		Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
 
-		/* see earlier check above */
-#ifdef USE_ASSERT_CHECKING
-		if (HeapTupleIsHeapOnly(heapTuple) && !onlySummarized)
-		{
-			Assert(!ReindexIsProcessingIndex(RelationGetRelid(index)));
-			continue;
-		}
-#endif							/* USE_ASSERT_CHECKING */
-
-		/*
-		 * Skip insertions into non-summarizing indexes if we only need to
-		 * update summarizing indexes.
-		 */
-		if (onlySummarized && !indexInfo->ii_Summarizing)
-			continue;
-
 		/*
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
@@ -240,7 +212,7 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup)
 
 	simple_heap_insert(heapRel, tup);
 
-	CatalogIndexInsert(indstate, tup, TU_All);
+	CatalogIndexInsert(indstate, tup);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -260,7 +232,7 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 
 	simple_heap_insert(heapRel, tup);
 
-	CatalogIndexInsert(indstate, tup, TU_All);
+	CatalogIndexInsert(indstate, tup);
 }
 
 /*
@@ -291,7 +263,7 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
 
 		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
 		tuple->t_tableOid = slot[i]->tts_tableOid;
-		CatalogIndexInsert(indstate, tuple, TU_All);
+		CatalogIndexInsert(indstate, tuple);
 
 		if (should_free)
 			heap_freetuple(tuple);
@@ -313,15 +285,14 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
-	TU_UpdateIndexes updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -337,13 +308,11 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	TU_UpdateIndexes updateIndexes = TU_All;
-
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup);
 }
 
 /*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 7c87f012c30..094bf499028 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -117,6 +117,8 @@
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -168,6 +170,7 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 	IndexInfo **indexInfoArray;
 
 	resultRelInfo->ri_NumIndices = 0;
+	resultRelation->rd_indexinfolist = NIL;
 
 	/* fast path if no indexes */
 	if (!RelationGetForm(resultRelation)->relhasindex)
@@ -210,6 +213,10 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		/* extract index key information from the index's pg_index info */
 		ii = BuildIndexInfo(indexDesc);
 
+		/* used when determining if indexed values changed during update */
+		resultRelation->rd_indexinfolist =
+			lappend(resultRelation->rd_indexinfolist, ii);
+
 		/*
 		 * If the indexes are to be used for speculative insertion or conflict
 		 * detection in logical replication, add extra information required by
@@ -307,7 +314,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 					  bool noDupErr,
 					  bool *specConflict,
 					  List *arbiterIndexes,
-					  bool onlySummarizing)
+					  Bitmapset *modified_indexes)
 {
 	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
@@ -364,10 +371,12 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			continue;
 
 		/*
-		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * If modified_indexes is NULL, we're not in a HOT update, so we
+		 * should update all indexes.  If it's not NULL, we should only update
+		 * the listed indexes.  This list will include any summarizing indexes
+		 * that require updates on the HOT path as well.
 		 */
-		if (onlySummarizing && !indexInfo->ii_Summarizing)
+		if (update && modified_indexes && !bms_is_member(i, modified_indexes))
 			continue;
 
 		/* Check for partial index */
@@ -1089,6 +1098,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1166,3 +1178,221 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine which indexes must be invoked during ExecIndexInsertTuple().
+ *
+ * That will include: any index on a column where there is an attribute that was
+ * modified, any expression index where the expression's value updated, any
+ * index used in a constraint, and any summarizing index.
+ */
+Bitmapset *
+ExecIndexesRequiringUpdates(Relation relation,
+							Bitmapset *modified_attrs,
+							EState *estate,
+							HeapTuple old_tuple,
+							HeapTuple new_tuple,
+							bool *only_summarizing)
+{
+	ListCell   *lc;
+	List	   *indexinfolist = relation->rd_indexinfolist;
+	ExprContext *econtext = NULL;
+	int			num_summarizing = 0;
+	TupleDesc	tupledesc;
+	TupleTableSlot *old_tts,
+			   *new_tts;
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	Bitmapset  *result = NULL;
+
+	/*
+	 * Examine each index on this relation relative to the changes between old
+	 * and new tuples.
+	 */
+	foreach(lc, indexinfolist)
+	{
+		IndexInfo  *indexInfo = (IndexInfo *) lfirst(lc);
+
+		/*
+		 * Summarizing indexes don't prevent HOT updates, but do require that
+		 * they are updated when necessary so we include it in the set.
+		 */
+		if (indexInfo->ii_Summarizing)
+		{
+			if (bms_overlap(indexInfo->ii_IndexAttrs, modified_attrs))
+			{
+				num_summarizing++;
+				result = bms_add_member(result, foreach_current_index(lc));
+			}
+			continue;
+		}
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (expression_checks && estate != NULL &&
+			bms_overlap(indexInfo->ii_PredicateAttrs, modified_attrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = bms_add_member(result, foreach_current_index(lc));
+				continue;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modified_attrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			bool		changed = false;
+
+			/*
+			 * Assume the index is changed when we don't have an estate
+			 * context to use or the reloption is disabled.
+			 */
+			if (!expression_checks || estate == NULL)
+			{
+				result = bms_add_member(result, foreach_current_index(lc));
+				continue;
+			}
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			indexInfo->ii_ExpressionsState = NIL;
+
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+
+			for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+			{
+				if (old_isnull[i] != new_isnull[i])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[i])
+				{
+					int16		elmlen;
+					bool		elmbyval;
+					Oid			opcintyp = indexInfo->ii_OpClassDataTypes[i];
+
+					get_typlenbyval(opcintyp, &elmlen, &elmbyval);
+					if (!datum_image_eq(old_values[i], new_values[i],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			if (changed)
+				result = bms_add_member(result, foreach_current_index(lc));
+
+			/* Shortcut index_unchanged_by_update(), we know the answer. */
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+			continue;
+		}
+
+		/*
+		 * If the index references modified attributes then it needs to be
+		 * updated.
+		 */
+		if (bms_overlap(indexInfo->ii_IndexAttrs, modified_attrs))
+			result = bms_add_member(result, foreach_current_index(lc));
+	}
+
+	if (econtext)
+	{
+		ExecDropSingleTupleTableSlot(old_tts);
+		ExecDropSingleTupleTableSlot(new_tts);
+	}
+
+	*only_summarizing = (list_length(indexinfolist) > 0 &&
+						 num_summarizing == bms_num_members(result));
+
+	return result;
+}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 5f7613cc831..dd5108dc8f2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -639,9 +639,9 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
 		List	   *conflictindexes;
 		bool		conflict = false;
+		Bitmapset  *modified_indexes = NULL;
 
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
@@ -655,17 +655,16 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
-		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
-								  &update_indexes);
+		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
 
-		if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
+		if (resultRelInfo->ri_NumIndices > 0)
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   conflictindexes ? true : false,
 												   &conflict, conflictindexes,
-												   (update_indexes == TU_Summarizing));
+												   modified_indexes);
 
 		/*
 		 * Refer to the comments above the call to CheckAndReportConflict() in
@@ -683,6 +682,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 							 recheckIndexes, NULL, false);
 
 		list_free(recheckIndexes);
+		bms_free(modified_indexes);
 	}
 }
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a15e7863b0d..98ae771c8f7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -120,8 +120,12 @@ typedef struct ModifyTableContext
  */
 typedef struct UpdateContext
 {
+	Bitmapset  *modifiedIndexes;	/* Either NULL indicating that all indexes
+									 * should be updated or a bitmap of
+									 * IndexInfo array positions for the
+									 * subset of modified indexes requiring
+									 * updates. */
 	bool		crossPartUpdate;	/* was it a cross-partition update? */
-	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
 
 	/*
 	 * Lock mode to acquire on the latest tuple version before performing
@@ -2283,7 +2287,8 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&updateCxt->modifiedIndexes,
+								estate);
 
 	return result;
 }
@@ -2303,12 +2308,12 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	List	   *recheckIndexes = NIL;
 
 	/* insert index entries for tuple if necessary */
-	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
+	if (resultRelInfo->ri_NumIndices > 0)
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   updateCxt->modifiedIndexes);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 398114373e9..f1ddf1ea505 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5229,7 +5229,11 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in
+										 * expressions */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in
+										 * expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5252,6 +5256,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5295,7 +5301,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5309,6 +5317,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5352,14 +5361,20 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = indexDesc->rd_index->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5375,30 +5390,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5427,11 +5440,27 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {hot-blocking columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	/* {summarized-only columns} = {summarized columns} - {direct columns} */
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	/* {summarized columns} = {direct columns} + {summarized-only columns} */
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5444,6 +5473,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5458,6 +5489,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_expressionattr = bms_copy(hotblockingexprattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5474,6 +5506,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return hotblockingexprattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index a9a81ab3c14..1c4cee75157 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2967,7 +2967,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f7..e12173eba42 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -339,7 +340,7 @@ extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 Bitmapset **modified_indexes, struct EState *estate);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -374,7 +375,7 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+							   HeapTuple tup);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 131c050c15f..300226d44d5 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -38,6 +38,7 @@ struct IndexInfo;
 struct SampleScanState;
 struct VacuumParams;
 struct ValidateIndexState;
+struct EState;
 
 /*
  * Bitmask values for the flags argument to the scan_begin callback.
@@ -109,21 +110,6 @@ typedef enum TM_Result
 	TM_WouldBlock,
 } TM_Result;
 
-/*
- * Result codes for table_update(..., update_indexes*..).
- * Used to determine which indexes to update.
- */
-typedef enum TU_UpdateIndexes
-{
-	/* No indexed columns were updated (incl. TID addressing of tuple) */
-	TU_None,
-
-	/* A non-summarizing indexed column was updated, or the TID has changed */
-	TU_All,
-
-	/* Only summarized columns were updated, TID is unchanged */
-	TU_Summarizing,
-} TU_UpdateIndexes;
 
 /*
  * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail
@@ -550,7 +536,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 Bitmapset **modified_indexes,
+								 struct EState *estate);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1541,12 +1528,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   Bitmapset **modified_indexes, struct EState *estate)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 modified_indexes, estate);
 }
 
 /*
@@ -2075,8 +2062,7 @@ extern void simple_table_tuple_insert(Relation rel, TupleTableSlot *slot);
 extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
-									  TupleTableSlot *slot, Snapshot snapshot,
-									  TU_UpdateIndexes *update_indexes);
+									  TupleTableSlot *slot, Snapshot snapshot);
 
 
 /* ----------------------------------------------------------------------------
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 30e2a82346f..1018bb2afa5 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -650,7 +650,7 @@ extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   bool update,
 								   bool noDupErr,
 								   bool *specConflict, List *arbiterIndexes,
-								   bool onlySummarizing);
+								   Bitmapset *modified_indexes);
 extern bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo,
 									  TupleTableSlot *slot,
 									  EState *estate, ItemPointer conflictTid,
@@ -661,6 +661,12 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern Bitmapset *ExecIndexesRequiringUpdates(Relation relation,
+											  Bitmapset *modified_attrs,
+											  EState *estate,
+											  HeapTuple old_tuple,
+											  HeapTuple new_tuple,
+											  bool *only_summarizing);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e2d1dc1e067..54035aeb9f8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -159,12 +159,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -183,6 +186,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -194,10 +198,13 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
@@ -217,6 +224,7 @@ typedef struct IndexInfo
 	int			ii_ParallelWorkers;
 	Oid			ii_Am;
 	void	   *ii_AmCache;
+	Oid		   *ii_OpClassDataTypes;
 	MemoryContext ii_Context;
 } IndexInfo;
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index db3e504c3d2..3067379b036 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -154,6 +154,10 @@ typedef struct RelationData
 	bool		rd_ispkdeferrable;	/* is rd_pkindex a deferrable PK? */
 	Oid			rd_replidindex; /* OID of replica identity index, if any */
 
+	/* list of IndexInfo for this relation */
+	List	   *rd_indexinfolist;	/* an ordered list of IndexInfo for
+									 * indexes on relation */
+
 	/* data managed by RelationGetStatExtList: */
 	List	   *rd_statlist;	/* list of OIDs of extended stats */
 
@@ -164,6 +168,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -344,6 +349,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -405,6 +411,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339e..0cc28cb97e2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -63,6 +63,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..2ad6e5418e9
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,586 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions         
+---------------------------
+ {expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions        
+--------------------------
+ {expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+               reloptions                
+-----------------------------------------
+ {fillfactor=60,expression_checks=false}
+(1 row)
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            0 |                0 |                            |             6 |           1 |                       16.67
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX expression ON ex USING btree((att1->'data'));
+CREATE INDEX summarizing ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and not
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate!"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 both indexes updated
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e63ee2cf2bb..f9def3d93aa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..7728075a7ae
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,440 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX expression ON ex USING btree((att1->'data'));
+CREATE INDEX summarizing ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and not
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate!"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 both indexes updated
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
-- 
2.42.0

#13Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#12)
Re: Expanding HOT updates for expression and partial indexes

On Thu, 13 Feb 2025 at 19:46, Burd, Greg <gregburd@amazon.com> wrote:

Attached find an updated patchset v5 that is an evolution of v4.

Changes v4 to v5 are:
* replaced GUC with table reloption called "expression_checks" (open to other name ideas)
* minimal documentation updates to README.HOT to address changes
* avoid, when possible, the expensive path that requires evaluating an estate using bitmaps
* determines the set of summarized indexes requiring updates, only updates those
* more tests in heap_hot_updates.sql (perhaps too many...)
* rebased to master, formatted, and make check-world passes

Thank you for the update. Below some comments, in no particular order.

-----

I'm not a fan of how you replaced TU_UpdateIndexes with a bitmap. It
seems unergonomic and a waste of performance.

In HEAD, we don't do any expensive computations for the fast path of
"indexed attribute updated" - at most we do the bitmap compare and
then set a pointer. With this patch, the way we signal that is by
allocating a bitmap of size O(n_indexes). That's potentially quite
expensive (given 1000s of indexes), and definitely more expensive than
only a pointer assignment.
In HEAD, we have a clear indication of which classes of indexes to
update, with TU_UpdateIndexes. With this patch, we have to derive that
from the (lack of) bits in the bitmap that might be output by the
table_update procedure.

I think we can do with an additional parameter for which indexes would
be updated (or store that info in the parameter which also will hold
EState et al). I think it's cheaper that way, too - only when
update_indexes could be TU_SUMMARIZING we might need the exact
information for which indexes to insert new tuples into, and it only
really needs to be sized to the number of summarizing indexes (usually
small/nonexistent, but potentially huge).

-----

I think your patch design came from trying to include at least two
distinct optimizations:
1) to make the HOT-or-not check include whether the expressions of
indexes were updated, and
2) to only insert index tuples into indexes that got updated values
when table_tuple_update returns update_indexes=TU_Summarizing.

While they touch similar code (clearly seen here), I think those
should be implemented in different patches. For (1), the current API
surface is good enough when the EState is passed down. For (2), you'll
indeed also need an additional argument we can use to fill with the
right summarizing indexes, but I don't think that can nor should
replace the function of TU_UpdateIndexes.

If you agree with my observation of those being distinct
optimizations, could you split this patch into parts (but still within
the same series) so that these are separately reviewable?

-----

I notice that ExecIndexesRequiringUpdates() does work on all indexes,
rather than just indexes relevant to this exact phase of checking. I
think that is a waste of time, so if we sort the indexes in order of
[hotblocking without expressions, hotblocking with expressions,
summarizing], then (with stored start/end indexes) we can save time in
cases where there are comparatively few of the types we're not going
to look at.

As an extreme example: we shouldn't do the (comparatively) expensive
work evaluating expressions to determine which of 1000s of summarizing
indexes has been updated when we're still not sure if we can apply HOT
at all.

(Sidenote: Though, arguably, we could be smarter by skipping index
insertions into unmodified summarizing indexes altogether regardless
of HOT status, as long as the update is on the same page - but that's
getting ahead of ourselves and not relevant to this discussion.)

-----

I noticed you've disabled any passing of "HOT or not" in the
simple_update cases, and have done away with the various checks that
are in place to prevent corruption. I don't think that's a great idea,
it's quite likely to cause bugs.

-----

You're extracting type info from the opclass, to use in
datum_image_eq(). Couldn't you instead use the index relation's
TupleDesc and its stored attribute information instead? That saves us
from having to do further catalog lookups during execution. I'm also
fairly sure that that information is supposed to be a more accurate
representation of attributes' expression output types than the
opclass' type information (though, they probably should match).

-----

The operations applied in ExecIndexesRequiringUpdates partially
duplicate those done in index_unchanged_by_update. Can we (partially)
unify this, and pass which indexes were updated through the IndexInfo,
rather than the current bitmap?

-----

I don't see a good reason to add IndexInfo to Relation, by way of
rd_indexInfoList. It seems like an ad-hoc way of passing data around,
and I don't think that's the right way.

I mean, it's clear that "updated indexed column's value == non-HOT
update". And that to determine whether an updated *projected* column's
value (i.e., expression index column's value) was actually updated we
need to calculate the previous and current index value, thus execute
the projection twice. But why would we have significant additional
overhead if there are no expression indexes, or when we can know by
bitmap overlap that the only interesting cases are summarizing
indexes?

See the attached approach. Evaluation of the expressions only has to
happen if there are any HOT-blocking attributes which are exclusively
hot-blockingly indexed through expressions, so if the updated
attribute numbers are a subset of hotblockingexprattrs. (substitute
hotblocking with summarizing for the summarizing approach)

I believe I've incorporated the gist of your idea in this v5 patch, let me know if I missed something.

Seems about accurate.

Show quoted text

I would've implemented this with (1) two new bitmaps, one each for
normal and summarizing indexes, each containing which columns are
exclusively used in expression indexes (and which should thus be used
to trigger the (comparatively) expensive recalculation).

That was one where I started, over time that became harder to work as the bitmaps contain the union of index attributes for the table not per-column.

I think it's fairly easy to create, though.

Now there is one bitmap to cover the broadest case and then a function to find the modified set of indexes where each is examined against bitmaps that contain only attributes specific to the index in question. This helped in cases where there were both expression and non-expression indexes on the same attribute.

Fair, but do we care about one expression index on (attr1->>'data')'s
value *not* changing when an index on (attr1) exists and attr1 has
changed? That index on att1 would block HOT updates regardless of the
(lack of) changes to the (att1->>'data') index, so doing those
expensive calculations seems quite wasteful.

Agreed, when both a non-expression and an expression index exist on the same attribute then the expression checks are unnecessary and should be avoided. In this v5 patchset this case becomes two checks of bitmaps (first hot_attrs, then exclusively exp_attrs) before proceeding with a non-HOT update.

So, in my opinion, we should also keep track of those attributes only
included in expressions of indexes, and that's fairly easy: see
attached prototype.diff.txt (might need some work, the patch was
drafted on v16's codebase, but the idea is clear).

Thank you for your patch, I've included and expanded it.

#14Burd, Greg
gregburd@amazon.com
In reply to: Burd, Greg (#1)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Matthias,

First off, I can't thank you enough for taking the time to review in detail the patch. I appreciate and value your time and excellent feedback.

Second, I think that I should admit to the fact that I've also been working on making PHOT functional again. I have it rebased against master, however it is still at the proof-of-concept phase and there are some larger issues to flesh out. One of the changes in this patch would have enabled that future with PHOT, specifically the bitmap as the method for conveying what indexes need updating.

That said, I agree that this patch should simply focus on expanding HOT to expression indexes and partial indexes. With that in mind I will return to the TU_UpdateIndexes approach, even though I'm not a fan of it, and later propose the bitmap approach (or something better) as part of PHOT if and when that's ready.

Changes v5 to v6:
* reverted to TU_UpdateIndexes rather than Bitmapset
* renamed ExecIndexesRequiringUpdates to ExecIndexesExpressionsWereNotUpdated
* ExecIndexesExpressionsWereNotUpdated returns bool, exits early if possible
* simple_update paths can be HOT once again
* removed efforts to determine the subset of updated summarizing indexes
* create filtered IndexInfo list in relcache containing only indexes with expressions
* now using index TupleDesc CompactAttributes for arguments to datum_is_equal() <- did I get this one right, I'm not sure it is what you had in mind

best.

-greg

On Feb 15, 2025, at 5:49 AM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Thu, 13 Feb 2025 at 19:46, Burd, Greg <gregburd@amazon.com> wrote:

-----

I'm not a fan of how you replaced TU_UpdateIndexes with a bitmap. It
seems unergonomic and a waste of performance.

In HEAD, we don't do any expensive computations for the fast path of
"indexed attribute updated" - at most we do the bitmap compare and
then set a pointer. With this patch, the way we signal that is by
allocating a bitmap of size O(n_indexes). That's potentially quite
expensive (given 1000s of indexes), and definitely more expensive than
only a pointer assignment.

Fair point. I'm not a fan of the TU_UpdateIndexes enum, but I appreciate your argument against the bitmapset. Under the conditions you describe (1000s of indexes) it could grow unwieldy and impact performance.

In HEAD, we have a clear indication of which classes of indexes to
update, with TU_UpdateIndexes. With this patch, we have to derive that
from the (lack of) bits in the bitmap that might be output by the
table_update procedure.

Yes, but... that "clear indication" is lacking the ability to convey more detailed information. It doesn't tell you which summarizing indexes really need updating just that as a result of being on the HOT path all summarizing indexes require updates.

I think we can do with an additional parameter for which indexes would
be updated (or store that info in the parameter which also will hold
EState et al). I think it's cheaper that way, too - only when
update_indexes could be TU_SUMMARIZING we might need the exact
information for which indexes to insert new tuples into, and it only
really needs to be sized to the number of summarizing indexes (usually
small/nonexistent, but potentially huge).

Okay, yes with this patch we need only concern ourselves with all, none, or some subset of summarizing as before. I'll work on the opaque parameter next iteration.

-----

I think your patch design came from trying to include at least two
distinct optimizations:
1) to make the HOT-or-not check include whether the expressions of
indexes were updated, and
2) to only insert index tuples into indexes that got updated values
when table_tuple_update returns update_indexes=TU_Summarizing.

It did. (1) for sure, but the second is more related to the PHOT work (as mentioned above). With that work almost all updates are on the HOT/PHOT path and so the bitmap of changed indexes is small. It would take an update of a table with 1000s of indexes where almost all those were modified to create a bitmap that was large, which certainly could (and somewhere likely does) exist, but it's not the common case (I'd imagine). In that case the work to update all those indexes will likely dwarf the work to build that bitmap, but I could be wrong.

But you're fundamentally right, I'm conflating two ideas and I shouldn't. I will focus the changes required for this idea without pulling in changes useful to a future ones.

While they touch similar code (clearly seen here), I think those
should be implemented in different patches. For (1), the current API
surface is good enough when the EState is passed down. For (2), you'll
indeed also need an additional argument we can use to fill with the
right summarizing indexes, but I don't think that can nor should
replace the function of TU_UpdateIndexes.

Okay, I'll combine the earlier v3 patch with the changes in v5. That should leave TU_UpdateIndexes in place and allow for HOT with expression indexes. I'll change the ExecIndexesRequiringUpdates() a bit (and rename it) so that it is just a boolean test for any index that spoils the HOT path and can exit early in that case potentially avoiding extra work.

If you agree with my observation of those being distinct
optimizations, could you split this patch into parts (but still within
the same series) so that these are separately reviewable?

I agree, but I think that a single simple more focused patch will suffice.

-----

I notice that ExecIndexesRequiringUpdates() does work on all indexes,
rather than just indexes relevant to this exact phase of checking. I
think that is a waste of time, so if we sort the indexes in order of
[hotblocking without expressions, hotblocking with expressions,
summarizing], then (with stored start/end indexes) we can save time in
cases where there are comparatively few of the types we're not going
to look at.

If I plan on just having ExecIndexesRequiringUpdates() return a bool rather than a bitmap then sorting, or even just filtering the list of IndexInfo to only include indexes with expressions, makes sense. That way the only indexes in question in that function's loop will be those that may spoil the HOT path. When that list is length 0, we can skip the tests entirely.

As an extreme example: we shouldn't do the (comparatively) expensive
work evaluating expressions to determine which of 1000s of summarizing
indexes has been updated when we're still not sure if we can apply HOT
at all.

That makes sense, and your sorting idea would inform that kind of work. I'll keep that in mind if I reintroduce code that aims to only update changed summarized indexes.

(Sidenote: Though, arguably, we could be smarter by skipping index
insertions into unmodified summarizing indexes altogether regardless
of HOT status, as long as the update is on the same page - but that's
getting ahead of ourselves and not relevant to this discussion.)

-----

I noticed you've disabled any passing of "HOT or not" in the
simple_update cases, and have done away with the various checks that
are in place to prevent corruption. I don't think that's a great idea,
it's quite likely to cause bugs.

Yes. I'll resurrect that.

-----

You're extracting type info from the opclass, to use in
datum_image_eq(). Couldn't you instead use the index relation's
TupleDesc and its stored attribute information instead? That saves us
from having to do further catalog lookups during execution. I'm also
fairly sure that that information is supposed to be a more accurate
representation of attributes' expression output types than the
opclass' type information (though, they probably should match).

I hadn't thought of that, I think it's a valid idea and I'll update accordingly. I think I understand what you are suggesting.

-----

The operations applied in ExecIndexesRequiringUpdates partially
duplicate those done in index_unchanged_by_update. Can we (partially)
unify this, and pass which indexes were updated through the IndexInfo,
rather than the current bitmap?

I think I do that now, feel free to say otherwise. When the expression is checked in ExecIndexesExpressionsWereNotUpdated() I set:

/* Shortcut index_unchanged_by_update(), we know the answer. */ indexInfo->ii_CheckedUnchanged = true; indexInfo->ii_IndexUnchanged = !changed;

That prevents duplicate effort in index_unchanged_by_update().

-----

I don't see a good reason to add IndexInfo to Relation, by way of
rd_indexInfoList. It seems like an ad-hoc way of passing data around,
and I don't think that's the right way.

At one point I'd created a way to get this set via relcache, I will resurrect that approach but I'm not sure it is what you were hinting at. The current method avoids pulling a the lock on the index to build the list, but doing that once in relcache isn't horrible. Maybe you were suggesting using that opaque struct to pass around the list of IndexInfo? Let me know on this one if you had a specific idea. The swap I've made in v6 really just moves the IndexInfo list to a filtered list with a new name created in relcache.

Attachments:

v6-0001-Expand-HOT-update-path-to-include-expression-and-.patchapplication/octet-stream; name=v6-0001-Expand-HOT-update-path-to-include-expression-and-.patchDownload
From 47aa9640223d2b95dbb074ffc780fa109053191b Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v6] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/ref/create_table.sgml            |  17 +
 doc/src/sgml/storage.sgml                     |  21 +
 src/backend/access/common/reloptions.c        |  12 +-
 src/backend/access/heap/README.HOT            |  43 +-
 src/backend/access/heap/heapam.c              |  43 +-
 src/backend/access/heap/heapam_handler.c      |   6 +-
 src/backend/access/table/tableam.c            |   2 +-
 src/backend/catalog/index.c                   |  15 +
 src/backend/executor/execIndexing.c           | 200 ++++++
 src/backend/executor/nodeModifyTable.c        |   3 +-
 src/backend/utils/cache/relcache.c            | 181 +++++-
 src/bin/psql/tab-complete.in.c                |   2 +-
 src/include/access/genam.h                    |   2 +-
 src/include/access/heapam.h                   |   3 +-
 src/include/access/tableam.h                  |  10 +-
 src/include/executor/executor.h               |   5 +
 src/include/nodes/execnodes.h                 |   8 +
 src/include/utils/rel.h                       |  14 +
 src/include/utils/relcache.h                  |   2 +
 .../regress/expected/heap_hot_updates.out     | 586 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 440 +++++++++++++
 22 files changed, 1560 insertions(+), 60 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 0a3e520f215..e29c9ea45a7 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1981,6 +1981,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..f40ada9c989 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  and index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determin this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 59fb53e7707..c081611926e 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1903,7 +1912,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..48aab81fdf0 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,18 +133,26 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results are used when check for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
@@ -152,9 +160,7 @@ all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
 indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+the summarizing indexes that have updated values.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fa7935a0ed3..46ce824c4ea 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3164,12 +3164,13 @@ TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3246,6 +3247,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3311,6 +3314,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3612,6 +3616,7 @@ l2:
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -3928,25 +3933,30 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		bool		expression_checks = RelationGetExpressionChecks(relation);
+
 		/*
 		 * 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.
+		 * to do a HOT update.  As a reminder, hot_attrs includes attributes
+		 * used in expressions, but not attributes used by summarizing
+		 * indexes.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
-		{
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(expression_checks == true && estate != NULL && exp_attrs &&
+			 bms_overlap(modified_attrs, exp_attrs) &&
+			 ExecIndexesExpressionsWereNotUpdated(relation, modified_attrs,
+												  estate, &oldtup, newtup)))
 			use_hot_update = true;
 
-			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
-			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
-				summarized_update = true;
-		}
+		/*
+		 * If none of the columns that are used in hot-blocking indexes were
+		 * updated, we can apply HOT, but we do still need to check if we need
+		 * to update the summarizing indexes, and update those indexes if the
+		 * columns were updated, or we may fail to detect e.g. value bound
+		 * changes in BRIN minmax indexes.
+		 */
+		if (use_hot_update && bms_overlap(modified_attrs, sum_attrs))
+			summarized_update = true;
 	}
 	else
 	{
@@ -4126,7 +4136,6 @@ l2:
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
-	bms_free(sum_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4413,7 +4422,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, update_indexes);
+						 &tmfd, &lockmode, update_indexes, 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 c0bec014154..8e9ab892d73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -312,8 +312,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+					TU_UpdateIndexes *update_indexes, EState *estate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -324,7 +324,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, update_indexes);
+						 tmfd, lockmode, update_indexes, estate);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e18a8f8250f..9feca8a296f 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -345,7 +345,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/index.c b/src/backend/catalog/index.c
index cdabf780244..c28857a0943 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2455,7 +2455,22 @@ BuildIndexInfo(Relation index)
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
+	{
 		ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   indexStruct->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+
+		ii->ii_CompactAttr[i] = TupleDescCompactAttr(RelationGetDescr(index), i);
+	}
+
+	/* collect attributes used in the expression, if one is present */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate, if one is present */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 7c87f012c30..7dce38dd08a 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -117,6 +117,8 @@
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -1089,6 +1091,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1166,3 +1171,198 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine if any index with an expression requires updating.
+ *
+ * This function will review all indexes with expressions to determine if the
+ * update from old to new tuple requires that the index be updated.  This is
+ * used to determine if a HOT update is possible or not.
+ *
+ * Returns true iff none of the expression indexes require updating due to the
+ * change from old to new tuple or when both the old and new tuples do not
+ * satisfy the predicate of the index.
+ */
+bool
+ExecIndexesExpressionsWereNotUpdated(Relation relation,
+									 Bitmapset *modified_attrs,
+									 EState *estate,
+									 HeapTuple old_tuple,
+									 HeapTuple new_tuple)
+{
+	ListCell   *lc;
+	List	   *indexinfolist = RelationGetExprIndexInfoList(relation);
+	ExprContext *econtext = NULL;
+	TupleDesc	tupledesc;
+	TupleTableSlot *old_tts = NULL;
+	TupleTableSlot *new_tts = NULL;
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	bool		changed = false;
+
+#ifndef USE_ASSERT_CHECKING
+
+	/*
+	 * Assume the index is changed when we don't have an estate context to use
+	 * or the reloption is disabled.
+	 */
+	if (!expression_checks || estate == NULL)
+		return changed;
+#endif
+
+	Assert(expression_checks == true);
+	Assert(estate != NULL);
+
+	/*
+	 * Examine expression and partial indexs on this relation relative to the
+	 * changes between old and new tuples.
+	 */
+	foreach(lc, indexinfolist)
+	{
+		IndexInfo  *indexInfo = (IndexInfo *) lfirst(lc);
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modified_attrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				changed = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modified_attrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			indexInfo->ii_ExpressionsState = NIL;
+
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+
+			for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+			{
+				if (old_isnull[i] != new_isnull[i])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[i])
+				{
+					int16		elmlen = indexInfo->ii_CompactAttr[i]->attlen;
+					bool		elmbyval = indexInfo->ii_CompactAttr[i]->attbyval;
+
+					if (!datum_image_eq(old_values[i], new_values[i],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			if (changed)
+			{
+				/* Shortcut index_unchanged_by_update(), we know the answer. */
+				indexInfo->ii_CheckedUnchanged = true;
+				indexInfo->ii_IndexUnchanged = !changed;
+				break;
+			}
+		}
+	}
+
+	if (econtext)
+	{
+		ExecDropSingleTupleTableSlot(old_tts);
+		ExecDropSingleTupleTableSlot(new_tts);
+	}
+
+	return !changed;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b0fe50075ad..d68f40cb998 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2283,7 +2283,8 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&updateCxt->updateIndexes,
+								estate);
 
 	return result;
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 398114373e9..b5e47b3521e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -5188,6 +5189,128 @@ RelationGetIndexPredicate(Relation relation)
 	return result;
 }
 
+ /*
+  * RelationGetExprIndexInfoList -- get a list of IndexInfo for expression
+  * indexes on this relation
+  */
+List *
+RelationGetExprIndexInfoList(Relation relation)
+{
+	ListCell   *lc;
+	List	   *indexoidlist;
+	List	   *newindexoidlist;
+	List	   *result = NULL;
+	List	   *oldlist;
+	Oid			relpkindex;
+	Oid			relreplindex;
+	MemoryContext oldcxt;
+
+	if (relation->rd_iiexprlist)
+		return list_copy(relation->rd_iiexprlist);
+
+	/* Fast path if definitely no indexes */
+	if (!RelationGetForm(relation)->relhasindex)
+		return NIL;
+
+restart:
+	indexoidlist = RelationGetIndexList(relation);
+
+	/* Fall out if no indexes (but relhasindex was set) */
+	if (indexoidlist == NIL)
+		return NIL;
+
+	/*
+	 * 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;
+
+	foreach(lc, indexoidlist)
+	{
+		Oid			oid = lfirst_oid(lc);
+		Datum		datum;
+		bool		isnull;
+		Node	   *indexExpressions;
+		Node	   *indexPredicate;
+		Relation	indexDesc;
+		IndexInfo  *indexInfo;
+
+		/*
+		 * 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.)
+		 */
+		indexDesc = index_open(oid, AccessShareLock);
+		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;
+
+		if (indexExpressions || indexPredicate)
+		{
+			oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+			indexInfo = BuildIndexInfo(indexDesc);
+			MemoryContextSwitchTo(oldcxt);
+			result = lappend(result, indexInfo);
+		}
+
+		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 IndexInfo.
+	 */
+	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);
+		list_free(result);
+
+		goto restart;
+	}
+
+	/* Now save a copy of the completed list in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldlist = relation->rd_iiexprlist;
+	relation->rd_iiexprlist = list_copy(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Don't leak the old list, if there is one */
+	if (oldlist)
+		list_free(oldlist);
+
+	return result;
+}
+
 /*
  * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
  *
@@ -5229,7 +5352,11 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in
+										 * expressions */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in
+										 * expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5252,6 +5379,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5295,7 +5424,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5309,6 +5440,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5352,14 +5484,20 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = indexDesc->rd_index->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5375,30 +5513,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5427,11 +5563,27 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {hot-blocking columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	/* {summarized-only columns} = {summarized columns} - {direct columns} */
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	/* {summarized columns} = {direct columns} + {summarized-only columns} */
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5444,6 +5596,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5458,6 +5612,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_expressionattr = bms_copy(hotblockingexprattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5474,6 +5629,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return hotblockingexprattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index eb8bc128720..f0a8286a25b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2992,7 +2992,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 1be8739573f..91af1e8238d 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -194,7 +194,7 @@ extern bool index_can_return(Relation indexRelation, int attno);
 extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
 									uint16 procnum);
 extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
-								   uint16 procnum);
+								   uint16 procnum);								   
 extern void index_store_float8_orderby_distances(IndexScanDesc scan,
 												 Oid *orderByTypes,
 												 IndexOrderByDistance *distances,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f7..e12da1934e9 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -339,7 +340,7 @@ extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 TU_UpdateIndexes *update_indexes, struct EState *estate);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 131c050c15f..62ed6592b85 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -38,6 +38,7 @@ struct IndexInfo;
 struct SampleScanState;
 struct VacuumParams;
 struct ValidateIndexState;
+struct EState;
 
 /*
  * Bitmask values for the flags argument to the scan_begin callback.
@@ -550,7 +551,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 TU_UpdateIndexes *update_indexes,
+								 struct EState *estate);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1541,12 +1543,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 update_indexes, estate);
 }
 
 /*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 30e2a82346f..343d04fff57 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -661,6 +661,11 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecIndexesExpressionsWereNotUpdated(Relation relation,
+												 Bitmapset *modified_attrs,
+												 EState *estate,
+												 HeapTuple old_tuple,
+												 HeapTuple new_tuple);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2625d7e8222..9bfdd0d1464 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -159,12 +159,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -183,6 +186,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -194,10 +198,14 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
+	CompactAttribute *ii_CompactAttr[INDEX_MAX_KEYS];
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index db3e504c3d2..3eba254a366 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,11 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
+
+	/* data managed by RelationGetExprIndexInfoList: */
+	List	   *rd_iiexprlist;	/* list of IndexInfo for indexes with
+								 * expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -344,6 +349,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -405,6 +411,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339e..56524a73399 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -52,6 +52,7 @@ extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetDummyIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
 extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
+extern List *RelationGetExprIndexInfoList(Relation relation);
 
 /*
  * Which set of columns to return by RelationGetIndexAttrBitmap.
@@ -63,6 +64,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..2ad6e5418e9
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,586 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions         
+---------------------------
+ {expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions        
+--------------------------
+ {expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+               reloptions                
+-----------------------------------------
+ {fillfactor=60,expression_checks=false}
+(1 row)
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            0 |                0 |                            |             6 |           1 |                       16.67
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX expression ON ex USING btree((att1->'data'));
+CREATE INDEX summarizing ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and not
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate!"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 both indexes updated
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e63ee2cf2bb..f9def3d93aa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..7728075a7ae
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,440 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX expression ON ex USING btree((att1->'data'));
+CREATE INDEX summarizing ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and not
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate!"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 both indexes updated
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
-- 
2.42.0

#15Burd, Greg
gregburd@amazon.com
In reply to: Burd, Greg (#14)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Changes v6 to v7:
* Fixed documentation oversight causing build failure
* Changed how I convey attribute len/by-val in IndexInfo
* Fixed method to shortcut index_unchanged_by_update() when possible

-greg

Attachments:

v7-0001-Expand-HOT-update-path-to-include-expression-and-.patchapplication/octet-stream; name=v7-0001-Expand-HOT-update-path-to-include-expression-and-.patchDownload
From c675c674644b7a521712913b5cfc53f899e435ce Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v7] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/ref/create_table.sgml            |  18 +
 doc/src/sgml/storage.sgml                     |  21 +
 src/backend/access/common/reloptions.c        |  12 +-
 src/backend/access/heap/README.HOT            |  45 +-
 src/backend/access/heap/heapam.c              |  43 +-
 src/backend/access/heap/heapam_handler.c      |   6 +-
 src/backend/access/table/tableam.c            |   2 +-
 src/backend/catalog/index.c                   |  19 +
 src/backend/executor/execIndexing.c           | 199 ++++++
 src/backend/executor/nodeModifyTable.c        |   3 +-
 src/backend/utils/cache/relcache.c            | 181 +++++-
 src/bin/psql/tab-complete.in.c                |   2 +-
 src/include/access/genam.h                    |   2 +-
 src/include/access/heapam.h                   |   3 +-
 src/include/access/tableam.h                  |  10 +-
 src/include/executor/executor.h               |   5 +
 src/include/nodes/execnodes.h                 |   9 +
 src/include/utils/rel.h                       |  14 +
 src/include/utils/relcache.h                  |   2 +
 .../regress/expected/heap_hot_updates.out     | 586 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 440 +++++++++++++
 22 files changed, 1566 insertions(+), 61 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 0a3e520f215..8ee2ac523ee 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1981,6 +1981,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..f40ada9c989 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  and index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determin this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 59fb53e7707..c081611926e 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1903,7 +1912,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..5bd61bd153c 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,28 +133,34 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results are used when check for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
 all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
-indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+indexes, the HOT optimization is applied, and the update is propagated to
+all of the summarizing indexes.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fa7935a0ed3..46ce824c4ea 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3164,12 +3164,13 @@ TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3246,6 +3247,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3311,6 +3314,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3612,6 +3616,7 @@ l2:
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -3928,25 +3933,30 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		bool		expression_checks = RelationGetExpressionChecks(relation);
+
 		/*
 		 * 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.
+		 * to do a HOT update.  As a reminder, hot_attrs includes attributes
+		 * used in expressions, but not attributes used by summarizing
+		 * indexes.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
-		{
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(expression_checks == true && estate != NULL && exp_attrs &&
+			 bms_overlap(modified_attrs, exp_attrs) &&
+			 ExecIndexesExpressionsWereNotUpdated(relation, modified_attrs,
+												  estate, &oldtup, newtup)))
 			use_hot_update = true;
 
-			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
-			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
-				summarized_update = true;
-		}
+		/*
+		 * If none of the columns that are used in hot-blocking indexes were
+		 * updated, we can apply HOT, but we do still need to check if we need
+		 * to update the summarizing indexes, and update those indexes if the
+		 * columns were updated, or we may fail to detect e.g. value bound
+		 * changes in BRIN minmax indexes.
+		 */
+		if (use_hot_update && bms_overlap(modified_attrs, sum_attrs))
+			summarized_update = true;
 	}
 	else
 	{
@@ -4126,7 +4136,6 @@ l2:
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
-	bms_free(sum_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4413,7 +4422,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, update_indexes);
+						 &tmfd, &lockmode, update_indexes, 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 c0bec014154..8e9ab892d73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -312,8 +312,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+					TU_UpdateIndexes *update_indexes, EState *estate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -324,7 +324,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, update_indexes);
+						 tmfd, lockmode, update_indexes, estate);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e18a8f8250f..9feca8a296f 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -345,7 +345,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/index.c b/src/backend/catalog/index.c
index cdabf780244..78b4310e05f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2455,7 +2455,26 @@ BuildIndexInfo(Relation index)
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
+	{
+		CompactAttribute *attr = TupleDescCompactAttr(RelationGetDescr(index), i);
+
 		ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   indexStruct->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+
+		ii->ii_IndexAttrLen[i] = attr->attlen;
+		if (attr->attbyval)
+			ii->ii_IndexAttrByVal = bms_add_member(ii->ii_IndexAttrByVal, i);
+	}
+
+	/* collect attributes used in the expression, if one is present */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate, if one is present */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 7c87f012c30..679124d1dfa 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -117,6 +117,8 @@
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -1089,6 +1091,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1166,3 +1171,197 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine if any index with an expression requires updating.
+ *
+ * This function will review all indexes with expressions to determine if the
+ * update from old to new tuple requires that the index be updated.  This is
+ * used to determine if a HOT update is possible or not.
+ *
+ * Returns true iff none of the expression indexes require updating due to the
+ * change from old to new tuple or when both the old and new tuples do not
+ * satisfy the predicate of the index.
+ */
+bool
+ExecIndexesExpressionsWereNotUpdated(Relation relation,
+									 Bitmapset *modified_attrs,
+									 EState *estate,
+									 HeapTuple old_tuple,
+									 HeapTuple new_tuple)
+{
+	ListCell   *lc;
+	List	   *indexinfolist = RelationGetExprIndexInfoList(relation);
+	ExprContext *econtext = NULL;
+	TupleDesc	tupledesc;
+	TupleTableSlot *old_tts = NULL;
+	TupleTableSlot *new_tts = NULL;
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	bool		changed = false;
+
+#ifndef USE_ASSERT_CHECKING
+
+	/*
+	 * Assume the index is changed when we don't have an estate context to use
+	 * or the reloption is disabled.
+	 */
+	if (!expression_checks || estate == NULL)
+		return changed;
+#endif
+
+	Assert(expression_checks == true);
+	Assert(estate != NULL);
+
+	/*
+	 * Examine expression and partial indexs on this relation relative to the
+	 * changes between old and new tuples.
+	 */
+	foreach(lc, indexinfolist)
+	{
+		IndexInfo  *indexInfo = (IndexInfo *) lfirst(lc);
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modified_attrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				changed = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modified_attrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (!econtext)
+			{
+				econtext = GetPerTupleExprContext(estate);
+				tupledesc = RelationGetDescr(relation);
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			indexInfo->ii_ExpressionsState = NIL;
+
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+
+			for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+			{
+				if (old_isnull[i] != new_isnull[i])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[i])
+				{
+					int16		elmlen = indexInfo->ii_IndexAttrLen[i];
+					bool		elmbyval = bms_is_member(i, indexInfo->ii_IndexAttrByVal);
+
+					if (!datum_image_eq(old_values[i], new_values[i],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			/* Shortcut index_unchanged_by_update(), we know the answer. */
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+
+			if (changed)
+				break;
+		}
+	}
+
+	if (econtext)
+	{
+		ExecDropSingleTupleTableSlot(old_tts);
+		ExecDropSingleTupleTableSlot(new_tts);
+	}
+
+	return !changed;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b0fe50075ad..d68f40cb998 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2283,7 +2283,8 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&updateCxt->updateIndexes,
+								estate);
 
 	return result;
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 398114373e9..b5e47b3521e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -5188,6 +5189,128 @@ RelationGetIndexPredicate(Relation relation)
 	return result;
 }
 
+ /*
+  * RelationGetExprIndexInfoList -- get a list of IndexInfo for expression
+  * indexes on this relation
+  */
+List *
+RelationGetExprIndexInfoList(Relation relation)
+{
+	ListCell   *lc;
+	List	   *indexoidlist;
+	List	   *newindexoidlist;
+	List	   *result = NULL;
+	List	   *oldlist;
+	Oid			relpkindex;
+	Oid			relreplindex;
+	MemoryContext oldcxt;
+
+	if (relation->rd_iiexprlist)
+		return list_copy(relation->rd_iiexprlist);
+
+	/* Fast path if definitely no indexes */
+	if (!RelationGetForm(relation)->relhasindex)
+		return NIL;
+
+restart:
+	indexoidlist = RelationGetIndexList(relation);
+
+	/* Fall out if no indexes (but relhasindex was set) */
+	if (indexoidlist == NIL)
+		return NIL;
+
+	/*
+	 * 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;
+
+	foreach(lc, indexoidlist)
+	{
+		Oid			oid = lfirst_oid(lc);
+		Datum		datum;
+		bool		isnull;
+		Node	   *indexExpressions;
+		Node	   *indexPredicate;
+		Relation	indexDesc;
+		IndexInfo  *indexInfo;
+
+		/*
+		 * 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.)
+		 */
+		indexDesc = index_open(oid, AccessShareLock);
+		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;
+
+		if (indexExpressions || indexPredicate)
+		{
+			oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+			indexInfo = BuildIndexInfo(indexDesc);
+			MemoryContextSwitchTo(oldcxt);
+			result = lappend(result, indexInfo);
+		}
+
+		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 IndexInfo.
+	 */
+	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);
+		list_free(result);
+
+		goto restart;
+	}
+
+	/* Now save a copy of the completed list in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldlist = relation->rd_iiexprlist;
+	relation->rd_iiexprlist = list_copy(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Don't leak the old list, if there is one */
+	if (oldlist)
+		list_free(oldlist);
+
+	return result;
+}
+
 /*
  * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
  *
@@ -5229,7 +5352,11 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in
+										 * expressions */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in
+										 * expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5252,6 +5379,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5295,7 +5424,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5309,6 +5440,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5352,14 +5484,20 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = indexDesc->rd_index->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5375,30 +5513,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5427,11 +5563,27 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {hot-blocking columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	/* {summarized-only columns} = {summarized columns} - {direct columns} */
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	/* {summarized columns} = {direct columns} + {summarized-only columns} */
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5444,6 +5596,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5458,6 +5612,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_expressionattr = bms_copy(hotblockingexprattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5474,6 +5629,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return hotblockingexprattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index eb8bc128720..f0a8286a25b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2992,7 +2992,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 1be8739573f..91af1e8238d 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -194,7 +194,7 @@ extern bool index_can_return(Relation indexRelation, int attno);
 extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
 									uint16 procnum);
 extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
-								   uint16 procnum);
+								   uint16 procnum);								   
 extern void index_store_float8_orderby_distances(IndexScanDesc scan,
 												 Oid *orderByTypes,
 												 IndexOrderByDistance *distances,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f7..e12da1934e9 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -339,7 +340,7 @@ extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 TU_UpdateIndexes *update_indexes, struct EState *estate);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 131c050c15f..62ed6592b85 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -38,6 +38,7 @@ struct IndexInfo;
 struct SampleScanState;
 struct VacuumParams;
 struct ValidateIndexState;
+struct EState;
 
 /*
  * Bitmask values for the flags argument to the scan_begin callback.
@@ -550,7 +551,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 TU_UpdateIndexes *update_indexes,
+								 struct EState *estate);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1541,12 +1543,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 update_indexes, estate);
 }
 
 /*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 30e2a82346f..343d04fff57 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -661,6 +661,11 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecIndexesExpressionsWereNotUpdated(Relation relation,
+												 Bitmapset *modified_attrs,
+												 EState *estate,
+												 HeapTuple old_tuple,
+												 HeapTuple new_tuple);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2625d7e8222..aca9371dccd 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -159,12 +159,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -183,6 +186,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -194,10 +198,15 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
+	uint16		ii_IndexAttrLen[INDEX_MAX_KEYS];
+	Bitmapset  *ii_IndexAttrByVal;
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index db3e504c3d2..3eba254a366 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,11 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
+
+	/* data managed by RelationGetExprIndexInfoList: */
+	List	   *rd_iiexprlist;	/* list of IndexInfo for indexes with
+								 * expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -344,6 +349,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -405,6 +411,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339e..56524a73399 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -52,6 +52,7 @@ extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetDummyIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
 extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
+extern List *RelationGetExprIndexInfoList(Relation relation);
 
 /*
  * Which set of columns to return by RelationGetIndexAttrBitmap.
@@ -63,6 +64,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..2ad6e5418e9
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,586 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions         
+---------------------------
+ {expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions        
+--------------------------
+ {expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+               reloptions                
+-----------------------------------------
+ {fillfactor=60,expression_checks=false}
+(1 row)
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            0 |                0 |                            |             6 |           1 |                       16.67
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX expression ON ex USING btree((att1->'data'));
+CREATE INDEX summarizing ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and not
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate!"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 both indexes updated
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e63ee2cf2bb..f9def3d93aa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..7728075a7ae
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,440 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+SELECT 
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float / 
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float / 
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex' 
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+
+-- Create a partial index on the email column, updates 
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX expression ON ex USING btree((att1->'data'));
+CREATE INDEX summarizing ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and not
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate!"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 both indexes updated
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
-- 
2.42.0

#16Burd, Greg
gregburd@amazon.com
In reply to: Burd, Greg (#1)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Hello,

I've rebased and updated the patch a bit. The biggest change is that the performance penalty measured with v1 of this patch is essentially gone in v10. The overhead was due to re-creating IndexInfo information unnecessarily, which I found existed in the estate. I've added a few fields in IndexInfo that are not populated by default but necessary when checking expression indexes, those fields are populated on demand and only once limiting their overhead.

Here's what you'll find if you look into execIndexing.c where the majority of changes happened.

* assumes estate->es_result_relations[0] is the ResultRelInfo being updated
* uses ri_IndexRelationInfo[] from within estate rather than re-creating it
* augments IndexInfo only when needed for testing expressions and only once
* only creates a local old/new TupleTableSlot when not present in estate
* retains existing summarized index HOT update logic

One remaining concern stems from the assumption that estate->es_result_relations[0] is always going to be the relation being updated. This is guarded by assert()'s in the patch. It seems this is safe, all tests are passing (including TAP) and my review of the code seems to line up with that assumption. That said... opinions?

Another lingering question is under what conditions the old/new TupleTableSlots are not created and available via the ResultRelInfo found in estate. I've only seen this happen when there is an INSERT ... ON CONFLICT UPDATE ... with expression indexes. I was hopeful that in all cases I could avoid re-creating those when checking expression indexes to avoid that repeated overhead. I still avoid it when possible in this patch.

When you have time I'd appreciate any feedback.

-greg
Amazon Web Services: https://aws.amazon.com

Attachments:

v10-0001-Expand-HOT-update-path-to-include-expression-and.patchapplication/octet-stream; name=v10-0001-Expand-HOT-update-path-to-include-expression-and.patchDownload
From 9422526a14e9d48150eb1d54c030351221d087a5 Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v10] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

For historical context, this is a rewrite of a patch first proposed on the
pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/ref/create_table.sgml            |  18 +
 doc/src/sgml/storage.sgml                     |  21 +
 src/backend/access/common/reloptions.c        |  12 +-
 src/backend/access/heap/README.HOT            |  45 +-
 src/backend/access/heap/heapam.c              |  23 +-
 src/backend/access/heap/heapam_handler.c      |   6 +-
 src/backend/access/table/tableam.c            |   2 +-
 src/backend/catalog/index.c                   |  19 +
 src/backend/executor/execIndexing.c           | 255 ++++++-
 src/backend/executor/execReplication.c        |   2 +-
 src/backend/executor/nodeModifyTable.c        |   7 +-
 src/backend/utils/cache/relcache.c            |  70 +-
 src/bin/psql/tab-complete.in.c                |   2 +-
 src/include/access/heapam.h                   |   3 +-
 src/include/access/tableam.h                  |  10 +-
 src/include/executor/executor.h               |   5 +
 src/include/nodes/execnodes.h                 |   9 +
 src/include/utils/rel.h                       |  10 +
 src/include/utils/relcache.h                  |   1 +
 .../regress/expected/heap_hot_updates.out     | 665 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 481 +++++++++++++
 22 files changed, 1619 insertions(+), 52 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 0a3e520f215..8ee2ac523ee 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1981,6 +1981,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..64a6264d8fd 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  an index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determin this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 59fb53e7707..c081611926e 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1903,7 +1912,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..2340d82053f 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,28 +133,34 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results used when checking for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
 all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
-indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+indexes, the HOT optimization is applied and the update is propagated to
+all of the summarizing indexes.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fa7935a0ed3..32b3cc1d4ef 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3164,12 +3164,13 @@ TM_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3246,6 +3247,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3311,6 +3314,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3612,6 +3616,7 @@ l2:
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -3928,12 +3933,19 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		bool		expression_checks = RelationGetExpressionChecks(relation);
+
 		/*
 		 * 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.
+		 * to do a HOT update.  As a reminder, hot_attrs includes attributes
+		 * used by indexes including within expressions and predicates, but
+		 * not attributes only used by summarizing indexes.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(expression_checks &&
+			 bms_overlap(modified_attrs, exp_attrs) &&
+			 !ExecExpressionIndexesUpdated(relation, modified_attrs, estate,
+										   &oldtup, newtup)))
 		{
 			use_hot_update = true;
 
@@ -4126,7 +4138,6 @@ l2:
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
-	bms_free(sum_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4413,7 +4424,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, update_indexes);
+						 &tmfd, &lockmode, update_indexes, 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 e78682c3cef..a8710f29ffc 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -312,8 +312,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+					TU_UpdateIndexes *update_indexes, EState *estate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -324,7 +324,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, update_indexes);
+						 tmfd, lockmode, update_indexes, estate);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index a56c5eceb14..a62df7f4bce 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -346,7 +346,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/index.c b/src/backend/catalog/index.c
index 8e1741c81f5..d9bd64bfe96 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2455,7 +2455,26 @@ BuildIndexInfo(Relation index)
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
+	{
+		CompactAttribute *attr = TupleDescCompactAttr(RelationGetDescr(index), i);
+
 		ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   indexStruct->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+
+		ii->ii_IndexAttrLen[i] = attr->attlen;
+		if (attr->attbyval)
+			ii->ii_IndexAttrByVal = bms_add_member(ii->ii_IndexAttrByVal, i);
+	}
+
+	/* collect attributes used in the expression, if one is present */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate, if one is present */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 742f3f8c08d..2b61147ad88 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -113,10 +113,13 @@
 #include "catalog/index.h"
 #include "executor/executor.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "storage/lmgr.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -372,7 +375,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -1096,6 +1099,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1173,3 +1179,250 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+
+ /*
+  * AttributeIndexInfo -- adds attribute information to IndexInfo
+  */
+static IndexInfo *
+AttributeIndexInfo(Relation index, IndexInfo *indexInfo)
+{
+	if (indexInfo->ii_IndexAttrByVal)
+		return indexInfo;
+
+	/*
+	 * collect attributes used by the index, their len and if they are by
+	 * value
+	 */
+	for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+	{
+		indexInfo->ii_IndexAttrs =
+			bms_add_member(indexInfo->ii_IndexAttrs,
+						   indexInfo->ii_IndexAttrNumbers[i] - FirstLowInvalidHeapAttributeNumber);
+
+		if (i < indexInfo->ii_NumIndexKeyAttrs)
+		{
+			int16		elmlen;
+			bool		elmbyval;
+
+			get_typlenbyval(index->rd_opcintype[i], &elmlen, &elmbyval);
+			indexInfo->ii_IndexAttrLen[i] = elmlen;
+			if (elmbyval)
+				indexInfo->ii_IndexAttrByVal = bms_add_member(indexInfo->ii_IndexAttrByVal, i);
+		}
+	}
+
+	/* collect attributes used in the expression */
+	if (indexInfo->ii_Expressions)
+		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexInfo->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate */
+	if (indexInfo->ii_Predicate)
+		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexInfo->ii_PredicateAttrs);
+
+	return indexInfo;
+}
+
+/*
+ * Determine if indexes that have expressions or predicates require updates
+ * for the purposes of allowing HOT updates.  Returns false iff all indexed
+ * values are unchanged.
+ */
+bool
+ExecExpressionIndexesUpdated(Relation relation,
+							 Bitmapset *modified_attrs,
+							 EState *estate,
+							 HeapTuple old_tuple,
+							 HeapTuple new_tuple)
+{
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	bool		result = false;
+	ResultRelInfo *resultRelInfo;
+	Relation	index;
+	IndexInfo  *indexInfo;
+	ExprContext *econtext = NULL;
+	TupleDesc	tupledesc;
+	TupleTableSlot *old_tts;
+	TupleTableSlot *new_tts;
+
+	/*
+	 * We're not able to access the IndexInfo array without an estate.
+	 */
+	if (estate == NULL || modified_attrs == NULL)
+		return true;
+
+#ifndef USE_ASSERT_CHECKING
+	if (estate->es_result_relations[0]->ri_RelationDesc != relation)
+		return true;
+#endif
+
+	Assert(estate->es_result_relations[0]->ri_RelationDesc == relation);
+
+	if (expression_checks == false)
+		return true;
+
+	resultRelInfo = estate->es_result_relations[0];
+	old_tts = resultRelInfo->ri_oldTupleSlot;
+	new_tts = resultRelInfo->ri_newTupleSlot;
+	econtext = GetPerTupleExprContext(estate);
+	tupledesc = RelationGetDescr(relation);
+
+	/*
+	 * Examine each index on this relation relative to the changes between old
+	 * and new tuples.
+	 */
+	for (int i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		index = (Relation) resultRelInfo->ri_IndexRelationDescs[i];
+		indexInfo = AttributeIndexInfo(index,
+									   resultRelInfo->ri_IndexRelationInfo[i]);
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modified_attrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (old_tts == NULL)
+			{
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+			}
+
+			if (new_tts == NULL)
+			{
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated. If any
+			 * single non-summarizing index requires updates then they all
+			 * should be updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modified_attrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			bool		changed = false;
+
+			/* Create these once, only if necessary, then reuse them. */
+			if (old_tts == NULL)
+			{
+				old_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				ExecStoreHeapTuple(old_tuple, old_tts, InvalidBuffer);
+			}
+
+			if (new_tts == NULL)
+			{
+				new_tts = MakeSingleTupleTableSlot(tupledesc,
+												   &TTSOpsHeapTuple);
+				ExecStoreHeapTuple(new_tuple, new_tts, InvalidBuffer);
+			}
+
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				if (old_isnull[j] != new_isnull[j])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[j])
+				{
+					int16		elmlen = indexInfo->ii_IndexAttrLen[j];
+					bool		elmbyval = bms_is_member(j, indexInfo->ii_IndexAttrByVal);
+
+					if (!datum_image_eq(old_values[j], new_values[j],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+
+			if (changed)
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	if (resultRelInfo->ri_oldTupleSlot == NULL)
+		ExecDropSingleTupleTableSlot(old_tts);
+
+	if (resultRelInfo->ri_newTupleSlot == NULL)
+		ExecDropSingleTupleTableSlot(new_tts);
+
+	return result;
+}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 5cef54f00ed..b3cba354d8a 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -639,8 +639,8 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
 		List	   *conflictindexes;
+		TU_UpdateIndexes update_indexes;
 		bool		conflict = false;
 
 		/* Compute stored generated columns */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b0fe50075ad..ef4e8d2499d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2283,7 +2283,8 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&updateCxt->updateIndexes,
+								estate);
 
 	return result;
 }
@@ -2301,6 +2302,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 {
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
+	bool		onlySummarizing = updateCxt->updateIndexes == TU_Summarizing;
 
 	/* insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
@@ -2308,7 +2310,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   onlySummarizing);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
@@ -4362,6 +4364,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d1ae761b3f6..21ccefeefa1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -5208,6 +5209,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_EXPRESSION	Columns included in expresion indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5231,7 +5233,11 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in
+										 * expressions */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in
+										 * expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5254,6 +5260,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5297,7 +5305,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5311,6 +5321,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5354,14 +5365,20 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = indexDesc->rd_index->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5377,30 +5394,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5429,11 +5444,37 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/*
+	 * HOT-blocking attributes (columns) should include all columns that are
+	 * part of the index, but not part of the index expressions from partial
+	 * and expression indexes.  So, we need to remove the expression-only
+	 * columns from the HOT-blocking columns bitmap as those will be checked
+	 * separately.  This is true for both summarizing and non-summarizing
+	 * indexes which we've separated above, so we have to do this for both
+	 * bitmaps.
+	 */
+
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {hot-blocking columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	/* {summarized-only columns} = {all summarized columns} - {direct columns} */
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	/* {summarized columns} = {all direct columns} + {summarized-only columns} */
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5446,6 +5487,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5460,6 +5503,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_expressionattr = bms_copy(hotblockingexprattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5476,6 +5520,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return hotblockingexprattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8432be641ac..fc3250176eb 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2992,7 +2992,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f7..e12da1934e9 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -339,7 +340,7 @@ extern TM_Result heap_update(Relation relation, ItemPointer otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 struct TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 TU_UpdateIndexes *update_indexes, struct EState *estate);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 131c050c15f..62ed6592b85 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -38,6 +38,7 @@ struct IndexInfo;
 struct SampleScanState;
 struct VacuumParams;
 struct ValidateIndexState;
+struct EState;
 
 /*
  * Bitmask values for the flags argument to the scan_begin callback.
@@ -550,7 +551,8 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 TU_UpdateIndexes *update_indexes,
+								 struct EState *estate);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1541,12 +1543,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   TU_UpdateIndexes *update_indexes, struct EState *estate)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 update_indexes, estate);
 }
 
 /*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d12e3f451d2..b5fe74f176b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -689,6 +689,11 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecExpressionIndexesUpdated(Relation relation,
+										 Bitmapset *modified_attrs,
+										 EState *estate,
+										 HeapTuple old_tuple,
+										 HeapTuple new_tuple);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a323fa98bbb..6e5f60cad76 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -160,12 +160,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -184,6 +187,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -195,10 +199,15 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
+	uint16		ii_IndexAttrLen[INDEX_MAX_KEYS];
+	Bitmapset  *ii_IndexAttrByVal;
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index db3e504c3d2..39975672da1 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -344,6 +345,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -405,6 +407,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339e..0cc28cb97e2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -63,6 +63,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..027c8760d69
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,665 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions         
+---------------------------
+ {expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions        
+--------------------------
+ {expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+               reloptions                
+-----------------------------------------
+ {fillfactor=60,expression_checks=false}
+(1 row)
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float /
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float /
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex'
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            0 |                0 |                            |             6 |           1 |                       16.67
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+SELECT * FROM ex;
+ id |     att1     |      att2       | att3 | att4 
+----+--------------+-----------------+------+------
+  1 | {"data": []} | nothing special |      | 
+(1 row)
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM ex;
+ id |     att1     |      att2      | att3 |   att4   
+----+--------------+----------------+------+----------
+  1 | {"data": []} | special indeed |      | whatever
+(1 row)
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        |        att2         | att3 |   att4   
+----+-------------------+---------------------+------+----------
+  1 | {"data": "howdy"} | special, so special |      | whatever
+(1 row)
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 with one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | a    | b    | c
+(1 row)
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 with one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | d    | e    | c
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 4 both indexes updated
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 37b6d21e1f9..7ab9712e8a8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..56ec4f2f96c
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,481 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+SELECT
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float /
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float /
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex'
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+
+SELECT * FROM ex;
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+SELECT * FROM ex;
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+SELECT * FROM ex;
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 with one new HOT update
+SELECT * FROM ex;
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 with one new HOT update
+SELECT * FROM ex;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 4 both indexes updated
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
-- 
2.42.0

#17Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#14)
Re: Expanding HOT updates for expression and partial indexes

Hi,

Sorry for the delay. This is a reply for the mail thread up to 17 Feb,
so it might be very out-of-date by now, in which case sorry for the
noise.

On Mon, 17 Feb 2025 at 20:54, Burd, Greg <gregburd@amazon.com> wrote:

On Feb 15, 2025, at 5:49 AM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

In HEAD, we have a clear indication of which classes of indexes to
update, with TU_UpdateIndexes. With this patch, we have to derive that
from the (lack of) bits in the bitmap that might be output by the
table_update procedure.

Yes, but... that "clear indication" is lacking the ability to convey more detailed information. It doesn't tell you which summarizing indexes really need updating just that as a result of being on the HOT path all summarizing indexes require updates.

Agreed that it's not great if you want to know about which indexes
were meaningfully updated. I think that barring significant advances
in performance of update checks, we can devise a way of transfering
this info to the table_tuple_update caller once we get a need for more
detailed information (e.g. this could be transfered through the
IndexInfo* that's currently also used by index_unchanged_by_update).

I think we can do with an additional parameter for which indexes would
be updated (or store that info in the parameter which also will hold
EState et al). I think it's cheaper that way, too - only when
update_indexes could be TU_SUMMARIZING we might need the exact
information for which indexes to insert new tuples into, and it only
really needs to be sized to the number of summarizing indexes (usually
small/nonexistent, but potentially huge).

Okay, yes with this patch we need only concern ourselves with all, none, or some subset of summarizing as before. I'll work on the opaque parameter next iteration.

Thanks!

-----

I notice that ExecIndexesRequiringUpdates() does work on all indexes,
rather than just indexes relevant to this exact phase of checking. I
think that is a waste of time, so if we sort the indexes in order of
[hotblocking without expressions, hotblocking with expressions,
summarizing], then (with stored start/end indexes) we can save time in
cases where there are comparatively few of the types we're not going
to look at.

If I plan on just having ExecIndexesRequiringUpdates() return a bool rather than a bitmap then sorting, or even just filtering the list of IndexInfo to only include indexes with expressions, makes sense. That way the only indexes in question in that function's loop will be those that may spoil the HOT path. When that list is length 0, we can skip the tests entirely.

Yes, exactly. Though I'm not sure it should hit that path if the
length is 0, as would mean we had expression indexes that matched the
updated columns, but somehow none are in the list?

You're extracting type info from the opclass, to use in
datum_image_eq(). Couldn't you instead use the index relation's
TupleDesc and its stored attribute information instead? That saves us
from having to do further catalog lookups during execution. I'm also
fairly sure that that information is supposed to be a more accurate
representation of attributes' expression output types than the
opclass' type information (though, they probably should match).

I hadn't thought of that, I think it's a valid idea and I'll update accordingly. I think I understand what you are suggesting.

Thanks, that change was exactly what I meant.

-----

The operations applied in ExecIndexesRequiringUpdates partially
duplicate those done in index_unchanged_by_update. Can we (partially)
unify this, and pass which indexes were updated through the IndexInfo,
rather than the current bitmap?

I think I do that now, feel free to say otherwise. When the expression is checked in ExecIndexesExpressionsWereNotUpdated() I set:

/* Shortcut index_unchanged_by_update(), we know the answer. */ indexInfo->ii_CheckedUnchanged = true; indexInfo->ii_IndexUnchanged = !changed;

That prevents duplicate effort in index_unchanged_by_update().

Exactly, yes.

-----

I don't see a good reason to add IndexInfo to Relation, by way of
rd_indexInfoList. It seems like an ad-hoc way of passing data around,
and I don't think that's the right way.

At one point I'd created a way to get this set via relcache, I will resurrect that approach but I'm not sure it is what you were hinting at.

AFAIK, we don't have IndexInfo in the relcaches currently. I'm very
hesitant to add an executor node (!) subtype to catalog caches, as
IndexInfos are also used to store temporary information about e.g.
index tuple insertion state, which (if IndexInfo is stored in
relcaches) would imply modifying relcache entries without any further
locks, and I'm not sure that's at all an OK thing to do.

The current method avoids pulling a the lock on the index to build the list, but doing that once in relcache isn't horrible. Maybe you were suggesting using that opaque struct to pass around the list of IndexInfo? Let me know on this one if you had a specific idea. The swap I've made in v6 really just moves the IndexInfo list to a filtered list with a new name created in relcache.

My main concern is the storage of executor nodes(!) directly in the
relcache. I don't think we need that: We have relatively direct access
to the right IndexInfo** in ResultRelInfo->ri_IndexRelationInfo, which
I think should be sufficient for this purpose. (The relevant RRI is
available in table_tuple_update caller ExecUpdateAct; and could be
passed down by ExecSimpleRelationUpdate to simple_table_tuple_update,
covering both (current, core) callers of table_tuple_update). That
would then be passed down to the TableAM using an opaque pointer type;
for example (names, file locations, exact layout all bikesheddable):

/* tableam.h */
/* exact definition somewhere else, in e.g. an executor_internal.h */
typedef struct TU_UpdateIndexData TU_UpdateIndexData;

table_tuple_update(..., TU_UpdateIndexData *idxupdate, ...)

/* executor.h */

TU_UpdateIndexes
UpdateDetermineChangedIndexes(TU_UpdateIndexData *idxupdate,
TableTupleSlot *old, TableTupleSlot *new, bitmap *changed_atts, ...);

/* executor_internal.h */
struct TU_UpdateIndexData
{
EState estate;
IndexInfo **idxinfos;
...
}

-----

Looking at your later comments about RRI in patch v8, I think that
would solve and clean up the way that you currently get access to the
RRI and thus index set.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#18Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#16)
Re: Expanding HOT updates for expression and partial indexes

On Wed, 5 Mar 2025 at 18:21, Burd, Greg <gregburd@amazon.com> wrote:

Hello,

I've rebased and updated the patch a bit. The biggest change is that the performance penalty measured with v1 of this patch is essentially gone in v10. The overhead was due to re-creating IndexInfo information unnecessarily, which I found existed in the estate. I've added a few fields in IndexInfo that are not populated by default but necessary when checking expression indexes, those fields are populated on demand and only once limiting their overhead.

This review is based on a light reading of patch v10. I have not read
all 90kB, and am unlikely to finish a full review soon:

* assumes estate->es_result_relations[0] is the ResultRelInfo being updated

I'm not sure that's a valid assumption. I suspect it might be false in
cases of nested updates, like

$ UPDATE table1 SET value = other.value FROM (UPDATE table2 SET value
= 2 ) other WHERE other.id = table1.id;

If this table1 or table2 has expression indexes I suspect it may
result in this assertion failing (but I haven't spun up a server with
the patch).
Alternatively, please also check that it doesn't break if any of these
two tables is partitioned with multiple partitions (and/or has
expression indexes, etc.).

* uses ri_IndexRelationInfo[] from within estate rather than re-creating it

As I mentioned above, I think it's safer to pass the known-correct RRI
(known by callers of table_tuple_update) down the stack.

* augments IndexInfo only when needed for testing expressions and only once

ExecExpressionIndexesUpdated seems to always loop over all indexes,
always calling AttributeIndexInfo which always updates the fields in
the IndexInfo when the index has only !byval attributes (e.g. text,
json, or other such varlena types). You say it happens only once, have
I missed something?

I'm also somewhat concerned about the use of typecache lookups on
index->rd_opcintype[i], rather than using
TupleDescCompactAttr(index->rd_att, i); the latter of which I think
should be faster, especially when multiple wide indexes are scanned
with various column types. In hot loops of single-tuple update
statements I think this may make a few 0.1%pt difference - not a lot,
but worth considering.

* only creates a local old/new TupleTableSlot when not present in estate

I'm not sure it's safe for us to touch that RRI's tupleslots.

* retains existing summarized index HOT update logic

Great, thanks!

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#19Burd, Greg
gregburd@amazon.com
In reply to: Matthias van de Meent (#17)
Re: Expanding HOT updates for expression and partial indexes

On Mar 5, 2025, at 5:56 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

Hi,

Sorry for the delay. This is a reply for the mail thread up to 17 Feb,
so it might be very out-of-date by now, in which case sorry for the
noise.

Never noise, always helpful.

On Mon, 17 Feb 2025 at 20:54, Burd, Greg <gregburd@amazon.com> wrote:

On Feb 15, 2025, at 5:49 AM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

In HEAD, we have a clear indication of which classes of indexes to
update, with TU_UpdateIndexes. With this patch, we have to derive that
from the (lack of) bits in the bitmap that might be output by the
table_update procedure.

Yes, but... that "clear indication" is lacking the ability to convey more detailed information. It doesn't tell you which summarizing indexes really need updating just that as a result of being on the HOT path all summarizing indexes require updates.

Agreed that it's not great if you want to know about which indexes
were meaningfully updated. I think that barring significant advances
in performance of update checks, we can devise a way of transfering
this info to the table_tuple_update caller once we get a need for more
detailed information (e.g. this could be transfered through the
IndexInfo* that's currently also used by index_unchanged_by_update).

One idea I had and tested a bit was to re-order the arrays of ri_IndexRelationInfo/Desc[] and then have a ri_NumModifiedIndices. This avoided allocation of a Bitmapset and was something downstream code could use or not depending on the requirements within that path. It may be, and I didn't check, that the order of indexes in that array has meaning in other contexts in the code so I put this aside. I think I'll keep focused as much as possible and consider that within the context of another patch later if needed.

I think we can do with an additional parameter for which indexes would
be updated (or store that info in the parameter which also will hold
EState et al). I think it's cheaper that way, too - only when
update_indexes could be TU_SUMMARIZING we might need the exact
information for which indexes to insert new tuples into, and it only
really needs to be sized to the number of summarizing indexes (usually
small/nonexistent, but potentially huge).

Okay, yes with this patch we need only concern ourselves with all, none, or some subset of summarizing as before. I'll work on the opaque parameter next iteration.

Thanks!

I still haven't added the opaque parameter as suggested, but it's on my mind to give it a shot.

-----

I don't see a good reason to add IndexInfo to Relation, by way of
rd_indexInfoList. It seems like an ad-hoc way of passing data around,
and I don't think that's the right way.

At one point I'd created a way to get this set via relcache, I will resurrect that approach but I'm not sure it is what you were hinting at.

AFAIK, we don't have IndexInfo in the relcaches currently. I'm very
hesitant to add an executor node (!) subtype to catalog caches, as
IndexInfos are also used to store temporary information about e.g.
index tuple insertion state, which (if IndexInfo is stored in
relcaches) would imply modifying relcache entries without any further
locks, and I'm not sure that's at all an OK thing to do.

This is gone in the v10 patch in favor of finding IndexInfo within the EState's ri_IndexRelationInfo[].

Thanks again for your continued support!

-greg

#20Burd, Greg
gregburd@amazon.com
In reply to: Matthias van de Meent (#18)
Re: Expanding HOT updates for expression and partial indexes

On Mar 5, 2025, at 6:39 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Wed, 5 Mar 2025 at 18:21, Burd, Greg <gregburd@amazon.com> wrote:

Hello,

I've rebased and updated the patch a bit. The biggest change is that the performance penalty measured with v1 of this patch is essentially gone in v10. The overhead was due to re-creating IndexInfo information unnecessarily, which I found existed in the estate. I've added a few fields in IndexInfo that are not populated by default but necessary when checking expression indexes, those fields are populated on demand and only once limiting their overhead.

This review is based on a light reading of patch v10. I have not read
all 90kB, and am unlikely to finish a full review soon:

* assumes estate->es_result_relations[0] is the ResultRelInfo being updated

I'm not sure that's a valid assumption. I suspect it might be false in
cases of nested updates, like

$ UPDATE table1 SET value = other.value FROM (UPDATE table2 SET value
= 2 ) other WHERE other.id = table1.id;

If this table1 or table2 has expression indexes I suspect it may
result in this assertion failing (but I haven't spun up a server with
the patch).
Alternatively, please also check that it doesn't break if any of these
two tables is partitioned with multiple partitions (and/or has
expression indexes, etc.).

Valid, and possible. I'll check and find a way to pass along the known-correct RRI index into that array.

* uses ri_IndexRelationInfo[] from within estate rather than re-creating it

As I mentioned above, I think it's safer to pass the known-correct RRI
(known by callers of table_tuple_update) down the stack.

I think passing the known-correct RRI index is the way to go as I need information from both ri_IndexRelationInfo/Desc[] arrays.

* augments IndexInfo only when needed for testing expressions and only once

ExecExpressionIndexesUpdated seems to always loop over all indexes,
always calling AttributeIndexInfo which always updates the fields in
the IndexInfo when the index has only !byval attributes (e.g. text,
json, or other such varlena types). You say it happens only once, have
I missed something?

There's a test that avoids doing it more than once, but I'm going to rename this as BuildExpressionIndexInfo() and call it from ExecOpenIndices() if there are expressions on the index. I think that's cleaner and there's precedent for it in the form of BuildSpeculativeIndexInfo().

I'm also somewhat concerned about the use of typecache lookups on
index->rd_opcintype[i], rather than using
TupleDescCompactAttr(index->rd_att, i); the latter of which I think
should be faster, especially when multiple wide indexes are scanned
with various column types. In hot loops of single-tuple update
statements I think this may make a few 0.1%pt difference - not a lot,
but worth considering.

I was just working on that. Good idea.

* only creates a local old/new TupleTableSlot when not present in estate

I'm not sure it's safe for us to touch that RRI's tupleslots.

Me neither, that's why I mentioned it. It was my attempt to avoid the work to create/destroy temp slots over and over that led to that idea. It's working, but needs more thought.

* retains existing summarized index HOT update logic

Great, thanks!

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

I might widen this patch a bit to include support for testing equality of index tuples using custom operators when they exist for the index. In the use case I'm solving for we use a custom operator for equality that is not the same as a memcmp(). Do you have thoughts on that? It may be hard to accomplish this as the notion of an equality operator is specific to the index access method and not well-defined outside that AFAICT. If that's the case I'd have to augment the definition of an index access method to provide that information.

-greg

#21Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Burd, Greg (#20)
Re: Expanding HOT updates for expression and partial indexes

On Thu, 6 Mar 2025 at 13:40, Burd, Greg <gregburd@amazon.com> wrote:

On Mar 5, 2025, at 6:39 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Wed, 5 Mar 2025 at 18:21, Burd, Greg <gregburd@amazon.com> wrote:

* augments IndexInfo only when needed for testing expressions and only once

ExecExpressionIndexesUpdated seems to always loop over all indexes,
always calling AttributeIndexInfo which always updates the fields in
the IndexInfo when the index has only !byval attributes (e.g. text,
json, or other such varlena types). You say it happens only once, have
I missed something?

There's a test that avoids doing it more than once, [...]

Is this that one?

+ if (indexInfo->ii_IndexAttrByVal)
+ return indexInfo;

I think that test doesn't work consistently: a bitmapset * is NULL
when no bits are set; and for some indexes no attribute will be byval,
thus failing this early-exit even after processing.

Another small issue with this approach is that it always calls and
tests in EEIU(), while it's quite likely we would do better if we
pre-processed _all_ indexes at once, so that we can have a path that
doesn't repeatedly get into EEIU only to exit immediately after. It'll
probably be hot enough to not matter much, but it's still cycles spent
on something that we can optimize for in code.

* retains existing summarized index HOT update logic

Great, thanks!

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

I might widen this patch a bit to include support for testing equality of index tuples using custom operators when they exist for the index. In the use case I'm solving for we use a custom operator for equality that is not the same as a memcmp(). Do you have thoughts on that?

I don't think that's a very great idea. From a certain point of view,
you can see HOT as "deduplicating multiple tuple versions behind a
single TID". Btree doesn't support deduplication for types that can
have more than one representation of the same value so that e.g.
'0.0'::numeric and '0'::numeric are both displayed correctly, even
when they compare as equal according to certain equality operators.

So, I don't think that's worth investing time into right now. Maybe in
the future if there are new discoveries about what we can and cannot
deduplicate, but I don't think it should be part of an MVP or 1.0.

Kind regards,

Matthias van de Meent

#22Burd, Greg
gregburd@amazon.com
In reply to: Burd, Greg (#1)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Matthias,

Rebased patch attached.

Changes in v14:
* UpdateContext now the location I've stored estate, resultRelInfo, etc.
* Reuse the result from the predicate on the partial index.

-greg

On Mar 7, 2025, at 5:47 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Thu, 6 Mar 2025 at 13:40, Burd, Greg <gregburd@amazon.com> wrote:

On Mar 5, 2025, at 6:39 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Wed, 5 Mar 2025 at 18:21, Burd, Greg <gregburd@amazon.com> wrote:

* augments IndexInfo only when needed for testing expressions and only once

ExecExpressionIndexesUpdated seems to always loop over all indexes,
always calling AttributeIndexInfo which always updates the fields in
the IndexInfo when the index has only !byval attributes (e.g. text,
json, or other such varlena types). You say it happens only once, have
I missed something?

There's a test that avoids doing it more than once, [...]

Is this that one?

+ if (indexInfo->ii_IndexAttrByVal)
+ return indexInfo;

I think that test doesn't work consistently: a bitmapset * is NULL
when no bits are set; and for some indexes no attribute will be byval,
thus failing this early-exit even after processing.

Another small issue with this approach is that it always calls and
tests in EEIU(), while it's quite likely we would do better if we
pre-processed _all_ indexes at once, so that we can have a path that
doesn't repeatedly get into EEIU only to exit immediately after. It'll
probably be hot enough to not matter much, but it's still cycles spent
on something that we can optimize for in code.

I've changed this a bit, now in ExecOpenIndices() when there are expressions or predicates I augment the IndexInfo with information necessary to perform the tests in EEIU(). I've debated adding another bool to ExecOpenIndices() to indicate that we're opening indexes for the purpose of an update to avoid building that information in cases where we are not. Similar to the bool `speculative` on ExecOpenIndices() today. Thoughts?

I might widen this patch a bit to include support for testing equality of index tuples using custom operators when they exist for the index. In the use case I'm solving for we use a custom operator for equality that is not the same as a memcmp(). Do you have thoughts on that?

I don't think that's a very great idea. From a certain point of view,
you can see HOT as "deduplicating multiple tuple versions behind a
single TID". Btree doesn't support deduplication for types that can
have more than one representation of the same value so that e.g.
'0.0'::numeric and '0'::numeric are both displayed correctly, even
when they compare as equal according to certain equality operators.

Interesting, good point. Seems like it would require a new index AM function:
bool indexed_tuple_would_change()

I'll drop this for now, it seems out of scope for this patch set.

Attachments:

v14-0001-Expand-HOT-update-path-to-include-expression-and.patchapplication/octet-stream; name=v14-0001-Expand-HOT-update-path-to-include-expression-and.patchDownload
From 05e2a84dc3541885392ebe85bd266018ec29e896 Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v14] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/ref/create_table.sgml            |  18 +
 doc/src/sgml/storage.sgml                     |  21 +
 src/backend/access/common/reloptions.c        |  12 +-
 src/backend/access/heap/README.HOT            |  45 +-
 src/backend/access/heap/heapam.c              |  43 +-
 src/backend/access/heap/heapam_handler.c      |  15 +-
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/catalog/index.c                   |  47 ++
 src/backend/catalog/indexing.c                |  16 +-
 src/backend/executor/execIndexing.c           | 192 ++++-
 src/backend/executor/execReplication.c        |  10 +-
 src/backend/executor/nodeModifyTable.c        |  27 +-
 src/backend/utils/cache/relcache.c            |  70 +-
 src/bin/psql/tab-complete.in.c                |   2 +-
 src/include/access/heapam.h                   |   6 +-
 src/include/access/tableam.h                  |  33 +-
 src/include/catalog/index.h                   |   1 +
 src/include/executor/executor.h               |   5 +
 src/include/nodes/execnodes.h                 |  13 +
 src/include/utils/rel.h                       |  10 +
 src/include/utils/relcache.h                  |   1 +
 .../regress/expected/heap_hot_updates.out     | 665 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 481 +++++++++++++
 src/tools/pgindent/typedefs.list              |   2 +-
 25 files changed, 1640 insertions(+), 105 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e5c034d724e..569b2c3ee45 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1980,6 +1980,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..64a6264d8fd 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  an index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determin this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 645b5c00467..41c727eafcb 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1914,7 +1923,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..2340d82053f 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,28 +133,34 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results used when checking for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
 all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
-indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+indexes, the HOT optimization is applied and the update is propagated to
+all of the summarizing indexes.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b12b583c4d9..8b523d9cb82 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3244,13 +3244,14 @@ 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,
-			TU_UpdateIndexes *update_indexes)
+			TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
+	LockTupleMode *lockmode = &updateCxt->lockmode;
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3327,6 +3328,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3388,10 +3391,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		tmfd->ctid = *otid;
 		tmfd->xmax = InvalidTransactionId;
 		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3689,10 +3693,11 @@ l2:
 			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -4009,12 +4014,24 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		ResultRelInfo *resultRelInfo = updateCxt->rri;
+		EState	   *estate = updateCxt->estate;
+		bool		expression_checks = RelationGetExpressionChecks(relation);
+
 		/*
 		 * 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.
+		 * to do a HOT update.  As a reminder, hot_attrs includes attributes
+		 * used by indexes including within expressions and predicates, but
+		 * not attributes only used by summarizing indexes.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(estate && resultRelInfo && expression_checks &&
+			 bms_overlap(modified_attrs, exp_attrs) &&
+			 !ExecExpressionIndexesUpdated(resultRelInfo,
+										   modified_attrs,
+										   estate,
+										   resultRelInfo->ri_oldTupleSlot,
+										   resultRelInfo->ri_newTupleSlot)))
 		{
 			use_hot_update = true;
 
@@ -4196,18 +4213,19 @@ l2:
 	if (use_hot_update)
 	{
 		if (summarized_update)
-			*update_indexes = TU_Summarizing;
+			updateCxt->updateIndexes = TU_Summarizing;
 		else
-			*update_indexes = TU_None;
+			updateCxt->updateIndexes = TU_None;
 	}
 	else
-		*update_indexes = TU_All;
+		updateCxt->updateIndexes = TU_All;
 
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
 	bms_free(sum_attrs);
+	bms_free(exp_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4485,16 +4503,15 @@ HeapDetermineColumnsInfo(Relation relation,
  */
 void
 simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-				   TU_UpdateIndexes *update_indexes)
+				   UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
+						 &tmfd, updateCxt);
 	switch (result)
 	{
 		case TM_SelfModified:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 24d3765aa20..54f627c5b27 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -316,8 +316,7 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -328,7 +327,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, update_indexes);
+						 tmfd, updateCxt);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
@@ -343,14 +342,14 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	 */
 	if (result != TM_Ok)
 	{
-		Assert(*update_indexes == TU_None);
-		*update_indexes = TU_None;
+		Assert(updateCxt->updateIndexes == TU_None);
+		updateCxt->updateIndexes = TU_None;
 	}
 	else if (!HeapTupleIsHeapOnly(tuple))
-		Assert(*update_indexes == TU_All);
+		Assert(updateCxt->updateIndexes == TU_All);
 	else
-		Assert((*update_indexes == TU_Summarizing) ||
-			   (*update_indexes == TU_None));
+		Assert((updateCxt->updateIndexes == TU_Summarizing) ||
+			   (updateCxt->updateIndexes == TU_None));
 
 	if (shouldFree)
 		pfree(tuple);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index a56c5eceb14..1eeeebadc09 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,17 +336,16 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  TU_UpdateIndexes *update_indexes)
+						  UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, updateCxt);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 739a92bdcc1..b8c9ff89b3b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2707,6 +2707,53 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
 	}
 }
 
+/* ----------------
+ *		BuildExpressionIndexInfo
+ *			Add extra state to IndexInfo record
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildExpressionIndexInfo(Relation index, IndexInfo *ii)
+{
+	int			i;
+	int			indnkeyatts;
+	Form_pg_index indexStruct = index->rd_index;
+
+	indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+
+	/*
+	 * Collect attributes used by the index, their len and if they are by
+	 * value.
+	 */
+	for (i = 0; i < indnkeyatts; i++)
+	{
+		CompactAttribute *attr = TupleDescCompactAttr(RelationGetDescr(index), i);
+
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   ii->ii_IndexAttrNumbers[i] - FirstLowInvalidHeapAttributeNumber);
+
+		ii->ii_IndexAttrLen[i] = attr->attlen;
+		if (attr->attbyval)
+			ii->ii_IndexAttrByVal = bms_add_member(ii->ii_IndexAttrByVal, i);
+	}
+
+	/* collect attributes used in the expression */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
+}
+
 /* ----------------
  *		FormIndexDatum
  *			Construct values[] and isnull[] arrays for a new index tuple.
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc87..91a6b455b14 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -313,15 +313,17 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -337,13 +339,15 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 }
 
 /*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index e3fe9b78bb5..429e08f759c 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -113,10 +113,13 @@
 #include "catalog/index.h"
 #include "executor/executor.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "storage/lmgr.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -221,6 +224,13 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		if (speculative && ii->ii_Unique && !indexDesc->rd_index->indisexclusion)
 			BuildSpeculativeIndexInfo(indexDesc, ii);
 
+		/*
+		 * If the index uses expressions then let's populate the additional
+		 * information nessaary to evaluate them for changes during updates.
+		 */
+		if (ii->ii_Expressions || ii->ii_Predicate)
+			BuildExpressionIndexInfo(indexDesc, ii);
+
 		relationDescs[i] = indexDesc;
 		indexInfoArray[i] = ii;
 		i++;
@@ -383,19 +393,33 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			ExprState  *predicate;
 
 			/*
-			 * If predicate state not set up yet, create it (in the estate's
-			 * per-query context)
+			 * It is possible that we've already checked the predicate, if so
+			 * then avoid the duplicate work.
 			 */
-			predicate = indexInfo->ii_PredicateState;
-			if (predicate == NULL)
+			if (indexInfo->ii_CheckedPredicate)
 			{
-				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
-				indexInfo->ii_PredicateState = predicate;
+				/* Skip this index-update if the predicate isn't satisfied */
+				if (!indexInfo->ii_PredicateSatisfied)
+					continue;
 			}
+			else
+			{
 
-			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
-				continue;
+				/*
+				 * 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 */
+				if (!ExecQual(predicate, econtext))
+					continue;
+			}
 		}
 
 		/*
@@ -1096,6 +1120,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1173,3 +1200,150 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine if indexes that have expressions or predicates require updates
+ * for the purposes of allowing HOT updates.  Returns false iff all indexed
+ * values are unchanged.
+ */
+bool
+ExecExpressionIndexesUpdated(ResultRelInfo *resultRelInfo,
+							 Bitmapset *modifiedAttrs,
+							 EState *estate,
+							 TupleTableSlot *old_tts,
+							 TupleTableSlot *new_tts)
+{
+	bool		result = false;
+	IndexInfo  *indexInfo;
+	ExprContext *econtext = NULL;
+
+	if (old_tts == NULL || new_tts == NULL)
+		return true;
+
+	econtext = GetPerTupleExprContext(estate);
+
+	/*
+	 * Examine each index on this relation relative to the changes between old
+	 * and new tuples.
+	 */
+	for (int i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		indexInfo = resultRelInfo->ri_IndexRelationInfo[i];
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modifiedAttrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateSatisfied = new_tuple_qualifies;
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated. If any
+			 * single non-summarizing index requires updates then they all
+			 * should be updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modifiedAttrs))
+		{
+			TupleTableSlot *save_scantuple;
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			bool		changed = false;
+
+			save_scantuple = econtext->ecxt_scantuple;
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+			econtext->ecxt_scantuple = save_scantuple;
+
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				if (old_isnull[j] != new_isnull[j])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[j])
+				{
+					int16		elmlen = indexInfo->ii_IndexAttrLen[j];
+					bool		elmbyval = bms_is_member(j, indexInfo->ii_IndexAttrByVal);
+
+					if (!datum_image_eq(old_values[j], new_values[j],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+
+			if (changed)
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index ede89ea3cf9..c13fb6befd5 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -647,7 +647,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
+		UpdateContext updateCxt = {0};
 		List	   *conflictindexes;
 		bool		conflict = false;
 
@@ -663,17 +663,19 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		updateCxt.estate = estate;
+		updateCxt.rri = resultRelInfo;
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
-								  &update_indexes);
+								  &updateCxt);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
 
-		if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
+		if (resultRelInfo->ri_NumIndices > 0 && (updateCxt.updateIndexes != TU_None))
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   conflictindexes ? true : false,
 												   &conflict, conflictindexes,
-												   (update_indexes == TU_Summarizing));
+												   (updateCxt.updateIndexes == TU_Summarizing));
 
 		/*
 		 * Refer to the comments above the call to CheckAndReportConflict() in
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 87c820276a8..8650c2be096 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -115,21 +115,6 @@ typedef struct ModifyTableContext
 	TupleTableSlot *cpUpdateReturningSlot;
 } ModifyTableContext;
 
-/*
- * Context struct containing output data specific to UPDATE operations.
- */
-typedef struct UpdateContext
-{
-	bool		crossPartUpdate;	/* was it a cross-partition update? */
-	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
-
-	/*
-	 * Lock mode to acquire on the latest tuple version before performing
-	 * EvalPlanQual on it
-	 */
-	LockTupleMode lockmode;
-} UpdateContext;
-
 
 static void ExecBatchInsert(ModifyTableState *mtstate,
 							ResultRelInfo *resultRelInfo,
@@ -2282,8 +2267,7 @@ lreplace:
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
-								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&context->tmfd, updateCxt);
 
 	return result;
 }
@@ -2301,6 +2285,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 {
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
+	bool		onlySummarizing = updateCxt->updateIndexes == TU_Summarizing;
 
 	/* insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
@@ -2308,7 +2293,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   onlySummarizing);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
@@ -2444,6 +2429,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
 
+	updateCxt.estate = estate;
+	updateCxt.rri = resultRelInfo;
+
 	/*
 	 * abort the operation if not running transactions
 	 */
@@ -3132,6 +3120,9 @@ lmerge_matched:
 		TM_Result	result;
 		UpdateContext updateCxt = {0};
 
+		updateCxt.rri = resultRelInfo;
+		updateCxt.estate = estate;
+
 		/*
 		 * Test condition, if any.
 		 *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f54a9e72b7..29cef048a87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -5211,6 +5212,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_EXPRESSION	Columns included in expresion indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5234,7 +5236,11 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in
+										 * expressions */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in
+										 * expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5257,6 +5263,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5300,7 +5308,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5314,6 +5324,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5357,14 +5368,20 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = indexDesc->rd_index->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5380,30 +5397,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5432,11 +5447,37 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/*
+	 * HOT-blocking attributes should include all columns that are part of the
+	 * index except attributes only referenced in expressions, including
+	 * expressions used to form partial indexes.  So, we need to remove the
+	 * expression-only columns from the HOT-blocking columns bitmap as those
+	 * will be checked separately.  This is true for both summarizing and
+	 * non-summarizing indexes which we've separated above, so we have to do
+	 * this for both bitmaps.
+	 */
+
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {hot-blocking columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	/* {summarized-only columns} = {all summarized columns} - {direct columns} */
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	/* {summarized columns} = {all direct columns} + {summarized-only columns} */
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5449,6 +5490,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5463,6 +5506,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_expressionattr = bms_copy(hotblockingexprattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5479,6 +5523,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return hotblockingexprattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 98951aef82c..3d4d50a95e9 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2993,7 +2993,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f7..a7eb7bd542d 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -338,8 +339,7 @@ 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,
-							 TU_UpdateIndexes *update_indexes);
+							 struct TM_FailureData *tmfd, UpdateContext *updateCxt);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -374,7 +374,7 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+							   HeapTuple tup, UpdateContext *updateCxt);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index b8cb1e744ad..13a0bf1e114 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -125,6 +125,28 @@ typedef enum TU_UpdateIndexes
 	TU_Summarizing,
 } TU_UpdateIndexes;
 
+/*
+ * Data specific to processing UPDATE operations.
+ *
+ * When table_tuple_update is called some storage managers, notably heapam,
+ * can at times avoid index updates.  In the heapam this is known as a HOT
+ * update.  This struct is used to provide the state required to test for
+ * HOT updates and to communicate that decision on to the index AMs.
+ */
+typedef struct UpdateContext
+{
+	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
+	struct ResultRelInfo *rri;	/* ResultRelInfo for the updated table. */
+	struct EState *estate;		/* EState used within the update. */
+	bool		crossPartUpdate;	/* Was it a cross-partition update? */
+
+	/*
+	 * Lock mode to acquire on the latest tuple version before performing
+	 * EvalPlanQual on it
+	 */
+	LockTupleMode lockmode;
+} UpdateContext;
+
 /*
  * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail
  * because the target tuple is already outdated, they fill in this struct to
@@ -549,8 +571,7 @@ typedef struct TableAmRoutine
 								 Snapshot crosscheck,
 								 bool wait,
 								 TM_FailureData *tmfd,
-								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 UpdateContext *updateCxt);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1504,13 +1525,11 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
 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,
-				   TU_UpdateIndexes *update_indexes)
+				   bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, updateCxt);
 }
 
 /*
@@ -2011,7 +2030,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  TU_UpdateIndexes *update_indexes);
+									  UpdateContext *updateCxt);
 
 
 /* ----------------------------------------------------------------------------
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 4daa8bef5ee..fdbf47f607b 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildExpressionIndexInfo(Relation index, IndexInfo *indexInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 6a1fec88928..507e142a10e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -773,6 +773,11 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecExpressionIndexesUpdated(ResultRelInfo *resultRelInfo,
+										 Bitmapset *modifiedAttrs,
+										 EState *estate,
+										 TupleTableSlot *old_tts,
+										 TupleTableSlot *new_tts);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e42f9f9f957..f60b20671b9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -160,12 +160,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -184,6 +187,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -195,10 +199,17 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
+	uint16		ii_IndexAttrLen[INDEX_MAX_KEYS];
+	Bitmapset  *ii_IndexAttrByVal;
+	FmgrInfo   *ii_EqualityProc[INDEX_MAX_KEYS];
+	Oid			ii_Collation[INDEX_MAX_KEYS];
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
@@ -211,6 +222,8 @@ typedef struct IndexInfo
 	bool		ii_ReadyForInserts;
 	bool		ii_CheckedUnchanged;
 	bool		ii_IndexUnchanged;
+	bool		ii_CheckedPredicate;
+	bool		ii_PredicateSatisfied;
 	bool		ii_Concurrent;
 	bool		ii_BrokenHotChain;
 	bool		ii_Summarizing;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d94fddd7cef..e44d9966778 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -345,6 +346,7 @@ typedef struct StdRdOptions
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
 	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -406,6 +408,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339e..0cc28cb97e2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -63,6 +63,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..027c8760d69
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,665 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions         
+---------------------------
+ {expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions        
+--------------------------
+ {expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+               reloptions                
+-----------------------------------------
+ {fillfactor=60,expression_checks=false}
+(1 row)
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float /
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float /
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex'
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            0 |                0 |                            |             6 |           1 |                       16.67
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+SELECT * FROM ex;
+ id |     att1     |      att2       | att3 | att4 
+----+--------------+-----------------+------+------
+  1 | {"data": []} | nothing special |      | 
+(1 row)
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM ex;
+ id |     att1     |      att2      | att3 |   att4   
+----+--------------+----------------+------+----------
+  1 | {"data": []} | special indeed |      | whatever
+(1 row)
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        |        att2         | att3 |   att4   
+----+-------------------+---------------------+------+----------
+  1 | {"data": "howdy"} | special, so special |      | whatever
+(1 row)
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 with one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | a    | b    | c
+(1 row)
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 with one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | d    | e    | c
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 4 both indexes updated
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 0a35f2f8f6a..e2fd5fb74a5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..56ec4f2f96c
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,481 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+SELECT
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float /
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float /
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex'
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+
+SELECT * FROM ex;
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+SELECT * FROM ex;
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+SELECT * FROM ex;
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 with one new HOT update
+SELECT * FROM ex;
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 with one new HOT update
+SELECT * FROM ex;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 4 both indexes updated
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3fbf5a4c212..0b6587dbd56 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2934,6 +2934,7 @@ TSVectorStat
 TState
 TStatus
 TStoreState
+UpdateContext
 TU_UpdateIndexes
 TXNEntryFile
 TYPCATEGORY
@@ -3118,7 +3119,6 @@ UniqueState
 UnlistenStmt
 UnresolvedTup
 UnresolvedTupData
-UpdateContext
 UpdateStmt
 UpgradeTask
 UpgradeTaskProcessCB
-- 
2.42.0

#23Burd, Greg
gregburd@amazon.com
In reply to: Burd, Greg (#1)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Apologies for the noise, I overlooked a compiler warning.

fixed.

-greg

Show quoted text

On Mar 25, 2025, at 7:47 AM, Burd, Greg <gregburd@amazon.com> wrote:

Matthias,

Rebased patch attached.

Changes in v14:
* UpdateContext now the location I've stored estate, resultRelInfo, etc.
* Reuse the result from the predicate on the partial index.

-greg

On Mar 7, 2025, at 5:47 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Thu, 6 Mar 2025 at 13:40, Burd, Greg <gregburd@amazon.com> wrote:

On Mar 5, 2025, at 6:39 PM, Matthias van de Meent <boekewurm+postgres@gmail.com> wrote:

On Wed, 5 Mar 2025 at 18:21, Burd, Greg <gregburd@amazon.com> wrote:

* augments IndexInfo only when needed for testing expressions and only once

ExecExpressionIndexesUpdated seems to always loop over all indexes,
always calling AttributeIndexInfo which always updates the fields in
the IndexInfo when the index has only !byval attributes (e.g. text,
json, or other such varlena types). You say it happens only once, have
I missed something?

There's a test that avoids doing it more than once, [...]

Is this that one?

+ if (indexInfo->ii_IndexAttrByVal)
+ return indexInfo;

I think that test doesn't work consistently: a bitmapset * is NULL
when no bits are set; and for some indexes no attribute will be byval,
thus failing this early-exit even after processing.

Another small issue with this approach is that it always calls and
tests in EEIU(), while it's quite likely we would do better if we
pre-processed _all_ indexes at once, so that we can have a path that
doesn't repeatedly get into EEIU only to exit immediately after. It'll
probably be hot enough to not matter much, but it's still cycles spent
on something that we can optimize for in code.

I've changed this a bit, now in ExecOpenIndices() when there are expressions or predicates I augment the IndexInfo with information necessary to perform the tests in EEIU(). I've debated adding another bool to ExecOpenIndices() to indicate that we're opening indexes for the purpose of an update to avoid building that information in cases where we are not. Similar to the bool `speculative` on ExecOpenIndices() today. Thoughts?

I might widen this patch a bit to include support for testing equality of index tuples using custom operators when they exist for the index. In the use case I'm solving for we use a custom operator for equality that is not the same as a memcmp(). Do you have thoughts on that?

I don't think that's a very great idea. From a certain point of view,
you can see HOT as "deduplicating multiple tuple versions behind a
single TID". Btree doesn't support deduplication for types that can
have more than one representation of the same value so that e.g.
'0.0'::numeric and '0'::numeric are both displayed correctly, even
when they compare as equal according to certain equality operators.

Interesting, good point. Seems like it would require a new index AM function:
bool indexed_tuple_would_change()

I'll drop this for now, it seems out of scope for this patch set.

<v14-0001-Expand-HOT-update-path-to-include-expression-and.patch>

Attachments:

v15-0001-Expand-HOT-update-path-to-include-expression-and.patchapplication/octet-stream; name=v15-0001-Expand-HOT-update-path-to-include-expression-and.patchDownload
From 989601d40a8a693631ae5194dfe59a95c98eb1b9 Mon Sep 17 00:00:00 2001
From: Gregory Burd <gregburd@amazon.com>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v15] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the heapam by
examining expression indexes and determining if indexed values where mutated
or not.  Previously, any expression index on a column would disqualify it
from the HOT update path. Also examines partial indexes to see if the
values are within the predicate or not.

This is a modified application of a patch proposed on the pgsql-hackers list:
https://www.postgresql.org/message-id/flat/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
reverted: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256

Signed-off-by: Greg Burd <gregburd@amazon.com>
---
 doc/src/sgml/ref/create_table.sgml            |  18 +
 doc/src/sgml/storage.sgml                     |  21 +
 src/backend/access/common/reloptions.c        |  12 +-
 src/backend/access/heap/README.HOT            |  45 +-
 src/backend/access/heap/heapam.c              |  43 +-
 src/backend/access/heap/heapam_handler.c      |  15 +-
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/catalog/index.c                   |  46 ++
 src/backend/catalog/indexing.c                |  16 +-
 src/backend/executor/execIndexing.c           | 192 ++++-
 src/backend/executor/execReplication.c        |  10 +-
 src/backend/executor/nodeModifyTable.c        |  27 +-
 src/backend/utils/cache/relcache.c            |  70 +-
 src/bin/psql/tab-complete.in.c                |   2 +-
 src/include/access/heapam.h                   |   6 +-
 src/include/access/tableam.h                  |  33 +-
 src/include/catalog/index.h                   |   1 +
 src/include/executor/executor.h               |   5 +
 src/include/nodes/execnodes.h                 |  13 +
 src/include/utils/rel.h                       |  10 +
 src/include/utils/relcache.h                  |   1 +
 .../regress/expected/heap_hot_updates.out     | 665 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 src/test/regress/sql/heap_hot_updates.sql     | 481 +++++++++++++
 src/tools/pgindent/typedefs.list              |   2 +-
 25 files changed, 1639 insertions(+), 105 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e5c034d724e..569b2c3ee45 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1980,6 +1980,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..64a6264d8fd 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  an index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determin this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 645b5c00467..41c727eafcb 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1914,7 +1923,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..2340d82053f 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,28 +133,34 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results used when checking for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
 all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
-indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+indexes, the HOT optimization is applied and the update is propagated to
+all of the summarizing indexes.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b12b583c4d9..8b523d9cb82 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3244,13 +3244,14 @@ 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,
-			TU_UpdateIndexes *update_indexes)
+			TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
+	LockTupleMode *lockmode = &updateCxt->lockmode;
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3327,6 +3328,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3388,10 +3391,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		tmfd->ctid = *otid;
 		tmfd->xmax = InvalidTransactionId;
 		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3689,10 +3693,11 @@ l2:
 			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -4009,12 +4014,24 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		ResultRelInfo *resultRelInfo = updateCxt->rri;
+		EState	   *estate = updateCxt->estate;
+		bool		expression_checks = RelationGetExpressionChecks(relation);
+
 		/*
 		 * 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.
+		 * to do a HOT update.  As a reminder, hot_attrs includes attributes
+		 * used by indexes including within expressions and predicates, but
+		 * not attributes only used by summarizing indexes.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(estate && resultRelInfo && expression_checks &&
+			 bms_overlap(modified_attrs, exp_attrs) &&
+			 !ExecExpressionIndexesUpdated(resultRelInfo,
+										   modified_attrs,
+										   estate,
+										   resultRelInfo->ri_oldTupleSlot,
+										   resultRelInfo->ri_newTupleSlot)))
 		{
 			use_hot_update = true;
 
@@ -4196,18 +4213,19 @@ l2:
 	if (use_hot_update)
 	{
 		if (summarized_update)
-			*update_indexes = TU_Summarizing;
+			updateCxt->updateIndexes = TU_Summarizing;
 		else
-			*update_indexes = TU_None;
+			updateCxt->updateIndexes = TU_None;
 	}
 	else
-		*update_indexes = TU_All;
+		updateCxt->updateIndexes = TU_All;
 
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
 	bms_free(sum_attrs);
+	bms_free(exp_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4485,16 +4503,15 @@ HeapDetermineColumnsInfo(Relation relation,
  */
 void
 simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-				   TU_UpdateIndexes *update_indexes)
+				   UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
+						 &tmfd, updateCxt);
 	switch (result)
 	{
 		case TM_SelfModified:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 24d3765aa20..54f627c5b27 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -316,8 +316,7 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -328,7 +327,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, update_indexes);
+						 tmfd, updateCxt);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
@@ -343,14 +342,14 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	 */
 	if (result != TM_Ok)
 	{
-		Assert(*update_indexes == TU_None);
-		*update_indexes = TU_None;
+		Assert(updateCxt->updateIndexes == TU_None);
+		updateCxt->updateIndexes = TU_None;
 	}
 	else if (!HeapTupleIsHeapOnly(tuple))
-		Assert(*update_indexes == TU_All);
+		Assert(updateCxt->updateIndexes == TU_All);
 	else
-		Assert((*update_indexes == TU_Summarizing) ||
-			   (*update_indexes == TU_None));
+		Assert((updateCxt->updateIndexes == TU_Summarizing) ||
+			   (updateCxt->updateIndexes == TU_None));
 
 	if (shouldFree)
 		pfree(tuple);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index a56c5eceb14..1eeeebadc09 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,17 +336,16 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  TU_UpdateIndexes *update_indexes)
+						  UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, updateCxt);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 739a92bdcc1..0d6a0235e79 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2707,6 +2707,52 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
 	}
 }
 
+/* ----------------
+ *		BuildExpressionIndexInfo
+ *			Add extra state to IndexInfo record
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildExpressionIndexInfo(Relation index, IndexInfo *ii)
+{
+	int			i;
+	int			indnkeyatts;
+
+	indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+
+	/*
+	 * Collect attributes used by the index, their len and if they are by
+	 * value.
+	 */
+	for (i = 0; i < indnkeyatts; i++)
+	{
+		CompactAttribute *attr = TupleDescCompactAttr(RelationGetDescr(index), i);
+
+		ii->ii_IndexAttrs =
+			bms_add_member(ii->ii_IndexAttrs,
+						   ii->ii_IndexAttrNumbers[i] - FirstLowInvalidHeapAttributeNumber);
+
+		ii->ii_IndexAttrLen[i] = attr->attlen;
+		if (attr->attbyval)
+			ii->ii_IndexAttrByVal = bms_add_member(ii->ii_IndexAttrByVal, i);
+	}
+
+	/* collect attributes used in the expression */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionAttrs);
+
+	/* collect attributes used in the predicate */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
+}
+
 /* ----------------
  *		FormIndexDatum
  *			Construct values[] and isnull[] arrays for a new index tuple.
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc87..91a6b455b14 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -313,15 +313,17 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -337,13 +339,15 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 }
 
 /*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index e3fe9b78bb5..429e08f759c 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -113,10 +113,13 @@
 #include "catalog/index.h"
 #include "executor/executor.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "storage/lmgr.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -221,6 +224,13 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		if (speculative && ii->ii_Unique && !indexDesc->rd_index->indisexclusion)
 			BuildSpeculativeIndexInfo(indexDesc, ii);
 
+		/*
+		 * If the index uses expressions then let's populate the additional
+		 * information nessaary to evaluate them for changes during updates.
+		 */
+		if (ii->ii_Expressions || ii->ii_Predicate)
+			BuildExpressionIndexInfo(indexDesc, ii);
+
 		relationDescs[i] = indexDesc;
 		indexInfoArray[i] = ii;
 		i++;
@@ -383,19 +393,33 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			ExprState  *predicate;
 
 			/*
-			 * If predicate state not set up yet, create it (in the estate's
-			 * per-query context)
+			 * It is possible that we've already checked the predicate, if so
+			 * then avoid the duplicate work.
 			 */
-			predicate = indexInfo->ii_PredicateState;
-			if (predicate == NULL)
+			if (indexInfo->ii_CheckedPredicate)
 			{
-				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
-				indexInfo->ii_PredicateState = predicate;
+				/* Skip this index-update if the predicate isn't satisfied */
+				if (!indexInfo->ii_PredicateSatisfied)
+					continue;
 			}
+			else
+			{
 
-			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
-				continue;
+				/*
+				 * 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 */
+				if (!ExecQual(predicate, econtext))
+					continue;
+			}
 		}
 
 		/*
@@ -1096,6 +1120,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1173,3 +1200,150 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * Determine if indexes that have expressions or predicates require updates
+ * for the purposes of allowing HOT updates.  Returns false iff all indexed
+ * values are unchanged.
+ */
+bool
+ExecExpressionIndexesUpdated(ResultRelInfo *resultRelInfo,
+							 Bitmapset *modifiedAttrs,
+							 EState *estate,
+							 TupleTableSlot *old_tts,
+							 TupleTableSlot *new_tts)
+{
+	bool		result = false;
+	IndexInfo  *indexInfo;
+	ExprContext *econtext = NULL;
+
+	if (old_tts == NULL || new_tts == NULL)
+		return true;
+
+	econtext = GetPerTupleExprContext(estate);
+
+	/*
+	 * Examine each index on this relation relative to the changes between old
+	 * and new tuples.
+	 */
+	for (int i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		indexInfo = resultRelInfo->ri_IndexRelationInfo[i];
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modifiedAttrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateSatisfied = new_tuple_qualifies;
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require that this index is updated. If any
+			 * single non-summarizing index requires updates then they all
+			 * should be updated.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionAttrs, modifiedAttrs))
+		{
+			TupleTableSlot *save_scantuple;
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			bool		changed = false;
+
+			save_scantuple = econtext->ecxt_scantuple;
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+			econtext->ecxt_scantuple = save_scantuple;
+
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				if (old_isnull[j] != new_isnull[j])
+				{
+					changed = true;
+					break;
+				}
+				else if (!old_isnull[j])
+				{
+					int16		elmlen = indexInfo->ii_IndexAttrLen[j];
+					bool		elmbyval = bms_is_member(j, indexInfo->ii_IndexAttrByVal);
+
+					if (!datum_image_eq(old_values[j], new_values[j],
+										elmbyval, elmlen))
+					{
+						changed = true;
+						break;
+					}
+				}
+			}
+
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !changed;
+
+			if (changed)
+			{
+				result = true;
+				break;
+			}
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index ede89ea3cf9..c13fb6befd5 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -647,7 +647,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
+		UpdateContext updateCxt = {0};
 		List	   *conflictindexes;
 		bool		conflict = false;
 
@@ -663,17 +663,19 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		updateCxt.estate = estate;
+		updateCxt.rri = resultRelInfo;
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
-								  &update_indexes);
+								  &updateCxt);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
 
-		if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
+		if (resultRelInfo->ri_NumIndices > 0 && (updateCxt.updateIndexes != TU_None))
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   conflictindexes ? true : false,
 												   &conflict, conflictindexes,
-												   (update_indexes == TU_Summarizing));
+												   (updateCxt.updateIndexes == TU_Summarizing));
 
 		/*
 		 * Refer to the comments above the call to CheckAndReportConflict() in
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 87c820276a8..8650c2be096 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -115,21 +115,6 @@ typedef struct ModifyTableContext
 	TupleTableSlot *cpUpdateReturningSlot;
 } ModifyTableContext;
 
-/*
- * Context struct containing output data specific to UPDATE operations.
- */
-typedef struct UpdateContext
-{
-	bool		crossPartUpdate;	/* was it a cross-partition update? */
-	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
-
-	/*
-	 * Lock mode to acquire on the latest tuple version before performing
-	 * EvalPlanQual on it
-	 */
-	LockTupleMode lockmode;
-} UpdateContext;
-
 
 static void ExecBatchInsert(ModifyTableState *mtstate,
 							ResultRelInfo *resultRelInfo,
@@ -2282,8 +2267,7 @@ lreplace:
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
-								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&context->tmfd, updateCxt);
 
 	return result;
 }
@@ -2301,6 +2285,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 {
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
+	bool		onlySummarizing = updateCxt->updateIndexes == TU_Summarizing;
 
 	/* insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
@@ -2308,7 +2293,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   onlySummarizing);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
@@ -2444,6 +2429,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
 
+	updateCxt.estate = estate;
+	updateCxt.rri = resultRelInfo;
+
 	/*
 	 * abort the operation if not running transactions
 	 */
@@ -3132,6 +3120,9 @@ lmerge_matched:
 		TM_Result	result;
 		UpdateContext updateCxt = {0};
 
+		updateCxt.rri = resultRelInfo;
+		updateCxt.estate = estate;
+
 		/*
 		 * Test condition, if any.
 		 *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f54a9e72b7..29cef048a87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -5211,6 +5212,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_EXPRESSION	Columns included in expresion indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5234,7 +5236,11 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
+	Bitmapset  *hotblockingexprattrs;	/* as above, but only those in
+										 * expressions */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *summarizedexprattrs;	/* as above, but only those in
+										 * expressions */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5257,6 +5263,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5300,7 +5308,9 @@ restart:
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
+	hotblockingexprattrs = NULL;
 	summarizedattrs = NULL;
+	summarizedexprattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5314,6 +5324,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5357,14 +5368,20 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
+		{
 			attrs = &summarizedattrs;
+			exprattrs = &summarizedexprattrs;
+		}
 		else
+		{
 			attrs = &hotblockingattrs;
+			exprattrs = &hotblockingexprattrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = indexDesc->rd_index->indkey.values[i];
 
 			/*
 			 * Since we have covering indexes with non-key columns, we must
@@ -5380,30 +5397,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5432,11 +5447,37 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
+		bms_free(hotblockingexprattrs);
 		bms_free(summarizedattrs);
+		bms_free(summarizedexprattrs);
 
 		goto restart;
 	}
 
+	/*
+	 * HOT-blocking attributes should include all columns that are part of the
+	 * index except attributes only referenced in expressions, including
+	 * expressions used to form partial indexes.  So, we need to remove the
+	 * expression-only columns from the HOT-blocking columns bitmap as those
+	 * will be checked separately.  This is true for both summarizing and
+	 * non-summarizing indexes which we've separated above, so we have to do
+	 * this for both bitmaps.
+	 */
+
+	/* {expression-only columns} = {expression columns} - {direct columns} */
+	hotblockingexprattrs = bms_del_members(hotblockingexprattrs,
+										   hotblockingattrs);
+	/* {hot-blocking columns} = {direct columns} + {expression-only columns} */
+	hotblockingattrs = bms_add_members(hotblockingattrs,
+									   hotblockingexprattrs);
+
+	/* {summarized-only columns} = {all summarized columns} - {direct columns} */
+	summarizedexprattrs = bms_del_members(summarizedexprattrs,
+										  summarizedattrs);
+	/* {summarized columns} = {all direct columns} + {summarized-only columns} */
+	summarizedattrs = bms_add_members(summarizedattrs,
+									  summarizedexprattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5449,6 +5490,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5463,6 +5506,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_expressionattr = bms_copy(hotblockingexprattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5479,6 +5523,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return hotblockingexprattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 98951aef82c..3d4d50a95e9 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2993,7 +2993,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1640d9c32f7..a7eb7bd542d 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -21,6 +21,7 @@
 #include "access/skey.h"
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -338,8 +339,7 @@ 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,
-							 TU_UpdateIndexes *update_indexes);
+							 struct TM_FailureData *tmfd, UpdateContext *updateCxt);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -374,7 +374,7 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+							   HeapTuple tup, UpdateContext *updateCxt);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index b8cb1e744ad..13a0bf1e114 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -125,6 +125,28 @@ typedef enum TU_UpdateIndexes
 	TU_Summarizing,
 } TU_UpdateIndexes;
 
+/*
+ * Data specific to processing UPDATE operations.
+ *
+ * When table_tuple_update is called some storage managers, notably heapam,
+ * can at times avoid index updates.  In the heapam this is known as a HOT
+ * update.  This struct is used to provide the state required to test for
+ * HOT updates and to communicate that decision on to the index AMs.
+ */
+typedef struct UpdateContext
+{
+	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
+	struct ResultRelInfo *rri;	/* ResultRelInfo for the updated table. */
+	struct EState *estate;		/* EState used within the update. */
+	bool		crossPartUpdate;	/* Was it a cross-partition update? */
+
+	/*
+	 * Lock mode to acquire on the latest tuple version before performing
+	 * EvalPlanQual on it
+	 */
+	LockTupleMode lockmode;
+} UpdateContext;
+
 /*
  * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail
  * because the target tuple is already outdated, they fill in this struct to
@@ -549,8 +571,7 @@ typedef struct TableAmRoutine
 								 Snapshot crosscheck,
 								 bool wait,
 								 TM_FailureData *tmfd,
-								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 UpdateContext *updateCxt);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1504,13 +1525,11 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
 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,
-				   TU_UpdateIndexes *update_indexes)
+				   bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, updateCxt);
 }
 
 /*
@@ -2011,7 +2030,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  TU_UpdateIndexes *update_indexes);
+									  UpdateContext *updateCxt);
 
 
 /* ----------------------------------------------------------------------------
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 4daa8bef5ee..fdbf47f607b 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildExpressionIndexInfo(Relation index, IndexInfo *indexInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 6a1fec88928..507e142a10e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -773,6 +773,11 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecExpressionIndexesUpdated(ResultRelInfo *resultRelInfo,
+										 Bitmapset *modifiedAttrs,
+										 EState *estate,
+										 TupleTableSlot *old_tts,
+										 TupleTableSlot *new_tts);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e42f9f9f957..f60b20671b9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -160,12 +160,15 @@ typedef struct ExprState
  *
  *		NumIndexAttrs		total number of columns in this index
  *		NumIndexKeyAttrs	number of key columns in index
+ *		IndexAttrs			bitmap of index attributes
  *		IndexAttrNumbers	underlying-rel attribute numbers used as keys
  *							(zeroes indicate expressions). It also contains
  * 							info about included columns.
  *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionAttrs		bitmap of attributes used within the expression
  *		ExpressionsState	exec state for expressions, or NIL if none
  *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateAttrs		bitmap of attributes used within the predicate
  *		PredicateState		exec state for predicate, or NIL if none
  *		ExclusionOps		Per-column exclusion operators, or NULL if none
  *		ExclusionProcs		Underlying function OIDs for ExclusionOps
@@ -184,6 +187,7 @@ typedef struct ExprState
  *		ParallelWorkers		# of workers requested (excludes leader)
  *		Am					Oid of index AM
  *		AmCache				private cache area for index AM
+ *		OpClassDataTypes	operator class data types
  *		Context				memory context holding this IndexInfo
  *
  * ii_Concurrent, ii_BrokenHotChain, and ii_ParallelWorkers are used only
@@ -195,10 +199,17 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;	/* total number of columns in index */
 	int			ii_NumIndexKeyAttrs;	/* number of key columns in index */
+	Bitmapset  *ii_IndexAttrs;
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
+	uint16		ii_IndexAttrLen[INDEX_MAX_KEYS];
+	Bitmapset  *ii_IndexAttrByVal;
+	FmgrInfo   *ii_EqualityProc[INDEX_MAX_KEYS];
+	Oid			ii_Collation[INDEX_MAX_KEYS];
 	List	   *ii_Expressions; /* list of Expr */
+	Bitmapset  *ii_ExpressionAttrs;
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
+	Bitmapset  *ii_PredicateAttrs;
 	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;	/* array with one entry per column */
@@ -211,6 +222,8 @@ typedef struct IndexInfo
 	bool		ii_ReadyForInserts;
 	bool		ii_CheckedUnchanged;
 	bool		ii_IndexUnchanged;
+	bool		ii_CheckedPredicate;
+	bool		ii_PredicateSatisfied;
 	bool		ii_Concurrent;
 	bool		ii_BrokenHotChain;
 	bool		ii_Summarizing;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d94fddd7cef..e44d9966778 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -345,6 +346,7 @@ typedef struct StdRdOptions
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
 	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -406,6 +408,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index a7c55db339e..0cc28cb97e2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -63,6 +63,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..027c8760d69
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,665 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions         
+---------------------------
+ {expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+        reloptions        
+--------------------------
+ {expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using idx_ex_b on ex
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+               reloptions                
+-----------------------------------------
+ {fillfactor=60,expression_checks=false}
+(1 row)
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float /
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float /
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex'
+AND c.relnamespace = 'public'::regnamespace;
+ table_name | xact_updates | xact_hot_updates | xact_hot_update_percentage | total_updates | hot_updates | total_hot_update_percentage 
+------------+--------------+------------------+----------------------------+---------------+-------------+-----------------------------
+ ex         |            0 |                0 |                            |             6 |           1 |                       16.67
+(1 row)
+
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+SELECT * FROM ex;
+ id |     att1     |      att2       | att3 | att4 
+----+--------------+-----------------+------+------
+  1 | {"data": []} | nothing special |      | 
+(1 row)
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM ex;
+ id |     att1     |      att2      | att3 |   att4   
+----+--------------+----------------+------+----------
+  1 | {"data": []} | special indeed |      | whatever
+(1 row)
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        |        att2         | att3 |   att4   
+----+-------------------+---------------------+------+----------
+  1 | {"data": "howdy"} | special, so special |      | whatever
+(1 row)
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 with one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | a    | b    | c
+(1 row)
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 with one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | d    | e    | c
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 4 both indexes updated
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   3
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 0a35f2f8f6a..e2fd5fb74a5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..56ec4f2f96c
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,481 @@
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (info->>'name').  That means that the indexed
+-- attributes are 'id' and 'info'.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update keyvalue set info='{"name": "john", "data": "something else"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 row
+
+-- Re-enable expression checks.
+ALTER TABLE keyvalue SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 'keyvalue';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 row
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 1 no new HOT updates
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 2 rows now
+drop table keyvalue;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the info column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the info column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+create index colindex on keyvalue(info);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the info column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass); -- expect: 0 rows
+drop table keyvalue;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE ex (docs JSONB) WITH (fillfactor = 60);
+INSERT INTO ex (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO ex (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX idx_ex_a ON ex ((docs->>'a'));
+CREATE INDEX idx_ex_b ON ex ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 row
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM ex WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Disable expression checks.
+ALTER TABLE ex SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 'ex';
+
+-- This update changes 'b' to a value within its predicate just like it
+-- previous value, which would allow for a HOT update but with expression
+-- checks disabled we can't determine that so this should not be a HOT update.
+UPDATE ex SET docs = jsonb_build_object('a', 2, 'b', 2) WHERE (docs->>'b')::numeric = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+
+
+-- Let's make sure we're recording HOT updates for our 'ex' relation properly in the system
+-- table pg_stat_user_tables.  Note that statistics are stored within a transaction context
+-- first (xact) and then later into the global statistics for a relation, so first we need
+-- to ensure pending stats are flushed.
+SELECT pg_stat_force_next_flush();
+SELECT
+    c.relname AS table_name,
+    -- Transaction statistics
+    pg_stat_get_xact_tuples_updated(c.oid) AS xact_updates,
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS xact_hot_updates,
+    ROUND((
+        pg_stat_get_xact_tuples_hot_updated(c.oid)::float /
+        NULLIF(pg_stat_get_xact_tuples_updated(c.oid), 0) * 100
+    )::numeric, 2) AS xact_hot_update_percentage,
+    -- Cumulative statistics
+    s.n_tup_upd AS total_updates,
+    s.n_tup_hot_upd AS hot_updates,
+    ROUND((
+        s.n_tup_hot_upd::float /
+        NULLIF(s.n_tup_upd, 0) * 100
+    )::numeric, 2) AS total_hot_update_percentage
+FROM pg_class c
+LEFT JOIN pg_stat_user_tables s ON c.relname = s.relname
+WHERE c.relname = 'ex'
+AND c.relnamespace = 'public'::regnamespace;
+-- expect: 5 xact updates with 1 xact hot update and no cumulative updates as yet
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 0 no new HOT updates
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 a single new HOT update
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT pg_stat_get_xact_tuples_hot_updated('users'::regclass); -- expect: 1 no new HOT updates
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 0 no new HOT updates
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('events'::regclass); -- expect: 1 one new HOT update
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+
+SELECT * FROM ex;
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+SELECT * FROM ex;
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 no new HOT updates
+SELECT * FROM ex;
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 with one new HOT update
+SELECT * FROM ex;
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 with one new HOT update
+SELECT * FROM ex;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 1 one new HOT update
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 2 one new HOT update
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 3 one new HOT update
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT pg_stat_get_xact_tuples_hot_updated('ex'::regclass); -- expect: 4 both indexes updated
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3fbf5a4c212..0b6587dbd56 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2934,6 +2934,7 @@ TSVectorStat
 TState
 TStatus
 TStoreState
+UpdateContext
 TU_UpdateIndexes
 TXNEntryFile
 TYPCATEGORY
@@ -3118,7 +3119,6 @@ UniqueState
 UnlistenStmt
 UnresolvedTup
 UnresolvedTupData
-UpdateContext
 UpdateStmt
 UpgradeTask
 UpgradeTaskProcessCB
-- 
2.42.0

#24Greg Burd
greg@burd.me
In reply to: Burd, Greg (#23)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

I'm working again on expanding the conditions under which HOT updates are allowable, it is still a work-in-progress at this point. It has been a while since my last update to this patch set so I'll refresh everyone's memories (including myself) with an overview. Apologies in advance for a long email...

The goal is to allow HOT updates under two new conditions:
* when an indexed expression has not changed
* when possible for a partial index

Expression Indexes and HOT updates
==========================================================
Indexes on expressions such as:
CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));
CREATE INDEX people_names ON people ((first_name || ' ' || last_name));
CREATE INDEX names ON people((docs->>'user'));

These are not currently candidates for HOT updates because the attributes they reference are added to the hotblockingattrs bitmapset that is compared for overlap with the modified_attrs bitmap in heap_update() as created by HeapDetermineColumnsInfo(). Logically this exclusion makes sense because expressions used to form indexes can only contain references to functions that are IMMUTABLE. This then allows for a simple and efficient method for determining if the update is a candidate for the HOT path, a quick check for overlap of two bitmapsets and you have an answer.

But there is a common case that is overlooked. The third example above is an expression index that references a JSONB attribute 'docs' field 'name'. Imagine a simple bit of JSON stored in the 'docs' JSONB column changed by an UPDATE from:
{ "user": "scott", "password": "tiger" }
to:
{ "user": "scott", "password": "$ecret" }
this will not use the HOT path in today's code because the attribute for 'docs' will be referenced in the UPDATE statement and that attribute's content did change so it will be in the modified_attrs which will overlap with the hotblockingattrs set. The result is that it is not possible to use the HOT update path in heap_update() if there is an indexed JSONB column in that statement. This has a huge impact on performance and bloat. It's not hard to see that the index doesn't require an update, the portion of the document used to form the index tuple isn't changing.

This patch addressed this problem by evaluating the expression on the index using the current and new tuple and then comparing them. If there is no change to the index tuple then the HOT path is still an option for the update whereas before this was not the case.

There is at least one major challenge to this approach left to solve, it invokes UDFs while holding BUFFER_LOCK_EXCLUSIVE which we learn from heap_attr_equals() "we cannot safely invoke user-defined functions while holding exclusive buffer lock."

So, for performance an safety reasons it makes sense to try to limit the expressions that can be evaluated to those that can't self-deadlock trying to pin the same buffer and are roughly constant time and fast so we don't hold that lock for very long. Additionally it would be nice to ensure that we really have to do the evaluation in the first place.

I looked for ways to limit the types of expressions to evaluate so as to only take this approach when necessary, but I wasn't able to identify a good way forward. The problem is that it is possible to author a UDF similar to the json/jsonb getter functions json_extract_path() that extract a portion of a datum that is then used when creating an index. There is currently no demarcation on functions similar to "IMMUTABLE" (for example "EXTRACTOR") that would indicate that's what the UDF does nor does it strictly fit within the category of volatility as I don't think of this as information for the optimizer to use. Ideally you'd only want to evaluate the expression when any function in the expression is known to extract a portion of the datum. Were that the case you could require for HOT updates in the presence of expression indexes that at least one function in the expression be an "EXTRACTOR". I might still do this depending on reaction to it here, I'm seeking ideas.

Another idea was to change up the strategy a bit and not check expressions at all, but rather find a method to keep the attribute out of the modified_attrs set to begin with. This would mean that HeapDeterminColumnsInfo() would have to change, any maybe that's a good idea to try out too. It could even morph into something that can test equality using type-specific equality tests supporting custom types and invoke index access method-specific path extraction functions. In some ways this feels more "correct" to me in that HeapDeterminColumnsInfo()'s goal is to reduce the set of attributes in play from the interesting set to the modified set and if what is interesting is an index on a field within a document that isn't modified then it shouldn't be in the set. This comes with challenges related to that comment mentioned earlier in heap_attr_equals() and the fact that index access methods don't have a way to supply their equality op or provide a way to call a path operator should one exist. But the benefit would be that we expand HOT updates a bit further than my initial goals.

I'm not wedded to the use of a reloption to disable expression checks, nor do I love the name I choose ("expression_checks"). I'm interested to hear if I should simple remove this.

Another benefit to changing HeapDeterminColumnsInfo() is that it is what provides information to the PHOT patch during heap pruning to record which attributes were changed. As it stands now, this patch and the PHOT patch when combined result in some undesirable index scan results.

Partial Indexes and HOT updates
==========================================================

Partial indexes are a problem when it comes to HOT updates because they are part of the modified_attrs set returned by HeapDeterminColumnsInfo() even in cases where the value being set in the UPDATE does not satisfy the partial index predicate. Take for example:

CREATE TABLE example (a int, b int);
CREATE INDEX idx_a ON example(a);
CREATE INDEX idx_b ON example(b) WHERE b > 100;
INSERT INTO example (a, b) VALUES (1, 50);
UPDATE example SET b = 60 WHERE a = 1;

What happened? Was the update HOT? No. Why? Because the predicate isn't taken into account at the time the decision for HOT (or not) is made, that happens later during ExecInsertIndexTuples(). Today this update would trigger new index entries for both idx_a and idx_b when neither were required.

This patch changes this behavior by evaluating the predicate for the partial index earlier during heap_update(). When both the existing and updated tuples fall outside of the predicate the HOT update path is still an option. To limit scope creep I've not implemented the case when the existing was within the index's predicate but now the updated falls outside the predicate because that would require adding an optional delete function to the index access method (something that I think would be generally useful at some point). When that happens this patch does not allow a HOT update forcing the new tuple in the heap to not be redirected so later on index scan the CTID from the index will be ignored.

Summary
==========================================================

Attached find v17 of this patch rebased to this morning's (EDT) HEAD passing make world and formatted.

any and all feedback welcome.

-greg

Attachments:

v17-0001-Expand-HOT-update-path-to-include-expression-and.patchtext/x-patch; name="=?UTF-8?Q?v17-0001-Expand-HOT-update-path-to-include-expression-and.patc?= =?UTF-8?Q?h?="Download
From 4057bfe4636de795bb14afa230d77cb7ead13b34 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v17] Expand HOT update path to include expression and partial
 indexes.

This patch extends the cases where HOT updates are possible in the
heapam.  Expression indexes have historically been considered "hot
blocking" because the functions used within an expression must be marked
IMMUTABLE and so any changed attribute referenced within the expression
logically requires a new index tuple.  However this logic doesn't hold
true in cases where only a portion of an attribute is used to form the
index tuple as is commonly the case with JSONB columns or other index
access methods that have custom operators which extract a subset of an
attribute when forming the index tuple. In these cases while an
attribute in an UPDATE statement may have changed as determined by
HeapDetermineColumnsInfo() and is part of the modified_attrs set the
result of the expression used to form the index tuple may be unchanged
from the previous version.  This oversight makes it impossible to have a
HOT update when an update includes a JSONB column.  This patch corrects
that. The index expression is evaluated to form the before and after
index datums and compares them to determine if the mutation to the
attribute(s) referenced in the expression is in fact a change that
requires inserting a new index tuple or not.

Secondly, updates to attributes referenced by partial indexes are
effectively "HOT blocking" even when they fall outside of the index's
predicate forcing all indexes on the relation to be updated.  This patch
changes that behavior allowing HOT updates only in cases where the
predicate on the index is not satisfied for either the current or the
new tuple.

It is important to note that this patch is an evolution of a previous
patch
https://postgr.es/m/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied in c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0 and later reverted
in 05f84605dbeb9cf8279a157234b24bbb706c5256.

Signed-off-by: Greg Burd <greg@burd.me>
---
 doc/src/sgml/ref/create_table.sgml            |   18 +
 doc/src/sgml/storage.sgml                     |   29 +-
 src/backend/access/common/reloptions.c        |   12 +-
 src/backend/access/heap/README.HOT            |   45 +-
 src/backend/access/heap/heapam.c              |   61 +-
 src/backend/access/heap/heapam_handler.c      |   15 +-
 src/backend/access/table/tableam.c            |    5 +-
 src/backend/catalog/index.c                   |   48 +
 src/backend/catalog/indexing.c                |   18 +-
 src/backend/commands/copyfrom.c               |    2 +-
 src/backend/executor/execIndexing.c           |  212 +++-
 src/backend/executor/execPartition.c          |    2 +-
 src/backend/executor/execReplication.c        |   10 +-
 src/backend/executor/nodeModifyTable.c        |   31 +-
 src/backend/replication/logical/worker.c      |    6 +-
 src/backend/utils/cache/relcache.c            |   81 +-
 src/bin/psql/tab-complete.in.c                |    2 +-
 src/include/access/heapam.h                   |    6 +-
 src/include/access/tableam.h                  |   33 +-
 src/include/catalog/index.h                   |    1 +
 src/include/executor/executor.h               |    8 +-
 src/include/nodes/execnodes.h                 |   13 +
 src/include/utils/rel.h                       |   10 +
 src/include/utils/relcache.h                  |    1 +
 .../regress/expected/heap_hot_updates.out     | 1048 +++++++++++++++++
 src/test/regress/parallel_schedule            |    5 +
 src/test/regress/sql/heap_hot_updates.sql     |  718 +++++++++++
 src/tools/pgindent/typedefs.list              |    2 +-
 28 files changed, 2302 insertions(+), 140 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index dc000e913c1..bece90dc192 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1997,6 +1997,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 61250799ec0..6b340e885f2 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1097,10 +1097,10 @@ data. Empty in ordinary tables.</entry>
   <itemizedlist>
    <listitem>
     <para>
-     The update does not modify any columns referenced by the table's indexes,
-     not including summarizing indexes.  The only summarizing index method in
-     the core <productname>PostgreSQL</productname> distribution is <link
-     linkend="brin">BRIN</link>.
+     The update does not modify index keys, or when using a summarized
+     index.  The only summarizing index method in the core
+     <productname>PostgreSQL</productname> distribution is
+     <link linkend="brin">BRIN</link>.
      </para>
    </listitem>
    <listitem>
@@ -1138,6 +1138,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  an index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determine this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 50747c16396..407d5dcf0c9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1915,7 +1924,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..2340d82053f 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,28 +133,34 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results used when checking for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
 all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
-indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+indexes, the HOT optimization is applied and the update is propagated to
+all of the summarizing indexes.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0dcd6ee817e..9edd18808f8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3238,13 +3238,14 @@ 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,
-			TU_UpdateIndexes *update_indexes)
+			TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
+	LockTupleMode *lockmode = &updateCxt->lockmode;
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3323,6 +3324,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
@@ -3384,10 +3387,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		tmfd->ctid = *otid;
 		tmfd->xmax = InvalidTransactionId;
 		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3685,10 +3689,11 @@ l2:
 			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -4005,23 +4010,39 @@ l2:
 
 	if (newbuf == buffer)
 	{
+		ResultRelInfo *resultRelInfo = updateCxt->rri;
+		EState	   *estate = updateCxt->estate;
+
 		/*
-		 * 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.
+		 * hot_attrs includes indexes with expressions and indexes with
+		 * predicates that may not be impacted by this change.  If the
+		 * modified attributes in this update don't overlap with any
+		 * attributes referenced by indexes on the relation then we can use
+		 * the HOT update path.  If they do overlap, then check to see if the
+		 * overlap is exclusively due to attributes that are only referenced
+		 * within expressions.  If that is the case, the HOT update path may
+		 * be possible iff the expression indexes are unchanged by this update
+		 * or, with partial indexes, both the new and the old heap tuples
+		 * don't satisfy the partial index predicate expression (meaning they
+		 * are both outside of the scope of the index).
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(bms_is_subset(modified_attrs, exp_attrs) &&
+			 !ExecExprIndexesRequireUpdates(relation,
+											resultRelInfo,
+											modified_attrs,
+											estate,
+											resultRelInfo->ri_oldTupleSlot,
+											resultRelInfo->ri_newTupleSlot)))
 		{
 			use_hot_update = true;
 
 			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
+			 * If all modified attributes were only referenced by summarizing
+			 * indexes then we remain HOT, but we need to update those indexes
+			 * to ensure that they are consistent with the new tuple.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_is_subset(modified_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4192,18 +4213,19 @@ l2:
 	if (use_hot_update)
 	{
 		if (summarized_update)
-			*update_indexes = TU_Summarizing;
+			updateCxt->updateIndexes = TU_Summarizing;
 		else
-			*update_indexes = TU_None;
+			updateCxt->updateIndexes = TU_None;
 	}
 	else
-		*update_indexes = TU_All;
+		updateCxt->updateIndexes = TU_All;
 
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
 	bms_free(sum_attrs);
+	bms_free(exp_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4481,16 +4503,15 @@ HeapDetermineColumnsInfo(Relation relation,
  */
 void
 simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-				   TU_UpdateIndexes *update_indexes)
+				   UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
+						 &tmfd, updateCxt);
 	switch (result)
 	{
 		case TM_SelfModified:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index cb4bc35c93e..ae03ed5e718 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -316,8 +316,7 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -328,7 +327,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, update_indexes);
+						 tmfd, updateCxt);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
@@ -343,14 +342,14 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	 */
 	if (result != TM_Ok)
 	{
-		Assert(*update_indexes == TU_None);
-		*update_indexes = TU_None;
+		Assert(updateCxt->updateIndexes == TU_None);
+		updateCxt->updateIndexes = TU_None;
 	}
 	else if (!HeapTupleIsHeapOnly(tuple))
-		Assert(*update_indexes == TU_All);
+		Assert(updateCxt->updateIndexes == TU_All);
 	else
-		Assert((*update_indexes == TU_Summarizing) ||
-			   (*update_indexes == TU_None));
+		Assert((updateCxt->updateIndexes == TU_Summarizing) ||
+			   (updateCxt->updateIndexes == TU_None));
 
 	if (shouldFree)
 		pfree(tuple);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index a56c5eceb14..1eeeebadc09 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,17 +336,16 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  TU_UpdateIndexes *update_indexes)
+						  UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, updateCxt);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index aa216683b74..cace52120af 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2467,6 +2467,8 @@ BuildIndexInfo(Relation index)
 								 &ii->ii_ExclusionStrats);
 	}
 
+	ii->ii_IndexAttrByVal = NULL;
+
 	return ii;
 }
 
@@ -2707,6 +2709,52 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
 	}
 }
 
+/* ----------------
+ *		BuildExpressionIndexInfo
+ *			Add extra state to IndexInfo record
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildExpressionIndexInfo(Relation index, IndexInfo *ii)
+{
+	int			i;
+	int			indnkeyatts;
+
+	/*
+	 * Expressions are not allowed on non-key attributes, so we can skip them
+	 * as they should show up in the index HOT-blocking attributes.
+	 */
+	indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+
+	/*
+	 * Collect attributes used by the index, their len and if they are by
+	 * value.
+	 */
+	for (i = 0; i < indnkeyatts; i++)
+	{
+		CompactAttribute *attr = TupleDescCompactAttr(RelationGetDescr(index), i);
+
+		ii->ii_IndexAttrLen[i] = attr->attlen;
+		if (attr->attbyval)
+			ii->ii_IndexAttrByVal = bms_add_member(ii->ii_IndexAttrByVal, i);
+	}
+
+	/* collect attributes used in the expression */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionsAttrs);
+
+	/* collect attributes used in the predicate */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
+}
+
 /* ----------------
  *		FormIndexDatum
  *			Construct values[] and isnull[] arrays for a new index tuple.
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc87..c413c099da8 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -49,7 +49,7 @@ CatalogOpenIndexes(Relation heapRel)
 	resultRelInfo->ri_RelationDesc = heapRel;
 	resultRelInfo->ri_TrigDesc = NULL;	/* we don't fire triggers */
 
-	ExecOpenIndices(resultRelInfo, false);
+	ExecOpenIndices(resultRelInfo, false, false);
 
 	return resultRelInfo;
 }
@@ -313,15 +313,17 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -337,13 +339,15 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 }
 
 /*
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index fbbbc09a97b..f8271aa8866 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -921,7 +921,7 @@ CopyFrom(CopyFromState cstate)
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT, NIL);
 
-	ExecOpenIndices(resultRelInfo, false);
+	ExecOpenIndices(resultRelInfo, false, false);
 
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index ca33a854278..940311dc0d6 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -113,10 +113,13 @@
 #include "catalog/index.h"
 #include "executor/executor.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "storage/lmgr.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -157,7 +160,7 @@ static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum at
  * ----------------------------------------------------------------
  */
 void
-ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
+ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative, bool update)
 {
 	Relation	resultRelation = resultRelInfo->ri_RelationDesc;
 	List	   *indexoidlist;
@@ -220,6 +223,13 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		if (speculative && ii->ii_Unique && !indexDesc->rd_index->indisexclusion)
 			BuildSpeculativeIndexInfo(indexDesc, ii);
 
+		/*
+		 * If the index uses expressions then let's populate the additional
+		 * information nessaary to evaluate them for changes during updates.
+		 */
+		if (update && (ii->ii_Expressions || ii->ii_Predicate))
+			BuildExpressionIndexInfo(indexDesc, ii);
+
 		relationDescs[i] = indexDesc;
 		indexInfoArray[i] = ii;
 		i++;
@@ -382,19 +392,33 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			ExprState  *predicate;
 
 			/*
-			 * If predicate state not set up yet, create it (in the estate's
-			 * per-query context)
+			 * It is possible that we've already checked the predicate, if so
+			 * then avoid the duplicate work.
 			 */
-			predicate = indexInfo->ii_PredicateState;
-			if (predicate == NULL)
+			if (indexInfo->ii_CheckedPredicate)
 			{
-				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
-				indexInfo->ii_PredicateState = predicate;
+				/* Skip this index-update if the predicate isn't satisfied */
+				if (!indexInfo->ii_PredicateSatisfied)
+					continue;
 			}
+			else
+			{
 
-			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
-				continue;
+				/*
+				 * 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 */
+				if (!ExecQual(predicate, econtext))
+					continue;
+			}
 		}
 
 		/*
@@ -1095,6 +1119,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1172,3 +1199,168 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ *
+ * This will first determine if the index has a predicate and if so if the
+ * update satisfies that or not.  Then, if necessary, we compare old and
+ * new values of the indexed expression and help determine if it is possible
+ * to use a HOT update or not.
+ *
+ * 'resultRelInfo' is the table with the indexes we should examine.
+ * 'modified' is the set of attributes that are modified by the update.
+ * 'estate' is the executor state for the update.
+ * 'old_tts' is a slot with the old tuple.
+ * 'new_tts' is a slot with the new tuple.
+ *
+ * Returns true iff none of the indexes on this relation require updating.
+ *
+ * When the changes in new tuple impact a value stored in an index we must
+ * return true. When an index has a predicate that is not satisfied by either
+ * the new or old tuples then that index is unchanged. When an index has a
+ * predicate that is satisfied by both the old and new tuples then we can
+ * proceed and check to see if the indexed values were changed or not.
+ */
+bool
+ExecExprIndexesRequireUpdates(Relation relation,
+							  ResultRelInfo *resultRelInfo,
+							  Bitmapset *modifiedAttrs,
+							  EState *estate,
+							  TupleTableSlot *old_tts,
+							  TupleTableSlot *new_tts)
+{
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	bool		result = false;
+	IndexInfo  *indexInfo;
+	TupleTableSlot *save_scantuple;
+	ExprContext *econtext = NULL;
+
+	if (resultRelInfo == NULL || estate == NULL ||
+		old_tts == NULL || new_tts == NULL ||
+		!expression_checks || IsolationIsSerializable())
+		return true;
+
+	econtext = GetPerTupleExprContext(estate);
+
+	/*
+	 * Examine each index on this relation to see if it is affected by the
+	 * changes in newtup.  If any index is changed, we must not use a HOT
+	 * update.
+	 */
+	for (int i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		indexInfo = resultRelInfo->ri_IndexRelationInfo[i];
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modifiedAttrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			save_scantuple = econtext->ecxt_scantuple;
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateSatisfied = new_tuple_qualifies;
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require an index update.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionsAttrs, modifiedAttrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+
+			save_scantuple = econtext->ecxt_scantuple;
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+			econtext->ecxt_scantuple = save_scantuple;
+
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				if (old_isnull[j] != new_isnull[j])
+				{
+					result = true;
+					break;
+				}
+				else if (!old_isnull[j])
+				{
+					int16		elmlen = indexInfo->ii_IndexAttrLen[j];
+					bool		elmbyval = bms_is_member(j, indexInfo->ii_IndexAttrByVal);
+
+					if (!datum_image_eq(old_values[j], new_values[j],
+										elmbyval, elmlen))
+					{
+						result = true;
+						break;
+					}
+				}
+			}
+
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !result;
+
+			if (result)
+				break;
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 514eae1037d..2886705ed39 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -539,7 +539,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		leaf_part_rri->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(leaf_part_rri,
 						(node != NULL &&
-						 node->onConflictAction != ONCONFLICT_NONE));
+						 node->onConflictAction != ONCONFLICT_NONE), false);
 
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 53ddd25c42d..3c97fce0100 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -677,7 +677,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
+		UpdateContext updateCxt = {0};
 		List	   *conflictindexes;
 		bool		conflict = false;
 
@@ -693,17 +693,19 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		updateCxt.estate = estate;
+		updateCxt.rri = resultRelInfo;
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
-								  &update_indexes);
+								  &updateCxt);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
 
-		if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
+		if (resultRelInfo->ri_NumIndices > 0 && (updateCxt.updateIndexes != TU_None))
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   conflictindexes ? true : false,
 												   &conflict, conflictindexes,
-												   (update_indexes == TU_Summarizing));
+												   (updateCxt.updateIndexes == TU_Summarizing));
 
 		/*
 		 * Refer to the comments above the call to CheckAndReportConflict() in
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 54da8e7995b..b159ecb7b15 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -116,21 +116,6 @@ typedef struct ModifyTableContext
 	TupleTableSlot *cpUpdateReturningSlot;
 } ModifyTableContext;
 
-/*
- * Context struct containing output data specific to UPDATE operations.
- */
-typedef struct UpdateContext
-{
-	bool		crossPartUpdate;	/* was it a cross-partition update? */
-	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
-
-	/*
-	 * Lock mode to acquire on the latest tuple version before performing
-	 * EvalPlanQual on it
-	 */
-	LockTupleMode lockmode;
-} UpdateContext;
-
 
 static void ExecBatchInsert(ModifyTableState *mtstate,
 							ResultRelInfo *resultRelInfo,
@@ -890,7 +875,7 @@ ExecInsert(ModifyTableContext *context,
 	 */
 	if (resultRelationDesc->rd_rel->relhasindex &&
 		resultRelInfo->ri_IndexRelationDescs == NULL)
-		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE, false);
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -2105,7 +2090,7 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelationDesc->rd_rel->relhasindex &&
 		resultRelInfo->ri_IndexRelationDescs == NULL)
-		ExecOpenIndices(resultRelInfo, false);
+		ExecOpenIndices(resultRelInfo, false, true);
 
 	/* BEFORE ROW UPDATE triggers */
 	if (resultRelInfo->ri_TrigDesc &&
@@ -2303,8 +2288,7 @@ lreplace:
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
-								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&context->tmfd, updateCxt);
 
 	return result;
 }
@@ -2322,6 +2306,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 {
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
+	bool		onlySummarizing = updateCxt->updateIndexes == TU_Summarizing;
 
 	/* insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
@@ -2329,7 +2314,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   onlySummarizing);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
@@ -2465,6 +2450,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
 
+	updateCxt.estate = estate;
+	updateCxt.rri = resultRelInfo;
+
 	/*
 	 * abort the operation if not running transactions
 	 */
@@ -3153,6 +3141,9 @@ lmerge_matched:
 		TM_Result	result;
 		UpdateContext updateCxt = {0};
 
+		updateCxt.rri = resultRelInfo;
+		updateCxt.estate = estate;
+
 		/*
 		 * Test condition, if any.
 		 *
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index fd11805a44c..51054693f24 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -2452,7 +2452,7 @@ apply_handle_insert(StringInfo s)
 	{
 		ResultRelInfo *relinfo = edata->targetRelInfo;
 
-		ExecOpenIndices(relinfo, false);
+		ExecOpenIndices(relinfo, false, false);
 		apply_handle_insert_internal(edata, relinfo, remoteslot);
 		ExecCloseIndices(relinfo);
 	}
@@ -2675,7 +2675,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	MemoryContext oldctx;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
-	ExecOpenIndices(relinfo, false);
+	ExecOpenIndices(relinfo, false, true);
 
 	found = FindReplTupleInLocalRel(edata, localrel,
 									&relmapentry->remoterel,
@@ -2819,7 +2819,7 @@ apply_handle_delete(StringInfo s)
 	{
 		ResultRelInfo *relinfo = edata->targetRelInfo;
 
-		ExecOpenIndices(relinfo, false);
+		ExecOpenIndices(relinfo, false, false);
 		apply_handle_delete_internal(edata, relinfo,
 									 remoteslot, rel->localindexoid);
 		ExecCloseIndices(relinfo);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 559ba9cdb2c..b7e78fb3bf1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -2482,6 +2483,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_expressionattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5285,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_EXPRESSION	Columns included in expresion indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5305,8 +5308,9 @@ RelationGetIndexAttrBitmap(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 */
-	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *idx_attrs;		/* columns referenced by indexes */
+	Bitmapset  *expr_attrs;		/* columns referenced by index expressions */
+	Bitmapset  *sum_attrs;		/* columns with summarizing indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5333,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5371,8 +5377,9 @@ restart:
 	uindexattrs = NULL;
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
-	hotblockingattrs = NULL;
-	summarizedattrs = NULL;
+	idx_attrs = NULL;
+	expr_attrs = NULL;
+	sum_attrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5386,6 +5393,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5429,20 +5437,26 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
-			attrs = &summarizedattrs;
+		{
+			attrs = &sum_attrs;
+			exprattrs = &sum_attrs;
+		}
 		else
-			attrs = &hotblockingattrs;
+		{
+			attrs = &idx_attrs;
+			exprattrs = &expr_attrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = 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
-			 * hotblockingattrs or summarizedattrs, since they are in index,
-			 * and update shouldn't miss them.
+			 * idx_attrs or sum_attrs, since they are in index, and update
+			 * shouldn't miss them.
 			 *
 			 * Summarizing indexes do not block HOT, but do need to be updated
 			 * when the column value changes, thus require a separate
@@ -5452,30 +5466,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5503,12 +5515,24 @@ restart:
 		bms_free(uindexattrs);
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
-		bms_free(hotblockingattrs);
-		bms_free(summarizedattrs);
+		bms_free(idx_attrs);
+		bms_free(expr_attrs);
+		bms_free(sum_attrs);
 
 		goto restart;
 	}
 
+	/*
+	 * HOT-blocking attributes should include all columns that are part of the
+	 * index except attributes only referenced in expressions, including
+	 * expressions used to form partial indexes.  So, we need to remove the
+	 * expression-only attributes from the HOT-blocking columns bitmap as
+	 * those will be checked separately.
+	 */
+	expr_attrs = bms_del_members(expr_attrs, idx_attrs);
+	idx_attrs = bms_add_members(idx_attrs, expr_attrs);
+	expr_attrs = bms_add_members(expr_attrs, sum_attrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5545,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5533,8 +5559,9 @@ restart:
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_pkattr = bms_copy(pkindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
-	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
-	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_hotblockingattr = bms_copy(idx_attrs);
+	relation->rd_summarizedattr = bms_copy(sum_attrs);
+	relation->rd_expressionattr = bms_copy(expr_attrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5548,9 +5575,11 @@ restart:
 		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 			return idindexattrs;
 		case INDEX_ATTR_BITMAP_HOT_BLOCKING:
-			return hotblockingattrs;
+			return idx_attrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
-			return summarizedattrs;
+			return sum_attrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return expr_attrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8c2ea0b9587..07d1cffdefa 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3007,7 +3007,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index a2bd5a897f8..5dd0922f52f 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -22,6 +22,7 @@
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
 #include "commands/vacuum.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -329,8 +330,7 @@ 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,
-							 TU_UpdateIndexes *update_indexes);
+							 struct TM_FailureData *tmfd, UpdateContext *updateCxt);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -365,7 +365,7 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+							   HeapTuple tup, UpdateContext *updateCxt);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 1c9e802a6b1..932f8ff6c4c 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -118,6 +118,28 @@ typedef enum TU_UpdateIndexes
 	TU_Summarizing,
 } TU_UpdateIndexes;
 
+/*
+ * Data specific to processing UPDATE operations.
+ *
+ * When table_tuple_update is called some storage managers, notably heapam,
+ * can at times avoid index updates.  In the heapam this is known as a HOT
+ * update.  This struct is used to provide the state required to test for
+ * HOT updates and to communicate that decision on to the index AMs.
+ */
+typedef struct UpdateContext
+{
+	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
+	struct ResultRelInfo *rri;	/* ResultRelInfo for the updated table. */
+	struct EState *estate;		/* EState used within the update. */
+	bool		crossPartUpdate;	/* Was it a cross-partition update? */
+
+	/*
+	 * Lock mode to acquire on the latest tuple version before performing
+	 * EvalPlanQual on it
+	 */
+	LockTupleMode lockmode;
+} UpdateContext;
+
 /*
  * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail
  * because the target tuple is already outdated, they fill in this struct to
@@ -542,8 +564,7 @@ typedef struct TableAmRoutine
 								 Snapshot crosscheck,
 								 bool wait,
 								 TM_FailureData *tmfd,
-								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 UpdateContext *updateCxt);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1494,13 +1515,11 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
 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,
-				   TU_UpdateIndexes *update_indexes)
+				   bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, updateCxt);
 }
 
 /*
@@ -2001,7 +2020,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  TU_UpdateIndexes *update_indexes);
+									  UpdateContext *updateCxt);
 
 
 /* ----------------------------------------------------------------------------
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 4daa8bef5ee..fdbf47f607b 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildExpressionIndexInfo(Relation index, IndexInfo *indexInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 104b059544d..9cffdaa8561 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -731,7 +731,7 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 /*
  * prototypes from functions in execIndexing.c
  */
-extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
+extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative, bool update);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
@@ -749,6 +749,12 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecExprIndexesRequireUpdates(Relation relation,
+										  ResultRelInfo *resultRelInfo,
+										  Bitmapset *modifiedAttrs,
+										  EState *estate,
+										  TupleTableSlot *old_tts,
+										  TupleTableSlot *new_tts);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e107d6e5f81..bc6d1919103 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -180,11 +180,20 @@ typedef struct IndexInfo
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes referenced by expressions, or NULL if none */
+	Bitmapset  *ii_ExpressionsAttrs;
+
+	/* index attribute length */
+	uint16		ii_IndexAttrLen[INDEX_MAX_KEYS];
+	/* is the index attribute by-value */
+	Bitmapset  *ii_IndexAttrByVal;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate, or NULL if none */
+	Bitmapset  *ii_PredicateAttrs;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -208,6 +217,10 @@ typedef struct IndexInfo
 	bool		ii_CheckedUnchanged;
 	/* aminsert hint, cached for retail inserts */
 	bool		ii_IndexUnchanged;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..27ed8db903d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -348,6 +349,7 @@ typedef struct StdRdOptions
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
 	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -409,6 +411,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..4b312bc8d06 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..7d22befc34a
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,1048 @@
+-- Create a function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN,
+    has_indexes BOOLEAN,
+    index_count INT,
+    fillfactor INT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+
+    -- We need to wait for statistics to update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Construct qualified name
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+
+    -- Get the OID using regclass
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+        RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+
+    -- Get current transaction stats
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    -- Combine stats
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+        p_table_name::TEXT,
+        v_updates::BIGINT as total_updates,
+        v_hot_updates::BIGINT as hot_updates,
+        CASE
+            WHEN v_updates > 0 THEN
+                ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+            ELSE 0
+        END as hot_update_percentage,
+        (v_hot_updates = expected)::BOOLEAN as matches_expected,
+        (EXISTS (
+            SELECT 1 FROM pg_index WHERE indrelid = v_relid
+        ))::BOOLEAN as has_indexes,
+        (
+            SELECT COUNT(*)::INT
+            FROM pg_index
+            WHERE indrelid = v_relid
+        ) as index_count,
+        COALESCE(
+            (
+                SELECT (regexp_match(array_to_string(reloptions, ','), 'fillfactor=(\d+)'))[1]::int
+                FROM pg_class
+                WHERE oid = v_relid
+            ),
+            100
+        ) as fillfactor;
+END;
+$$;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (docs->>'name').  That means that the indexed
+-- attributes are 'id' and 'docs'.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE t SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+                           reloptions                           
+----------------------------------------------------------------
+ {autovacuum_enabled=off,fillfactor=70,expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update t set docs='{"name": "john", "data": "something else"}' where id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE t SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+                          reloptions                           
+---------------------------------------------------------------
+ {autovacuum_enabled=off,fillfactor=70,expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           1 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+UPDATE t SET docs='{"name": "smith", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           1 |                 33.33 | t                | t           |           2 |         70
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+UPDATE t SET docs='{"name": "smith", "data": "some more data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           2 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the docs column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the docs column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the docs column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           3 |         70
+(1 row)
+
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE t (docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO t (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO t (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX t_idx_a ON t ((docs->>'a'));
+CREATE INDEX t_idx_b ON t ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           1 |                100.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           1 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           1 |                 33.33 | t                | t           |           2 |         70
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           1 |                 25.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             5 |           1 |                 20.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to ensure that HOT updates are not performed when multiple indexed
+-- attributes are updated.
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(a);
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (1, -1);
+-- Both are updated, the second is an expression index with an unchanged
+-- index value.  The change to the index on a should prevent HOT updates.
+UPDATE t SET a = 2, b = 1 WHERE a = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           2 |         70
+(1 row)
+
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to check the expression_checks reloption behavior.
+--
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(abs(a)) WHERE abs(a) > 10;
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (-1, -1), (-2, -2), (-3, -3), (-4, -4);
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Disable expression checks on indexes and partial index predicates.
+ALTER TABLE t SET (expression_checks = false);
+-- Before and after values of a are outside the predicate of the index and
+-- the indexed value of b hasn't changed however we've disabled expression
+-- checks so this should not be a HOT update.
+-- (-1, -1) -> (-5, -1)
+UPDATE t SET a = -5, b = -1 WHERE a = -1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Enable expression checks on indexes, but not on predicates yet.
+ALTER TABLE t SET (expression_checks = true);
+-- The indexed value of b hasn't changed, this should be a HOT update.
+-- (-5, -1) -> (-5, 1)
+UPDATE t SET b = 1 WHERE a = -5;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           1 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Now that we're not checking the predicate of the partial index, this
+-- update of a from -5 to 5 should be HOT because we should ignore the
+-- predicate and check the expression and find it unchanged.
+-- (-5, 1) -> (5, 1)
+UPDATE t SET a = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           2 |                 66.67 | t                | t           |           2 |         70
+(1 row)
+
+-- This update meets the critera for the partial index and should not
+-- be HOT.  Let's make sure of that and check the index as well.
+-- (-4, -4) -> (-11, -4)
+UPDATE t SET a = -11 WHERE a = -4;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           2 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+  a  | b  
+-----+----
+ -11 | -4
+(1 row)
+
+-- (-11, -4) -> (11, -4)
+UPDATE t SET a = 11 WHERE a = -11;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             5 |           3 |                 60.00 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+ a  | b  
+----+----
+ 11 | -4
+(1 row)
+
+-- (11, -4) -> (-4, -4)
+UPDATE t SET a = -4 WHERE a = 11;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             6 |           3 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+ a | b 
+---+---
+(0 rows)
+
+-- This update of a from 5 to -1 is HOT despite that attribute
+-- being indexed because the before and after values for the
+-- partial index predicate are outside the index definition.
+-- (5, 1) -> (-1, 1)
+UPDATE t SET a = -1 WHERE a = 5;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             7 |           4 |                 57.14 | t                | t           |           2 |         70
+(1 row)
+
+-- This update of a from -2 to -1 with predicate checks enabled should be
+-- HOT because the before/after values of a are both outside the predicate
+-- of the partial index.
+-- (-1, 1) -> (-2, 1)
+UPDATE t SET a = -2 WHERE a = -1;
+SELECT * FROM check_hot_updates(5);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             8 |           5 |                 62.50 | t                | t           |           2 |         70
+(1 row)
+
+-- The indexed value for b isn't changing, this should be HOT.
+-- (-2, -2) -> (-2, 2)
+UPDATE t SET b = 2 WHERE b = -2;
+SELECT * FROM check_hot_updates(6);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             9 |           6 |                 66.67 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT abs(b) FROM t;
+    QUERY PLAN    
+------------------
+ Seq Scan on t
+   Disabled: true
+(2 rows)
+
+SELECT abs(b) FROM t;
+ abs 
+-----
+   3
+   4
+   1
+   2
+(4 rows)
+
+-- Before and after values for a are outside the predicate of the index,
+-- and because we're checking this should be HOT.
+-- (-2, 1) -> (5, 1)
+-- (-2, -2) -> (5, -2)
+UPDATE t SET a = 5 WHERE a = -2;
+SELECT * FROM check_hot_updates(8);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |            11 |           8 |                 72.73 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+ a | b 
+---+---
+(0 rows)
+
+SELECT * FROM t;
+ a  | b  
+----+----
+ -3 | -3
+ -4 | -4
+  5 |  1
+  5 |  2
+(4 rows)
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The tests here examines the behavior of HOT updates when the relation
+-- has a JSONB column with an index on the field 'a' and the partial index
+-- expression on a different JSONB field 'b'.
+CREATE TABLE t(docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'a')) WHERE (docs->'b')::integer = 1;
+INSERT INTO t VALUES ('{"a": 1, "b": 1}');
+EXPLAIN (COSTS OFF) SELECT * FROM t;
+  QUERY PLAN   
+---------------
+ Seq Scan on t
+(1 row)
+
+SELECT * FROM t;
+       docs       
+------------------
+ {"a": 1, "b": 1}
+(1 row)
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::integer = 1;
+            QUERY PLAN            
+----------------------------------
+ Index Scan using t_docs_idx on t
+(1 row)
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+       docs       
+------------------
+ {"a": 1, "b": 1}
+(1 row)
+
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             0 |           0 |                     0 | t                | t           |           1 |         70
+(1 row)
+
+UPDATE t SET docs='{"a": 1, "b": 0}';
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+ docs 
+------
+(0 rows)
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests for multi-column indexes
+--
+CREATE TABLE t(id INT, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t(id, (docs->'a'));
+INSERT INTO t VALUES (1, '{"a": 1, "b": 1}');
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using t_docs_idx on t
+   Index Cond: (id > 0)
+   Filter: (((docs -> 'a'::text))::integer > 0)
+(3 rows)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  1 | {"a": 1, "b": 1}
+(1 row)
+
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             0 |           0 |                     0 | t                | t           |           1 |         70
+(1 row)
+
+-- Changing the id attribute which is an indexed attribute should
+-- prevent HOT updates.
+UPDATE t SET id = 2;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  2 | {"a": 1, "b": 1}
+(1 row)
+
+-- Changing the docs->'a' field in the indexed attribute 'docs'
+-- should prevent HOT updates.
+UPDATE t SET docs='{"a": -2, "b": 1}';
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           0 |                  0.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+ id |       docs        
+----+-------------------
+  2 | {"a": -2, "b": 1}
+(1 row)
+
+-- Leaving the docs->'a' attribute unchanged means that the expression
+-- is unchanged and because the 'id' attribute isn't in the modified
+-- set the indexed tuple is unchanged, this can go HOT.
+UPDATE t SET docs='{"a": -2, "b": 2}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           1 |                 33.33 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+ id |       docs        
+----+-------------------
+  2 | {"a": -2, "b": 2}
+(1 row)
+
+-- Here we change the 'id' attribute and the 'docs' attribute setting
+-- the expression docs->'a' to a new value, this cannot be a HOT update.
+UPDATE t SET id = 3, docs='{"a": 3, "b": 3}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           1 |                 25.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  3 | {"a": 3, "b": 3}
+(1 row)
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT * FROM check_hot_updates(0, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             1 |           0 |                  0.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             2 |           1 |                 50.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             3 |           1 |                 33.33 | t                | t           |           3 |        100
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             4 |           1 |                 25.00 | t                | t           |           3 |        100
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT * FROM check_hot_updates(0, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ events     |             1 |           0 |                  0.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ events     |             2 |           0 |                  0.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ events     |             3 |           1 |                 33.33 | t                | t           |           2 |        100
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+SELECT * FROM ex;
+ id |     att1     |      att2       | att3 | att4 
+----+--------------+-----------------+------+------
+  1 | {"data": []} | nothing special |      | 
+(1 row)
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             1 |           1 |                100.00 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |     att1     |      att2      | att3 |   att4   
+----+--------------+----------------+------+----------
+  1 | {"data": []} | special indeed |      | whatever
+(1 row)
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             2 |           1 |                 50.00 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        |        att2         | att3 |   att4   
+----+-------------------+---------------------+------+----------
+  1 | {"data": "howdy"} | special, so special |      | whatever
+(1 row)
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT * FROM check_hot_updates(2, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             3 |           2 |                 66.67 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | a    | b    | c
+(1 row)
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             4 |           3 |                 75.00 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | d    | e    | c
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             1 |           1 |                100.00 | t                | t           |           2 |         60
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT * FROM check_hot_updates(2, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             2 |           2 |                100.00 | t                | t           |           2 |         60
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             3 |           3 |                100.00 | t                | t           |           2 |         60
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT * FROM check_hot_updates(4, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             4 |           3 |                 75.00 | f                | t           |           2 |         60
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             1 |           0 |                  0.00 | t                | t           |           1 |        100
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             3 |           0 |                  0.00 | t                | t           |           1 |        100
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             4 |           0 |                  0.00 | t                | t           |           1 |        100
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             5 |           1 |                 20.00 | f                | t           |           1 |        100
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a424be2a6bf..f712acd9a20 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..7016e9eabd0
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,718 @@
+-- Create a function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN,
+    has_indexes BOOLEAN,
+    index_count INT,
+    fillfactor INT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+
+    -- We need to wait for statistics to update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Construct qualified name
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+
+    -- Get the OID using regclass
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+        RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+
+    -- Get current transaction stats
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    -- Combine stats
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+        p_table_name::TEXT,
+        v_updates::BIGINT as total_updates,
+        v_hot_updates::BIGINT as hot_updates,
+        CASE
+            WHEN v_updates > 0 THEN
+                ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+            ELSE 0
+        END as hot_update_percentage,
+        (v_hot_updates = expected)::BOOLEAN as matches_expected,
+        (EXISTS (
+            SELECT 1 FROM pg_index WHERE indrelid = v_relid
+        ))::BOOLEAN as has_indexes,
+        (
+            SELECT COUNT(*)::INT
+            FROM pg_index
+            WHERE indrelid = v_relid
+        ) as index_count,
+        COALESCE(
+            (
+                SELECT (regexp_match(array_to_string(reloptions, ','), 'fillfactor=(\d+)'))[1]::int
+                FROM pg_class
+                WHERE oid = v_relid
+            ),
+            100
+        ) as fillfactor;
+END;
+$$;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (docs->>'name').  That means that the indexed
+-- attributes are 'id' and 'docs'.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE t SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update t set docs='{"name": "john", "data": "something else"}' where id=1;
+SELECT * FROM check_hot_updates(0);
+
+-- Re-enable expression checks.
+ALTER TABLE t SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+UPDATE t SET docs='{"name": "smith", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+UPDATE t SET docs='{"name": "smith", "data": "some more data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the docs column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the docs column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the docs column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE t (docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO t (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO t (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX t_idx_a ON t ((docs->>'a'));
+CREATE INDEX t_idx_b ON t ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to ensure that HOT updates are not performed when multiple indexed
+-- attributes are updated.
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(a);
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (1, -1);
+
+-- Both are updated, the second is an expression index with an unchanged
+-- index value.  The change to the index on a should prevent HOT updates.
+UPDATE t SET a = 2, b = 1 WHERE a = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to check the expression_checks reloption behavior.
+--
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(abs(a)) WHERE abs(a) > 10;
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (-1, -1), (-2, -2), (-3, -3), (-4, -4);
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Disable expression checks on indexes and partial index predicates.
+ALTER TABLE t SET (expression_checks = false);
+
+-- Before and after values of a are outside the predicate of the index and
+-- the indexed value of b hasn't changed however we've disabled expression
+-- checks so this should not be a HOT update.
+-- (-1, -1) -> (-5, -1)
+UPDATE t SET a = -5, b = -1 WHERE a = -1;
+SELECT * FROM check_hot_updates(0);
+
+-- Enable expression checks on indexes, but not on predicates yet.
+ALTER TABLE t SET (expression_checks = true);
+
+-- The indexed value of b hasn't changed, this should be a HOT update.
+-- (-5, -1) -> (-5, 1)
+UPDATE t SET b = 1 WHERE a = -5;
+SELECT * FROM check_hot_updates(1);
+
+-- Now that we're not checking the predicate of the partial index, this
+-- update of a from -5 to 5 should be HOT because we should ignore the
+-- predicate and check the expression and find it unchanged.
+-- (-5, 1) -> (5, 1)
+UPDATE t SET a = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(2);
+
+-- This update meets the critera for the partial index and should not
+-- be HOT.  Let's make sure of that and check the index as well.
+-- (-4, -4) -> (-11, -4)
+UPDATE t SET a = -11 WHERE a = -4;
+SELECT * FROM check_hot_updates(2);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+-- (-11, -4) -> (11, -4)
+UPDATE t SET a = 11 WHERE a = -11;
+SELECT * FROM check_hot_updates(3);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+-- (11, -4) -> (-4, -4)
+UPDATE t SET a = -4 WHERE a = 11;
+SELECT * FROM check_hot_updates(3);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+-- This update of a from 5 to -1 is HOT despite that attribute
+-- being indexed because the before and after values for the
+-- partial index predicate are outside the index definition.
+-- (5, 1) -> (-1, 1)
+UPDATE t SET a = -1 WHERE a = 5;
+SELECT * FROM check_hot_updates(4);
+
+-- This update of a from -2 to -1 with predicate checks enabled should be
+-- HOT because the before/after values of a are both outside the predicate
+-- of the partial index.
+-- (-1, 1) -> (-2, 1)
+UPDATE t SET a = -2 WHERE a = -1;
+SELECT * FROM check_hot_updates(5);
+
+-- The indexed value for b isn't changing, this should be HOT.
+-- (-2, -2) -> (-2, 2)
+UPDATE t SET b = 2 WHERE b = -2;
+SELECT * FROM check_hot_updates(6);
+EXPLAIN (COSTS OFF) SELECT abs(b) FROM t;
+SELECT abs(b) FROM t;
+
+-- Before and after values for a are outside the predicate of the index,
+-- and because we're checking this should be HOT.
+-- (-2, 1) -> (5, 1)
+-- (-2, -2) -> (5, -2)
+UPDATE t SET a = 5 WHERE a = -2;
+SELECT * FROM check_hot_updates(8);
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+SELECT * FROM t;
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The tests here examines the behavior of HOT updates when the relation
+-- has a JSONB column with an index on the field 'a' and the partial index
+-- expression on a different JSONB field 'b'.
+CREATE TABLE t(docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'a')) WHERE (docs->'b')::integer = 1;
+INSERT INTO t VALUES ('{"a": 1, "b": 1}');
+
+EXPLAIN (COSTS OFF) SELECT * FROM t;
+SELECT * FROM t;
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::integer = 1;
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+
+SELECT * FROM check_hot_updates(0);
+
+UPDATE t SET docs='{"a": 1, "b": 0}';
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests for multi-column indexes
+--
+CREATE TABLE t(id INT, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t(id, (docs->'a'));
+INSERT INTO t VALUES (1, '{"a": 1, "b": 1}');
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+SELECT * FROM check_hot_updates(0);
+
+-- Changing the id attribute which is an indexed attribute should
+-- prevent HOT updates.
+UPDATE t SET id = 2;
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+-- Changing the docs->'a' field in the indexed attribute 'docs'
+-- should prevent HOT updates.
+UPDATE t SET docs='{"a": -2, "b": 1}';
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+
+-- Leaving the docs->'a' attribute unchanged means that the expression
+-- is unchanged and because the 'id' attribute isn't in the modified
+-- set the indexed tuple is unchanged, this can go HOT.
+UPDATE t SET docs='{"a": -2, "b": 2}';
+SELECT * FROM check_hot_updates(1);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+
+-- Here we change the 'id' attribute and the 'docs' attribute setting
+-- the expression docs->'a' to a new value, this cannot be a HOT update.
+UPDATE t SET id = 3, docs='{"a": 3, "b": 3}';
+SELECT * FROM check_hot_updates(1);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE t;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT * FROM check_hot_updates(0, 'users');
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT * FROM check_hot_updates(1, 'users');
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'events');
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+
+SELECT * FROM ex;
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT * FROM check_hot_updates(1, 'ex');
+SELECT * FROM ex;
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(1, 'ex');
+SELECT * FROM ex;
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT * FROM check_hot_updates(2, 'ex');
+SELECT * FROM ex;
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT * FROM check_hot_updates(3, 'ex');
+SELECT * FROM ex;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT * FROM check_hot_updates(1, 'ex');
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT * FROM check_hot_updates(2, 'ex');
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(3, 'ex');
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT * FROM check_hot_updates(4, 'ex');
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
+
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 220e5a4f6b3..b5cdf2ac89a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2967,6 +2967,7 @@ TSVectorStat
 TState
 TStatus
 TStoreState
+UpdateContext
 TU_UpdateIndexes
 TXNEntryFile
 TYPCATEGORY
@@ -3154,7 +3155,6 @@ UniqueState
 UnlistenStmt
 UnresolvedTup
 UnresolvedTupData
-UpdateContext
 UpdateStmt
 UpgradeTask
 UpgradeTaskProcessCB
-- 
2.49.0

#25Greg Burd
greg@burd.me
In reply to: Greg Burd (#24)
1 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

On Jul 2 2025, at 2:10 pm, Greg Burd <greg@burd.me> wrote:

The goal is to allow HOT updates under two new conditions:
* when an indexed expression has not changed
* when possible for a partial index

This is still true. :)

This patch has languished for nearly 2+ months at v17. Why? Primarily
due to feedback that although the idea had merit, there was a critical
flaw in the approach that made it a non-starter. The flaw was that I'd
been executing expressions while holding both the pin and a lock on the
buffer, which is not a great idea (self dead lock, etc.). This was
pointed out to me (thanks Robert Haas!) and so I needed to re-think my approach.

I put the patch aside for a while, then this past week at PGConf.dev/NYC
I heard interest from a few people (Jeff Davis, Nathan Bossart) who
encouraged me to move the code executing the expressions to just before
acquiring the lock but after pinning the buffer. The theory being that
my new code using the old/new tts to form and test the index tuples
resulting from executing expressions was using the resultsRelInfo struct
created during plan execution, not the information found on the page,
and so was safe without the lock.

This proved tricky because I had been using the modified_attrs and
expr_attrs as a test to avoid exercising expressions when unnecessary.
Calling HeapDetermineColumnsInfo() outside the buffer lock to get
modified_attrs proved to be a problem as it examines an oldtup that is
cobbled together from the elements on the page, requiring the lock I was
trying to avoid.

After reviewing how updates work in the executor, I discovered that
during execution the new tuple slot is populated with the information
from ExecBuildUpdateProjection() and the old tuple, but that most
importantly for this use case that function created a bitmap of the
modified columns (the columns specified in the update). This bitmap
isn't the same as the one produced by HeapDetermineColumnsInfo() as the
latter excludes attributes that are not changed after testing equality
with the helper function heap_attr_equals() where as the former will
include attributes that appear in the update but are the same value as
before. This, happily, is immaterial for the purposes of my function
ExecExprIndexesRequireUpdates() which simply needs to check to see if
index tuples generated are unchanged. So I had all I needed to run the
checks ahead of acquiring the lock on the buffer.

So, this led to v18 (attached), which passes test wold including a
number of new tests for the various corner cases relative to HOT updates
for expressions.

There is much room for improvement, and your suggestions are welcome.

I'll find time to quantify the benefit of this patch for the targeted
use cases and to ensure that all other cases see no regressions.

I need to review the tests I've added to ensure that they are the
minimal set required, that they communicate effectively their purpose,
etc. For now, it's more shotgun than scalpel, .... I'll get to it.

I added a reloption "expression_checks" to disable this new code path.
Good idea or bad precedent?

In execIndexing I special case for IsolationIsSerializable() and I can't
remember why now but I do recall one isolation test failing... I'll
check on this and get back to the thread. Or maybe you know why that

From small things like naming to larger questions like hijacking the
modified columns bitmapset for use later in the heap, I need feedback
and guidance.

I'm using UpdateContext for estate/resultRelInfo and I've added to that
lockmode, these change the table am API a tad, I'm open to better ideas
that accomplish the same.

I'd like not to build, then rebuild index tuples for these expressions
but I can't think of a way to do that without a palloc(), this is
avoided today.

Example:

CREATE TABLE t (
x INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
y JSONB
);
CREATE INDEX t_age_idx ON t (((y->>'age')::int));

INSERT INTO t (y)
VALUES ('{"name": "John", "age": 30, "city": "New York"}');

-- Update an indexed field in the JSONB document, should not be HOT
UPDATE t
SET y = jsonb_set(y, '{age}', '31')
WHERE x = 1;

SELECT pg_stat_force_next_flush();
SELECT relname, n_tup_upd, n_tup_hot_upd FROM pg_stat_user_tables
WHERE relname = 't';

-- Update a non-indexed field in the JSONB document, new HOT update
UPDATE t
SET y = jsonb_set(y, '{city}', '"Boston"')
WHERE x = 1;

SELECT pg_stat_force_next_flush();
SELECT relname, n_tup_upd, n_tup_hot_upd FROM pg_stat_user_tables
WHERE relname = 't';

This patch is far from "commit ready", but it does accomplish the
$subject and pass tests.

I look forward to any/all constructive feedback.

best.

-greg

Attachments:

v18-0001-Expand-HOT-update-path-to-include-expression-and.patchapplication/octet-streamDownload
From 24ed9c7dbf1297faebb484eebe2750c3b6a33d58 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Mon, 27 Jan 2025 13:28:59 -0500
Subject: [PATCH v18] Expand HOT update path to include expression and partial
 indexes

Expression indexes have historically been considered "hot blocking", but
this choice significanly impares the utility of the JSONB data type as
all indexes referencing data within documents are formed using
expressions. Thus an update to a JSONB column will prevent the HOT path
and force all indexes across all columns to be updated.

Updates to attributes referenced by partial indexes may fall entirely
outside of the range of values maintained by an index, but despite that
they are effectively "HOT blocking".  This too is changed to allow for
HOT updates in cases where the predicate on the index is not satisfied
for either the old or the new tuple.

It appears that the primary reason for not checking expression indexes
or the expressions forming partial indexes, besides the overhead of the
expression itself, was that the execution of expression could
self-deadlock.  Here we are careful to hold the pin, but not the buffer
lock when evaluating the expressions.  We are careful to only evaluate
expressions when they are both in the modified set for the update and
the modified attribute is only referenced by vars in those expressions.

It is important to note that this patch was inspired by, but is a
significant departure from, a previous patch.
https://postgr.es/m/4d9928ee-a9e6-15f9-9c82-5981f13ffca6%40postgrespro.ru
applied in c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0 and later reverted
in 05f84605dbeb9cf8279a157234b24bbb706c5256.

Signed-off-by: Greg Burd <greg@burd.me>
---
 doc/src/sgml/ref/create_table.sgml            |   18 +
 doc/src/sgml/storage.sgml                     |   29 +-
 src/backend/access/common/reloptions.c        |   12 +-
 src/backend/access/heap/README.HOT            |   45 +-
 src/backend/access/heap/heapam.c              |   71 +-
 src/backend/access/heap/heapam_handler.c      |   15 +-
 src/backend/access/table/tableam.c            |    5 +-
 src/backend/catalog/index.c                   |   48 +
 src/backend/catalog/indexing.c                |   18 +-
 src/backend/commands/copyfrom.c               |    2 +-
 src/backend/executor/execExpr.c               |    7 +-
 src/backend/executor/execIndexing.c           |  212 +++-
 src/backend/executor/execPartition.c          |    2 +-
 src/backend/executor/execReplication.c        |   10 +-
 src/backend/executor/nodeModifyTable.c        |   31 +-
 src/backend/replication/logical/worker.c      |    6 +-
 src/backend/utils/cache/relcache.c            |   81 +-
 src/bin/psql/tab-complete.in.c                |    2 +-
 src/include/access/heapam.h                   |    6 +-
 src/include/access/tableam.h                  |   36 +-
 src/include/catalog/index.h                   |    1 +
 src/include/executor/executor.h               |    8 +-
 src/include/nodes/execnodes.h                 |   15 +
 src/include/utils/rel.h                       |   10 +
 src/include/utils/relcache.h                  |    1 +
 .../regress/expected/heap_hot_updates.out     | 1048 +++++++++++++++++
 src/test/regress/parallel_schedule            |    5 +
 src/test/regress/sql/heap_hot_updates.sql     |  718 +++++++++++
 src/tools/pgindent/typedefs.list              |    2 +-
 29 files changed, 2321 insertions(+), 143 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index dc000e913c1..bece90dc192 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1997,6 +1997,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-expression-checks" xreflabel="expression_checks">
+    <term><literal>expression_checks</literal> (<type>boolean</type>)
+    <indexterm>
+     <primary><varname>expression_checks</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Enables or disables evaulation of predicate expressions on partial
+      indexes or expressions used to define indexes during updates.
+      If <literal>true</literal>, then these expressions are evaluated during
+      updates to data within the heap relation against the old and new values
+      and then compared to determine if <acronym>HOT</acronym> updates are
+      allowable or not. The default value is <literal>true</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    </variablelist>
 
   </refsect2>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 02ddfda834a..581ac0a5a51 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -1101,10 +1101,10 @@ data. Empty in ordinary tables.</entry>
   <itemizedlist>
    <listitem>
     <para>
-     The update does not modify any columns referenced by the table's indexes,
-     not including summarizing indexes.  The only summarizing index method in
-     the core <productname>PostgreSQL</productname> distribution is <link
-     linkend="brin">BRIN</link>.
+     The update does not modify index keys, or when using a summarized
+     index.  The only summarizing index method in the core
+     <productname>PostgreSQL</productname> distribution is
+     <link linkend="brin">BRIN</link>.
      </para>
    </listitem>
    <listitem>
@@ -1142,6 +1142,27 @@ data. Empty in ordinary tables.</entry>
   </itemizedlist>
  </para>
 
+ <para>
+  <acronym>HOT</acronym> updates can occur when the expression used to define
+  an index shows no changes to the indexed value. To determine this requires
+  that the expression be evaulated for the old and new values to be stored in
+  the index and then compared. This allows for <acronym>HOT</acronym> updates
+  when data indexed within JSONB columns is unchanged. To disable this
+  behavior and avoid the overhead of evaluating the expression during updates
+  set the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
+ <para>
+  <acronym>HOT</acronym> updates can also occur when updated values are not
+  within the predicate of a partial index. However, <acronym>HOT</acronym>
+  updates are not possible when the updated value and the current value differ
+  with regards to the predicate. To determine this requires that the predicate
+  expression be evaluated for the old and new values to be stored in the index
+  and then compared. To disable this behavior and avoid the overhead of
+  evaluating the expression during updates set
+  the <literal>expression_checks</literal> option to false for the table.
+ </para>
+
  <para>
   You can increase the likelihood of sufficient page space for
   <acronym>HOT</acronym> updates by decreasing a table's <link
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 35150bf237b..5983064a1be 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"expression_checks",
+			"When disabled prevents checking expressions on indexes and predicates on partial indexes for changes that might influence heap-only tuple (HOT) updates.",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1915,7 +1924,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"vacuum_truncate", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)},
+		{"expression_checks", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, expression_checks)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate, kind,
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 74e407f375a..2340d82053f 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -36,7 +36,7 @@ HOT solves this problem for two restricted but useful special cases:
 First, where a tuple is repeatedly updated in ways that do not change
 its indexed columns.  (Here, "indexed column" means any column referenced
 at all in an index definition, including for example columns that are
-tested in a partial-index predicate but are not stored in the index.)
+tested in a partial-index predicate, that has materially changed.)
 
 Second, where the modified columns are only used in indexes that do not
 contain tuple IDs, but maintain summaries of the indexed data by block.
@@ -133,28 +133,34 @@ Note: we can use a "dead" line pointer for any DELETEd tuple,
 whether it was part of a HOT chain or not.  This allows space reclamation
 in advance of running VACUUM for plain DELETEs as well as HOT updates.
 
-The requirement for doing a HOT update is that indexes which point to
-the root line pointer (and thus need to be cleaned up by VACUUM when the
-tuple is dead) do not reference columns which are updated in that HOT
-chain.  Summarizing indexes (such as BRIN) are assumed to have no
-references to individual tuples and thus are ignored when checking HOT
-applicability.  The updated columns are checked at execution time by
-comparing the binary representation of the old and new values.  We insist
-on bitwise equality rather than using datatype-specific equality routines.
-The main reason to avoid the latter is that there might be multiple
-notions of equality for a datatype, and we don't know exactly which one
-is relevant for the indexes at hand.  We assume that bitwise equality
-guarantees equality for all purposes.
+The requirement for doing a HOT update is that indexes which point to the root
+line pointer (and thus need to be cleaned up by VACUUM when the tuple is dead)
+do not reference columns which are updated in that HOT chain.
+
+Summarizing indexes (such as BRIN) are assumed to have no references to
+individual tuples and thus are ignored when checking HOT applicability.
+
+Expressions on indexes are evaluated and the results used when checking for
+changes.  This allows for the JSONB datatype to have HOT updates when the
+indexed portion of the document are not modified.
+
+Partial index expressions are evaluated, HOT updates are allowed when the
+updated index values do not satisfy the predicate.
+
+The updated columns are checked at execution time by comparing the binary
+representation of the old and new values.  We insist on bitwise equality rather
+than using datatype-specific equality routines.  The main reason to avoid the
+latter is that there might be multiple notions of equality for a datatype, and
+we don't know exactly which one is relevant for the indexes at hand.  We assume
+that bitwise equality guarantees equality for all purposes.
 
 If any columns that are included by non-summarizing indexes are updated,
 the HOT optimization is not applied, and the new tuple is inserted into
 all indexes of the table.  If none of the updated columns are included in
 the table's indexes, the HOT optimization is applied and no indexes are
 updated.  If instead the updated columns are only indexed by summarizing
-indexes, the HOT optimization is applied, but the update is propagated to
-all summarizing indexes.  (Realistically, we only need to propagate the
-update to the indexes that contain the updated values, but that is yet to
-be implemented.)
+indexes, the HOT optimization is applied and the update is propagated to
+all of the summarizing indexes.
 
 Abort Cases
 -----------
@@ -477,8 +483,9 @@ Heap-only tuple
 HOT-safe
 
 	A proposed tuple update is said to be HOT-safe if it changes
-	none of the tuple's indexed columns.  It will only become an
-	actual HOT update if we can find room on the same page for
+	none of the tuple's indexed columns or if the changes remain
+	outside of a partial index's predicate.  It will only become
+	an actual HOT update if we can find room on the same page for
 	the new tuple version.
 
 HOT update
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ed0c0c2dc9f..78d7e9dfc2d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3246,13 +3246,14 @@ 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,
-			TU_UpdateIndexes *update_indexes)
+			TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
+	LockTupleMode *lockmode = &updateCxt->lockmode;
 	Bitmapset  *hot_attrs;
 	Bitmapset  *sum_attrs;
+	Bitmapset  *exp_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
 	Bitmapset  *interesting_attrs;
@@ -3282,6 +3283,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		checked_lockers;
 	bool		locker_remains;
 	bool		id_has_external = false;
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	bool		expr_idx_updated = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3331,12 +3334,15 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
 	sum_attrs = RelationGetIndexAttrBitmap(relation,
 										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	exp_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_EXPRESSION);
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
 	interesting_attrs = NULL;
 	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
+	interesting_attrs = bms_add_members(interesting_attrs, exp_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
 
@@ -3345,6 +3351,29 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	buffer = ReadBuffer(relation, block);
 	page = BufferGetPage(buffer);
 
+	/*
+	 * hot_attrs includes indexes with expressions and indexes with predicates
+	 * that may not be impacted by this change.  If the modified attributes in
+	 * this update don't overlap with any attributes referenced by indexes on
+	 * the relation then we can use the HOT update path.  If they do overlap,
+	 * then check to see if the overlap is exclusively due to attributes that
+	 * are only referenced within expressions.  If that is the case, the HOT
+	 * update path may be possible iff the expression indexes are unchanged by
+	 * this update or, with partial indexes, both the new and the old heap
+	 * tuples don't satisfy the partial index predicate expression (meaning
+	 * they are both outside of the scope of the index).
+	 */
+	if (expression_checks &&
+		updateCxt->rri &&
+		updateCxt->rri->ri_projectNew &&
+		bms_is_subset(updateCxt->rri->ri_projectNew->pi_modifiedCols, exp_attrs))
+		expr_idx_updated = ExecExprIndexesRequireUpdates(relation,
+														 updateCxt->rri,
+														 updateCxt->rri->ri_projectNew->pi_modifiedCols,
+														 updateCxt->estate,
+														 updateCxt->rri->ri_oldTupleSlot,
+														 updateCxt->rri->ri_newTupleSlot);
+
 	/*
 	 * Before locking the buffer, pin the visibility map page if it appears to
 	 * be necessary.  Since we haven't got the lock yet, someone else might be
@@ -3392,10 +3421,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		tmfd->ctid = *otid;
 		tmfd->xmax = InvalidTransactionId;
 		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		/* modified_attrs not yet initialized */
@@ -3693,10 +3723,11 @@ l2:
 			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
-		*update_indexes = TU_None;
+		updateCxt->updateIndexes = TU_None;
 
 		bms_free(hot_attrs);
 		bms_free(sum_attrs);
+		bms_free(exp_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
@@ -4014,22 +4045,22 @@ l2:
 	if (newbuf == buffer)
 	{
 		/*
-		 * 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 all modified attributes were only referenced by summarizing
+		 * indexes then we remain HOT, but we need to update those indexes to
+		 * ensure that they are consistent with the new tuple.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(modified_attrs, hot_attrs) ||
+			(expression_checks &&
+			 (bms_is_subset(modified_attrs, exp_attrs) && !expr_idx_updated)))
 		{
 			use_hot_update = true;
 
 			/*
-			 * If none of the columns that are used in hot-blocking indexes
-			 * were updated, we can apply HOT, but we do still need to check
-			 * if we need to update the summarizing indexes, and update those
-			 * indexes if the columns were updated, or we may fail to detect
-			 * e.g. value bound changes in BRIN minmax indexes.
+			 * If all modified attributes were only referenced by summarizing
+			 * indexes then we remain HOT, but we need to update those indexes
+			 * to ensure that they are consistent with the new tuple.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_is_subset(modified_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4200,18 +4231,19 @@ l2:
 	if (use_hot_update)
 	{
 		if (summarized_update)
-			*update_indexes = TU_Summarizing;
+			updateCxt->updateIndexes = TU_Summarizing;
 		else
-			*update_indexes = TU_None;
+			updateCxt->updateIndexes = TU_None;
 	}
 	else
-		*update_indexes = TU_All;
+		updateCxt->updateIndexes = TU_All;
 
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
 	bms_free(hot_attrs);
 	bms_free(sum_attrs);
+	bms_free(exp_attrs);
 	bms_free(key_attrs);
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
@@ -4489,16 +4521,15 @@ HeapDetermineColumnsInfo(Relation relation,
  */
 void
 simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
-				   TU_UpdateIndexes *update_indexes)
+				   UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
+						 &tmfd, updateCxt);
 	switch (result)
 	{
 		case TM_SelfModified:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..a7e65f7e2f6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -316,8 +316,7 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 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, TU_UpdateIndexes *update_indexes)
+					bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -328,7 +327,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, update_indexes);
+						 tmfd, updateCxt);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
@@ -343,14 +342,14 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	 */
 	if (result != TM_Ok)
 	{
-		Assert(*update_indexes == TU_None);
-		*update_indexes = TU_None;
+		Assert(updateCxt->updateIndexes == TU_None);
+		updateCxt->updateIndexes = TU_None;
 	}
 	else if (!HeapTupleIsHeapOnly(tuple))
-		Assert(*update_indexes == TU_All);
+		Assert(updateCxt->updateIndexes == TU_All);
 	else
-		Assert((*update_indexes == TU_Summarizing) ||
-			   (*update_indexes == TU_None));
+		Assert((updateCxt->updateIndexes == TU_Summarizing) ||
+			   (updateCxt->updateIndexes == TU_None));
 
 	if (shouldFree)
 		pfree(tuple);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 5e41404937e..c1d41a1fb87 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,17 +336,16 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  TU_UpdateIndexes *update_indexes)
+						  UpdateContext *updateCxt)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
-	LockTupleMode lockmode;
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, updateCxt);
 
 	switch (result)
 	{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..205147fe341 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2467,6 +2467,8 @@ BuildIndexInfo(Relation index)
 								 &ii->ii_ExclusionStrats);
 	}
 
+	ii->ii_IndexAttrByVal = NULL;
+
 	return ii;
 }
 
@@ -2707,6 +2709,52 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
 	}
 }
 
+/* ----------------
+ *		BuildExpressionIndexInfo
+ *			Add extra state to IndexInfo record
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildExpressionIndexInfo(Relation index, IndexInfo *ii)
+{
+	int			i;
+	int			indnkeyatts;
+
+	/*
+	 * Expressions are not allowed on non-key attributes, so we can skip them
+	 * as they should show up in the index HOT-blocking attributes.
+	 */
+	indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+
+	/*
+	 * Collect attributes used by the index, their len and if they are by
+	 * value.
+	 */
+	for (i = 0; i < indnkeyatts; i++)
+	{
+		CompactAttribute *attr = TupleDescCompactAttr(RelationGetDescr(index), i);
+
+		ii->ii_IndexAttrLen[i] = attr->attlen;
+		if (attr->attbyval)
+			ii->ii_IndexAttrByVal = bms_add_member(ii->ii_IndexAttrByVal, i);
+	}
+
+	/* collect attributes used in the expression */
+	if (ii->ii_Expressions)
+		pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_ExpressionsAttrs);
+
+	/* collect attributes used in the predicate */
+	if (ii->ii_Predicate)
+		pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_PredicateAttrs);
+}
+
 /* ----------------
  *		FormIndexDatum
  *			Construct values[] and isnull[] arrays for a new index tuple.
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc87..c413c099da8 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -49,7 +49,7 @@ CatalogOpenIndexes(Relation heapRel)
 	resultRelInfo->ri_RelationDesc = heapRel;
 	resultRelInfo->ri_TrigDesc = NULL;	/* we don't fire triggers */
 
-	ExecOpenIndices(resultRelInfo, false);
+	ExecOpenIndices(resultRelInfo, false, false);
 
 	return resultRelInfo;
 }
@@ -313,15 +313,17 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -337,13 +339,15 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	TU_UpdateIndexes updateIndexes = TU_All;
+	UpdateContext updateCxt = {0};
+
+	updateCxt.updateIndexes = TU_All;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	simple_heap_update(heapRel, otid, tup, &updateCxt);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	CatalogIndexInsert(indstate, tup, updateCxt.updateIndexes);
 }
 
 /*
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 12781963b4f..aea42c49524 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -921,7 +921,7 @@ CopyFrom(CopyFromState cstate)
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT, ONCONFLICT_NONE, NIL);
 
-	ExecOpenIndices(resultRelInfo, false);
+	ExecOpenIndices(resultRelInfo, false, false);
 
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..8bb8692b46e 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -610,8 +610,9 @@ ExecBuildUpdateProjection(List *targetList,
 	{
 		AttrNumber	targetattnum = lfirst_int(lc);
 
-		assignedCols = bms_add_member(assignedCols, targetattnum);
+		assignedCols = bms_add_member(assignedCols, targetattnum - FirstLowInvalidHeapAttributeNumber);
 	}
+	projInfo->pi_modifiedCols = assignedCols;
 
 	/*
 	 * We need to insert EEOP_*_FETCHSOME steps to ensure the input tuples are
@@ -624,7 +625,7 @@ ExecBuildUpdateProjection(List *targetList,
 
 		if (attr->attisdropped)
 			continue;
-		if (bms_is_member(attnum, assignedCols))
+		if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, assignedCols))
 			continue;
 		deform.last_scan = attnum;
 		break;
@@ -732,7 +733,7 @@ ExecBuildUpdateProjection(List *targetList,
 			scratch.d.assign_tmp.resultnum = attnum - 1;
 			ExprEvalPushStep(state, &scratch);
 		}
-		else if (!bms_is_member(attnum, assignedCols))
+		else if (!bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, assignedCols))
 		{
 			/* Certainly the right type, so needn't check */
 			scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index ca33a854278..940311dc0d6 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -113,10 +113,13 @@
 #include "catalog/index.h"
 #include "executor/executor.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "storage/lmgr.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -157,7 +160,7 @@ static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum at
  * ----------------------------------------------------------------
  */
 void
-ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
+ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative, bool update)
 {
 	Relation	resultRelation = resultRelInfo->ri_RelationDesc;
 	List	   *indexoidlist;
@@ -220,6 +223,13 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 		if (speculative && ii->ii_Unique && !indexDesc->rd_index->indisexclusion)
 			BuildSpeculativeIndexInfo(indexDesc, ii);
 
+		/*
+		 * If the index uses expressions then let's populate the additional
+		 * information nessaary to evaluate them for changes during updates.
+		 */
+		if (update && (ii->ii_Expressions || ii->ii_Predicate))
+			BuildExpressionIndexInfo(indexDesc, ii);
+
 		relationDescs[i] = indexDesc;
 		indexInfoArray[i] = ii;
 		i++;
@@ -382,19 +392,33 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 			ExprState  *predicate;
 
 			/*
-			 * If predicate state not set up yet, create it (in the estate's
-			 * per-query context)
+			 * It is possible that we've already checked the predicate, if so
+			 * then avoid the duplicate work.
 			 */
-			predicate = indexInfo->ii_PredicateState;
-			if (predicate == NULL)
+			if (indexInfo->ii_CheckedPredicate)
 			{
-				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
-				indexInfo->ii_PredicateState = predicate;
+				/* Skip this index-update if the predicate isn't satisfied */
+				if (!indexInfo->ii_PredicateSatisfied)
+					continue;
 			}
+			else
+			{
 
-			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
-				continue;
+				/*
+				 * 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 */
+				if (!ExecQual(predicate, econtext))
+					continue;
+			}
 		}
 
 		/*
@@ -1095,6 +1119,9 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 
 	if (hasexpression)
 	{
+		if (indexInfo->ii_IndexUnchanged)
+			return true;
+
 		indexInfo->ii_IndexUnchanged = false;
 		return false;
 	}
@@ -1172,3 +1199,168 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ *
+ * This will first determine if the index has a predicate and if so if the
+ * update satisfies that or not.  Then, if necessary, we compare old and
+ * new values of the indexed expression and help determine if it is possible
+ * to use a HOT update or not.
+ *
+ * 'resultRelInfo' is the table with the indexes we should examine.
+ * 'modified' is the set of attributes that are modified by the update.
+ * 'estate' is the executor state for the update.
+ * 'old_tts' is a slot with the old tuple.
+ * 'new_tts' is a slot with the new tuple.
+ *
+ * Returns true iff none of the indexes on this relation require updating.
+ *
+ * When the changes in new tuple impact a value stored in an index we must
+ * return true. When an index has a predicate that is not satisfied by either
+ * the new or old tuples then that index is unchanged. When an index has a
+ * predicate that is satisfied by both the old and new tuples then we can
+ * proceed and check to see if the indexed values were changed or not.
+ */
+bool
+ExecExprIndexesRequireUpdates(Relation relation,
+							  ResultRelInfo *resultRelInfo,
+							  Bitmapset *modifiedAttrs,
+							  EState *estate,
+							  TupleTableSlot *old_tts,
+							  TupleTableSlot *new_tts)
+{
+	bool		expression_checks = RelationGetExpressionChecks(relation);
+	bool		result = false;
+	IndexInfo  *indexInfo;
+	TupleTableSlot *save_scantuple;
+	ExprContext *econtext = NULL;
+
+	if (resultRelInfo == NULL || estate == NULL ||
+		old_tts == NULL || new_tts == NULL ||
+		!expression_checks || IsolationIsSerializable())
+		return true;
+
+	econtext = GetPerTupleExprContext(estate);
+
+	/*
+	 * Examine each index on this relation to see if it is affected by the
+	 * changes in newtup.  If any index is changed, we must not use a HOT
+	 * update.
+	 */
+	for (int i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		indexInfo = resultRelInfo->ri_IndexRelationInfo[i];
+
+		/*
+		 * If this is a partial index it has a predicate, evaluate the
+		 * expression to determine if we need to include it or not.
+		 */
+		if (bms_overlap(indexInfo->ii_PredicateAttrs, modifiedAttrs))
+		{
+			ExprState  *pstate;
+			bool		old_tuple_qualifies,
+						new_tuple_qualifies;
+
+			pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+
+			/*
+			 * Here the term "qualifies" means "satisfies the predicate
+			 * condition of the partial index".
+			 */
+			save_scantuple = econtext->ecxt_scantuple;
+			econtext->ecxt_scantuple = old_tts;
+			old_tuple_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_tuple_qualifies = ExecQual(pstate, econtext);
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateSatisfied = new_tuple_qualifies;
+
+			/*
+			 * If neither the old nor the new tuples satisfy the predicate we
+			 * can be sure that this index doesn't need updating, continue to
+			 * the next index.
+			 */
+			if ((new_tuple_qualifies == false) && (old_tuple_qualifies == false))
+				continue;
+
+			/*
+			 * If there is a transition between indexed and not indexed,
+			 * that's enough to require an index update.
+			 */
+			if (new_tuple_qualifies != old_tuple_qualifies)
+			{
+				result = true;
+				break;
+			}
+
+			/*
+			 * Otherwise the old and new values exist in the index, but did
+			 * they get updated?  We don't yet know, so proceed with the next
+			 * statement in the loop to find out.
+			 */
+		}
+
+		/*
+		 * Indexes with expressions may or may not have changed, it is
+		 * impossible to know without exercising their expression and
+		 * reviewing index tuple state for changes.  This is a lot of work,
+		 * but because all indexes on JSONB columns fall into this category it
+		 * can be worth it to avoid index updates and remain on the HOT update
+		 * path when possible.
+		 */
+		if (bms_overlap(indexInfo->ii_ExpressionsAttrs, modifiedAttrs))
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+
+			save_scantuple = econtext->ecxt_scantuple;
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo,
+						   old_tts,
+						   estate,
+						   old_values,
+						   old_isnull);
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo,
+						   new_tts,
+						   estate,
+						   new_values,
+						   new_isnull);
+			econtext->ecxt_scantuple = save_scantuple;
+
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				if (old_isnull[j] != new_isnull[j])
+				{
+					result = true;
+					break;
+				}
+				else if (!old_isnull[j])
+				{
+					int16		elmlen = indexInfo->ii_IndexAttrLen[j];
+					bool		elmbyval = bms_is_member(j, indexInfo->ii_IndexAttrByVal);
+
+					if (!datum_image_eq(old_values[j], new_values[j],
+										elmbyval, elmlen))
+					{
+						result = true;
+						break;
+					}
+				}
+			}
+
+			indexInfo->ii_CheckedUnchanged = true;
+			indexInfo->ii_IndexUnchanged = !result;
+
+			if (result)
+				break;
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 1f2da072632..2f48919a620 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -544,7 +544,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		leaf_part_rri->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(leaf_part_rri,
 						(node != NULL &&
-						 node->onConflictAction != ONCONFLICT_NONE));
+						 node->onConflictAction != ONCONFLICT_NONE), false);
 
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index b409d4ecbf5..7f10cbff0df 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -920,7 +920,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
-		TU_UpdateIndexes update_indexes;
+		UpdateContext updateCxt = {0};
 		List	   *conflictindexes;
 		bool		conflict = false;
 
@@ -936,17 +936,19 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		updateCxt.estate = estate;
+		updateCxt.rri = resultRelInfo;
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
-								  &update_indexes);
+								  &updateCxt);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
 
-		if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
+		if (resultRelInfo->ri_NumIndices > 0 && (updateCxt.updateIndexes != TU_None))
 			recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 												   slot, estate, true,
 												   conflictindexes ? true : false,
 												   &conflict, conflictindexes,
-												   (update_indexes == TU_Summarizing));
+												   (updateCxt.updateIndexes == TU_Summarizing));
 
 		/*
 		 * Refer to the comments above the call to CheckAndReportConflict() in
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4c5647ac38a..db3d8789846 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -116,21 +116,6 @@ typedef struct ModifyTableContext
 	TupleTableSlot *cpUpdateReturningSlot;
 } ModifyTableContext;
 
-/*
- * Context struct containing output data specific to UPDATE operations.
- */
-typedef struct UpdateContext
-{
-	bool		crossPartUpdate;	/* was it a cross-partition update? */
-	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
-
-	/*
-	 * Lock mode to acquire on the latest tuple version before performing
-	 * EvalPlanQual on it
-	 */
-	LockTupleMode lockmode;
-} UpdateContext;
-
 
 static void ExecBatchInsert(ModifyTableState *mtstate,
 							ResultRelInfo *resultRelInfo,
@@ -890,7 +875,7 @@ ExecInsert(ModifyTableContext *context,
 	 */
 	if (resultRelationDesc->rd_rel->relhasindex &&
 		resultRelInfo->ri_IndexRelationDescs == NULL)
-		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE, false);
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -2106,7 +2091,7 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelationDesc->rd_rel->relhasindex &&
 		resultRelInfo->ri_IndexRelationDescs == NULL)
-		ExecOpenIndices(resultRelInfo, false);
+		ExecOpenIndices(resultRelInfo, false, true);
 
 	/* BEFORE ROW UPDATE triggers */
 	if (resultRelInfo->ri_TrigDesc &&
@@ -2305,8 +2290,7 @@ lreplace:
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
-								&context->tmfd, &updateCxt->lockmode,
-								&updateCxt->updateIndexes);
+								&context->tmfd, updateCxt);
 
 	return result;
 }
@@ -2324,6 +2308,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 {
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
+	bool		onlySummarizing = updateCxt->updateIndexes == TU_Summarizing;
 
 	/* insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
@@ -2331,7 +2316,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 											   slot, context->estate,
 											   true, false,
 											   NULL, NIL,
-											   (updateCxt->updateIndexes == TU_Summarizing));
+											   onlySummarizing);
 
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(context->estate, resultRelInfo,
@@ -2467,6 +2452,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
 
+	updateCxt.estate = estate;
+	updateCxt.rri = resultRelInfo;
+
 	/*
 	 * abort the operation if not running transactions
 	 */
@@ -3155,6 +3143,9 @@ lmerge_matched:
 		TM_Result	result;
 		UpdateContext updateCxt = {0};
 
+		updateCxt.rri = resultRelInfo;
+		updateCxt.estate = estate;
+
 		/*
 		 * Test condition, if any.
 		 *
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 419e478b4c6..91bb00f5a64 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -2672,7 +2672,7 @@ apply_handle_insert(StringInfo s)
 	{
 		ResultRelInfo *relinfo = edata->targetRelInfo;
 
-		ExecOpenIndices(relinfo, false);
+		ExecOpenIndices(relinfo, false, false);
 		apply_handle_insert_internal(edata, relinfo, remoteslot);
 		ExecCloseIndices(relinfo);
 	}
@@ -2895,7 +2895,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	MemoryContext oldctx;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
-	ExecOpenIndices(relinfo, false);
+	ExecOpenIndices(relinfo, false, true);
 
 	found = FindReplTupleInLocalRel(edata, localrel,
 									&relmapentry->remoterel,
@@ -3053,7 +3053,7 @@ apply_handle_delete(StringInfo s)
 	{
 		ResultRelInfo *relinfo = edata->targetRelInfo;
 
-		ExecOpenIndices(relinfo, false);
+		ExecOpenIndices(relinfo, false, false);
 		apply_handle_delete_internal(edata, relinfo,
 									 remoteslot, rel->localindexoid);
 		ExecCloseIndices(relinfo);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2b798b823ea..1538e6c0f79 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/index.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -2482,6 +2483,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_expressionattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5285,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_EXPRESSION	Columns included in expresion indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5305,8 +5308,9 @@ RelationGetIndexAttrBitmap(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 */
-	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *idx_attrs;		/* columns referenced by indexes */
+	Bitmapset  *expr_attrs;		/* columns referenced by index expressions */
+	Bitmapset  *sum_attrs;		/* columns with summarizing indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5333,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_EXPRESSION:
+				return bms_copy(relation->rd_expressionattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5371,8 +5377,9 @@ restart:
 	uindexattrs = NULL;
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
-	hotblockingattrs = NULL;
-	summarizedattrs = NULL;
+	idx_attrs = NULL;
+	expr_attrs = NULL;
+	sum_attrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5386,6 +5393,7 @@ restart:
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
 		Bitmapset **attrs;
+		Bitmapset **exprattrs;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -5429,20 +5437,26 @@ restart:
 		 * decide which bitmap we'll update in the following loop.
 		 */
 		if (indexDesc->rd_indam->amsummarizing)
-			attrs = &summarizedattrs;
+		{
+			attrs = &sum_attrs;
+			exprattrs = &sum_attrs;
+		}
 		else
-			attrs = &hotblockingattrs;
+		{
+			attrs = &idx_attrs;
+			exprattrs = &expr_attrs;
+		}
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexDesc->rd_index->indnatts; i++)
 		{
-			int			attrnum = indexDesc->rd_index->indkey.values[i];
+			int			attridx = 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
-			 * hotblockingattrs or summarizedattrs, since they are in index,
-			 * and update shouldn't miss them.
+			 * idx_attrs or sum_attrs, since they are in index, and update
+			 * shouldn't miss them.
 			 *
 			 * Summarizing indexes do not block HOT, but do need to be updated
 			 * when the column value changes, thus require a separate
@@ -5452,30 +5466,28 @@ restart:
 			 * key or identity key. Hence we do not include them into
 			 * uindexattrs, pkindexattrs and idindexattrs bitmaps.
 			 */
-			if (attrnum != 0)
+			if (attridx != 0)
 			{
-				*attrs = bms_add_member(*attrs,
-										attrnum - FirstLowInvalidHeapAttributeNumber);
+				AttrNumber	attrnum = attridx - FirstLowInvalidHeapAttributeNumber;
+
+				*attrs = bms_add_member(*attrs, attrnum);
 
 				if (isKey && i < indexDesc->rd_index->indnkeyatts)
-					uindexattrs = bms_add_member(uindexattrs,
-												 attrnum - FirstLowInvalidHeapAttributeNumber);
+					uindexattrs = bms_add_member(uindexattrs, attrnum);
 
 				if (isPK && i < indexDesc->rd_index->indnkeyatts)
-					pkindexattrs = bms_add_member(pkindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					pkindexattrs = bms_add_member(pkindexattrs, attrnum);
 
 				if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
-					idindexattrs = bms_add_member(idindexattrs,
-												  attrnum - FirstLowInvalidHeapAttributeNumber);
+					idindexattrs = bms_add_member(idindexattrs, attrnum);
 			}
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos(indexExpressions, 1, attrs);
+		pull_varattnos(indexExpressions, 1, exprattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos(indexPredicate, 1, attrs);
+		pull_varattnos(indexPredicate, 1, exprattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -5503,12 +5515,24 @@ restart:
 		bms_free(uindexattrs);
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
-		bms_free(hotblockingattrs);
-		bms_free(summarizedattrs);
+		bms_free(idx_attrs);
+		bms_free(expr_attrs);
+		bms_free(sum_attrs);
 
 		goto restart;
 	}
 
+	/*
+	 * HOT-blocking attributes should include all columns that are part of the
+	 * index except attributes only referenced in expressions, including
+	 * expressions used to form partial indexes.  So, we need to remove the
+	 * expression-only attributes from the HOT-blocking columns bitmap as
+	 * those will be checked separately.
+	 */
+	expr_attrs = bms_del_members(expr_attrs, idx_attrs);
+	idx_attrs = bms_add_members(idx_attrs, expr_attrs);
+	expr_attrs = bms_add_members(expr_attrs, sum_attrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5545,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_expressionattr);
+	relation->rd_expressionattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5533,8 +5559,9 @@ restart:
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_pkattr = bms_copy(pkindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
-	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
-	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_hotblockingattr = bms_copy(idx_attrs);
+	relation->rd_summarizedattr = bms_copy(sum_attrs);
+	relation->rd_expressionattr = bms_copy(expr_attrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5548,9 +5575,11 @@ restart:
 		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 			return idindexattrs;
 		case INDEX_ATTR_BITMAP_HOT_BLOCKING:
-			return hotblockingattrs;
+			return idx_attrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
-			return summarizedattrs;
+			return sum_attrs;
+		case INDEX_ATTR_BITMAP_EXPRESSION:
+			return expr_attrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 6176741d20b..20adc510626 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3045,7 +3045,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost", "expression_checks",
 					  "effective_io_concurrency", "maintenance_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index e60d34dad25..d1c9135b06d 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -22,6 +22,7 @@
 #include "access/table.h"		/* for backward compatibility */
 #include "access/tableam.h"
 #include "commands/vacuum.h"
+#include "executor/executor.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -324,8 +325,7 @@ 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,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 TM_FailureData *tmfd, UpdateContext *updateCxt);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -360,7 +360,7 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+							   HeapTuple tup, UpdateContext *updateCxt);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index e16bf025692..5a9685eb83a 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -119,6 +119,31 @@ typedef enum TU_UpdateIndexes
 	TU_Summarizing,
 } TU_UpdateIndexes;
 
+typedef struct ResultRelInfo ResultRelInfo;
+typedef struct EState EState;
+
+/*
+ * Data specific to processing UPDATE operations.
+ *
+ * When table_tuple_update is called some storage managers, notably heapam,
+ * can at times avoid index updates.  In the heapam this is known as a HOT
+ * update.  This struct is used to provide the state required to test for
+ * HOT updates and to communicate that decision on to the index AMs.
+ */
+typedef struct UpdateContext
+{
+	TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
+	ResultRelInfo *rri;			/* ResultRelInfo for the updated table. */
+	EState	   *estate;			/* EState used within the update. */
+	bool		crossPartUpdate;	/* Was it a cross-partition update? */
+
+	/*
+	 * Lock mode to acquire on the latest tuple version before performing
+	 * EvalPlanQual on it
+	 */
+	LockTupleMode lockmode;
+} UpdateContext;
+
 /*
  * When table_tuple_update, table_tuple_delete, or table_tuple_lock fail
  * because the target tuple is already outdated, they fill in this struct to
@@ -548,8 +573,7 @@ typedef struct TableAmRoutine
 								 Snapshot crosscheck,
 								 bool wait,
 								 TM_FailureData *tmfd,
-								 LockTupleMode *lockmode,
-								 TU_UpdateIndexes *update_indexes);
+								 UpdateContext *updateCxt);
 
 	/* see table_tuple_lock() for reference about parameters */
 	TM_Result	(*tuple_lock) (Relation rel,
@@ -1501,13 +1525,11 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
 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,
-				   TU_UpdateIndexes *update_indexes)
+				   bool wait, TM_FailureData *tmfd, UpdateContext *updateCxt)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, updateCxt);
 }
 
 /*
@@ -2010,7 +2032,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  TU_UpdateIndexes *update_indexes);
+									  UpdateContext *updateCxt);
 
 
 /* ----------------------------------------------------------------------------
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 4daa8bef5ee..fdbf47f607b 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildExpressionIndexInfo(Relation index, IndexInfo *indexInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3248e78cd28..412c4b3b70f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -734,7 +734,7 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 /*
  * prototypes from functions in execIndexing.c
  */
-extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
+extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative, bool update);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
@@ -752,6 +752,12 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool ExecExprIndexesRequireUpdates(Relation relation,
+										  ResultRelInfo *resultRelInfo,
+										  Bitmapset *modifiedAttrs,
+										  EState *estate,
+										  TupleTableSlot *old_tts,
+										  TupleTableSlot *new_tts);
 
 /*
  * prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a36653c37f9..e529587a6f1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -178,11 +178,20 @@ typedef struct IndexInfo
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes referenced by expressions, or NULL if none */
+	Bitmapset  *ii_ExpressionsAttrs;
+
+	/* index attribute length */
+	uint16		ii_IndexAttrLen[INDEX_MAX_KEYS];
+	/* is the index attribute by-value */
+	Bitmapset  *ii_IndexAttrByVal;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate, or NULL if none */
+	Bitmapset  *ii_PredicateAttrs;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -206,6 +215,10 @@ typedef struct IndexInfo
 	bool		ii_CheckedUnchanged;
 	/* aminsert hint, cached for retail inserts */
 	bool		ii_IndexUnchanged;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
@@ -386,6 +399,8 @@ typedef struct ProjectionInfo
 	ExprState	pi_state;
 	/* expression context in which to evaluate expression */
 	ExprContext *pi_exprContext;
+	/* the set of modified columns (attributes) */
+	Bitmapset  *pi_modifiedCols;
 } ProjectionInfo;
 
 /* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 21990436373..a3054a7da88 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_expressionattr;	/* indexed cols referenced by expressions */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
@@ -348,6 +349,7 @@ typedef struct StdRdOptions
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
 	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	bool		expression_checks;	/* use expression to checks for changes */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
@@ -409,6 +411,14 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * RelationGetExpressionChecks
+ *		Returns the relation's expression_checks reloption setting.
+ */
+#define RelationGetExpressionChecks(relation) \
+	((relation)->rd_options ? \
+	 ((StdRdOptions *) (relation)->rd_options)->expression_checks : true)
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..4b312bc8d06 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_EXPRESSION,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..7d22befc34a
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,1048 @@
+-- Create a function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN,
+    has_indexes BOOLEAN,
+    index_count INT,
+    fillfactor INT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+
+    -- We need to wait for statistics to update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Construct qualified name
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+
+    -- Get the OID using regclass
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+        RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+
+    -- Get current transaction stats
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    -- Combine stats
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+        p_table_name::TEXT,
+        v_updates::BIGINT as total_updates,
+        v_hot_updates::BIGINT as hot_updates,
+        CASE
+            WHEN v_updates > 0 THEN
+                ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+            ELSE 0
+        END as hot_update_percentage,
+        (v_hot_updates = expected)::BOOLEAN as matches_expected,
+        (EXISTS (
+            SELECT 1 FROM pg_index WHERE indrelid = v_relid
+        ))::BOOLEAN as has_indexes,
+        (
+            SELECT COUNT(*)::INT
+            FROM pg_index
+            WHERE indrelid = v_relid
+        ) as index_count,
+        COALESCE(
+            (
+                SELECT (regexp_match(array_to_string(reloptions, ','), 'fillfactor=(\d+)'))[1]::int
+                FROM pg_class
+                WHERE oid = v_relid
+            ),
+            100
+        ) as fillfactor;
+END;
+$$;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (docs->>'name').  That means that the indexed
+-- attributes are 'id' and 'docs'.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- Disable expression checks.
+ALTER TABLE t SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+                           reloptions                           
+----------------------------------------------------------------
+ {autovacuum_enabled=off,fillfactor=70,expression_checks=false}
+(1 row)
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update t set docs='{"name": "john", "data": "something else"}' where id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Re-enable expression checks.
+ALTER TABLE t SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+                          reloptions                           
+---------------------------------------------------------------
+ {autovacuum_enabled=off,fillfactor=70,expression_checks=true}
+(1 row)
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           1 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+UPDATE t SET docs='{"name": "smith", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           1 |                 33.33 | t                | t           |           2 |         70
+(1 row)
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+UPDATE t SET docs='{"name": "smith", "data": "some more data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           2 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the docs column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the docs column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the docs column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           3 |         70
+(1 row)
+
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE t (docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO t (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO t (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX t_idx_a ON t ((docs->>'a'));
+CREATE INDEX t_idx_b ON t ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           1 |                100.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           1 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           1 |                 33.33 | t                | t           |           2 |         70
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           1 |                 25.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             5 |           1 |                 20.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs ->> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to ensure that HOT updates are not performed when multiple indexed
+-- attributes are updated.
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(a);
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (1, -1);
+-- Both are updated, the second is an expression index with an unchanged
+-- index value.  The change to the index on a should prevent HOT updates.
+UPDATE t SET a = 2, b = 1 WHERE a = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           2 |         70
+(1 row)
+
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to check the expression_checks reloption behavior.
+--
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(abs(a)) WHERE abs(a) > 10;
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (-1, -1), (-2, -2), (-3, -3), (-4, -4);
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Disable expression checks on indexes and partial index predicates.
+ALTER TABLE t SET (expression_checks = false);
+-- Before and after values of a are outside the predicate of the index and
+-- the indexed value of b hasn't changed however we've disabled expression
+-- checks so this should not be a HOT update.
+-- (-1, -1) -> (-5, -1)
+UPDATE t SET a = -5, b = -1 WHERE a = -1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Enable expression checks on indexes, but not on predicates yet.
+ALTER TABLE t SET (expression_checks = true);
+-- The indexed value of b hasn't changed, this should be a HOT update.
+-- (-5, -1) -> (-5, 1)
+UPDATE t SET b = 1 WHERE a = -5;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           1 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+-- Now that we're not checking the predicate of the partial index, this
+-- update of a from -5 to 5 should be HOT because we should ignore the
+-- predicate and check the expression and find it unchanged.
+-- (-5, 1) -> (5, 1)
+UPDATE t SET a = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           2 |                 66.67 | t                | t           |           2 |         70
+(1 row)
+
+-- This update meets the critera for the partial index and should not
+-- be HOT.  Let's make sure of that and check the index as well.
+-- (-4, -4) -> (-11, -4)
+UPDATE t SET a = -11 WHERE a = -4;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           2 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+  a  | b  
+-----+----
+ -11 | -4
+(1 row)
+
+-- (-11, -4) -> (11, -4)
+UPDATE t SET a = 11 WHERE a = -11;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             5 |           3 |                 60.00 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+ a  | b  
+----+----
+ 11 | -4
+(1 row)
+
+-- (11, -4) -> (-4, -4)
+UPDATE t SET a = -4 WHERE a = 11;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             6 |           3 |                 50.00 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+ a | b 
+---+---
+(0 rows)
+
+-- This update of a from 5 to -1 is HOT despite that attribute
+-- being indexed because the before and after values for the
+-- partial index predicate are outside the index definition.
+-- (5, 1) -> (-1, 1)
+UPDATE t SET a = -1 WHERE a = 5;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             7 |           4 |                 57.14 | t                | t           |           2 |         70
+(1 row)
+
+-- This update of a from -2 to -1 with predicate checks enabled should be
+-- HOT because the before/after values of a are both outside the predicate
+-- of the partial index.
+-- (-1, 1) -> (-2, 1)
+UPDATE t SET a = -2 WHERE a = -1;
+SELECT * FROM check_hot_updates(5);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             8 |           5 |                 62.50 | t                | t           |           2 |         70
+(1 row)
+
+-- The indexed value for b isn't changing, this should be HOT.
+-- (-2, -2) -> (-2, 2)
+UPDATE t SET b = 2 WHERE b = -2;
+SELECT * FROM check_hot_updates(6);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             9 |           6 |                 66.67 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT abs(b) FROM t;
+    QUERY PLAN    
+------------------
+ Seq Scan on t
+   Disabled: true
+(2 rows)
+
+SELECT abs(b) FROM t;
+ abs 
+-----
+   3
+   4
+   1
+   2
+(4 rows)
+
+-- Before and after values for a are outside the predicate of the index,
+-- and because we're checking this should be HOT.
+-- (-2, 1) -> (5, 1)
+-- (-2, -2) -> (5, -2)
+UPDATE t SET a = 5 WHERE a = -2;
+SELECT * FROM check_hot_updates(8);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |            11 |           8 |                 72.73 | t                | t           |           2 |         70
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+(1 row)
+
+SELECT * FROM t WHERE abs(a) > 10;
+ a | b 
+---+---
+(0 rows)
+
+SELECT * FROM t;
+ a  | b  
+----+----
+ -3 | -3
+ -4 | -4
+  5 |  1
+  5 |  2
+(4 rows)
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The tests here examines the behavior of HOT updates when the relation
+-- has a JSONB column with an index on the field 'a' and the partial index
+-- expression on a different JSONB field 'b'.
+CREATE TABLE t(docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'a')) WHERE (docs->'b')::integer = 1;
+INSERT INTO t VALUES ('{"a": 1, "b": 1}');
+EXPLAIN (COSTS OFF) SELECT * FROM t;
+  QUERY PLAN   
+---------------
+ Seq Scan on t
+(1 row)
+
+SELECT * FROM t;
+       docs       
+------------------
+ {"a": 1, "b": 1}
+(1 row)
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::integer = 1;
+            QUERY PLAN            
+----------------------------------
+ Index Scan using t_docs_idx on t
+(1 row)
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+       docs       
+------------------
+ {"a": 1, "b": 1}
+(1 row)
+
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             0 |           0 |                     0 | t                | t           |           1 |         70
+(1 row)
+
+UPDATE t SET docs='{"a": 1, "b": 0}';
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+ docs 
+------
+(0 rows)
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests for multi-column indexes
+--
+CREATE TABLE t(id INT, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t(id, (docs->'a'));
+INSERT INTO t VALUES (1, '{"a": 1, "b": 1}');
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using t_docs_idx on t
+   Index Cond: (id > 0)
+   Filter: (((docs -> 'a'::text))::integer > 0)
+(3 rows)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  1 | {"a": 1, "b": 1}
+(1 row)
+
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             0 |           0 |                     0 | t                | t           |           1 |         70
+(1 row)
+
+-- Changing the id attribute which is an indexed attribute should
+-- prevent HOT updates.
+UPDATE t SET id = 2;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             1 |           0 |                  0.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  2 | {"a": 1, "b": 1}
+(1 row)
+
+-- Changing the docs->'a' field in the indexed attribute 'docs'
+-- should prevent HOT updates.
+UPDATE t SET docs='{"a": -2, "b": 1}';
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             2 |           0 |                  0.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+ id |       docs        
+----+-------------------
+  2 | {"a": -2, "b": 1}
+(1 row)
+
+-- Leaving the docs->'a' attribute unchanged means that the expression
+-- is unchanged and because the 'id' attribute isn't in the modified
+-- set the indexed tuple is unchanged, this can go HOT.
+UPDATE t SET docs='{"a": -2, "b": 2}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             3 |           1 |                 33.33 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+ id |       docs        
+----+-------------------
+  2 | {"a": -2, "b": 2}
+(1 row)
+
+-- Here we change the 'id' attribute and the 'docs' attribute setting
+-- the expression docs->'a' to a new value, this cannot be a HOT update.
+UPDATE t SET id = 3, docs='{"a": 3, "b": 3}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ t          |             4 |           1 |                 25.00 | t                | t           |           1 |         70
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  3 | {"a": 3, "b": 3}
+(1 row)
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE t;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT * FROM check_hot_updates(0, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             1 |           0 |                  0.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             2 |           1 |                 50.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             3 |           1 |                 33.33 | t                | t           |           3 |        100
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ users      |             4 |           1 |                 25.00 | t                | t           |           3 |        100
+(1 row)
+
+DROP TABLE users;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT * FROM check_hot_updates(0, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ events     |             1 |           0 |                  0.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ events     |             2 |           0 |                  0.00 | t                | t           |           2 |        100
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ events     |             3 |           1 |                 33.33 | t                | t           |           2 |        100
+(1 row)
+
+DROP TABLE events;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+SELECT * FROM ex;
+ id |     att1     |      att2       | att3 | att4 
+----+--------------+-----------------+------+------
+  1 | {"data": []} | nothing special |      | 
+(1 row)
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             1 |           1 |                100.00 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |     att1     |      att2      | att3 |   att4   
+----+--------------+----------------+------+----------
+  1 | {"data": []} | special indeed |      | whatever
+(1 row)
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             2 |           1 |                 50.00 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        |        att2         | att3 |   att4   
+----+-------------------+---------------------+------+----------
+  1 | {"data": "howdy"} | special, so special |      | whatever
+(1 row)
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT * FROM check_hot_updates(2, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             3 |           2 |                 66.67 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | a    | b    | c
+(1 row)
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             4 |           3 |                 75.00 | t                | t           |           9 |         60
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | d    | e    | c
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             1 |           1 |                100.00 | t                | t           |           2 |         60
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT * FROM check_hot_updates(2, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             2 |           2 |                100.00 | t                | t           |           2 |         60
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             3 |           3 |                100.00 | t                | t           |           2 |         60
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT * FROM check_hot_updates(4, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ ex         |             4 |           3 |                 75.00 | f                | t           |           2 |         60
+(1 row)
+
+DROP TABLE ex;
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             1 |           0 |                  0.00 | t                | t           |           1 |        100
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             3 |           0 |                  0.00 | t                | t           |           1 |        100
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             4 |           0 |                  0.00 | t                | t           |           1 |        100
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected | has_indexes | index_count | fillfactor 
+------------+---------------+-------------+-----------------------+------------------+-------------+-------------+------------
+ my_table   |             5 |           1 |                 20.00 | f                | t           |           1 |        100
+(1 row)
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+             QUERY PLAN              
+-------------------------------------
+ Seq Scan on my_table
+   Filter: (abs_val(custom_val) = 6)
+(2 rows)
+
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fbffc67ae60..94b50fd8c7a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -68,6 +68,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
 
+# ----------
+# Another group of parallel tests
+# ----------
+test: heap_hot_updates
+
 # ----------
 # Additional BRIN tests
 # ----------
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..7016e9eabd0
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,718 @@
+-- Create a function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN,
+    has_indexes BOOLEAN,
+    index_count INT,
+    fillfactor INT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+
+    -- We need to wait for statistics to update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Construct qualified name
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+
+    -- Get the OID using regclass
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+        RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+
+    -- Get current transaction stats
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    -- Combine stats
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+        p_table_name::TEXT,
+        v_updates::BIGINT as total_updates,
+        v_hot_updates::BIGINT as hot_updates,
+        CASE
+            WHEN v_updates > 0 THEN
+                ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+            ELSE 0
+        END as hot_update_percentage,
+        (v_hot_updates = expected)::BOOLEAN as matches_expected,
+        (EXISTS (
+            SELECT 1 FROM pg_index WHERE indrelid = v_relid
+        ))::BOOLEAN as has_indexes,
+        (
+            SELECT COUNT(*)::INT
+            FROM pg_index
+            WHERE indrelid = v_relid
+        ) as index_count,
+        COALESCE(
+            (
+                SELECT (regexp_match(array_to_string(reloptions, ','), 'fillfactor=(\d+)'))[1]::int
+                FROM pg_class
+                WHERE oid = v_relid
+            ),
+            100
+        ) as fillfactor;
+END;
+$$;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table will have two columns and two indexes, one on the primary key
+-- id and one on the expression (docs->>'name').  That means that the indexed
+-- attributes are 'id' and 'docs'.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- Disable expression checks.
+ALTER TABLE t SET (expression_checks = false);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+
+-- While the indexed attribute "name" is unchanged we've disabled expression
+-- checks so this update should not go HOT as the system can't determine if
+-- the indexed attribute has changed without evaluating the expression.
+update t set docs='{"name": "john", "data": "something else"}' where id=1;
+SELECT * FROM check_hot_updates(0);
+
+-- Re-enable expression checks.
+ALTER TABLE t SET (expression_checks = true);
+SELECT reloptions FROM pg_class WHERE relname = 't';
+
+-- The indexed attribute "name" with value "john" is unchanged, expect a HOT update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+
+-- The following update changes the indexed attribute "name", this should not be a HOT update.
+UPDATE t SET docs='{"name": "smith", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(1);
+
+-- Now, this update does not change the indexed attribute "name" from "smith", this should be HOT.
+UPDATE t SET docs='{"name": "smith", "data": "some more data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table is the same as the previous one but it has a third index.  The
+-- index 'colindex' isn't an expression index, it indexes the entire value
+-- in the docs column.  There are still only two indexed attributes for this
+-- relation, the same two as before.  The presence of an index on the entire
+-- value of the docs column should prevent HOT updates for any updates to any
+-- portion of JSONB content in that column.
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->>'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- This update doesn't change the value of the expression index, but it does
+-- change the content of the docs column and so should not be HOT because the
+-- indexed value changed as a result of the update.
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The table has one column docs and two indexes.  They are both expression
+-- indexes referencing the same column attribute (docs) but one is a partial
+-- index.
+CREATE TABLE t (docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO t (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO t (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX t_idx_a ON t ((docs->>'a'));
+CREATE INDEX t_idx_b ON t ((docs->>'b')) WHERE (docs->>'b')::numeric > 9;
+
+-- We're using BTREE indexes and for this test we want to make sure that they remain
+-- in sync with changes to our relation.  Force the choice of index scans below so
+-- that we know we're checking the index's understanding of what values should be
+-- in the index or not.
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->>'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+
+-- This update changes both 'a' and 'b' to new values that require index updates,
+-- this cannot use the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->>'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->>'b')::numeric = 12;
+SELECT * FROM check_hot_updates(1);
+-- Let's check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->>'b')::numeric > 9 AND (docs->>'b')::numeric < 100;
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to ensure that HOT updates are not performed when multiple indexed
+-- attributes are updated.
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(a);
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (1, -1);
+
+-- Both are updated, the second is an expression index with an unchanged
+-- index value.  The change to the index on a should prevent HOT updates.
+UPDATE t SET a = 2, b = 1 WHERE a = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests to check the expression_checks reloption behavior.
+--
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(abs(a)) WHERE abs(a) > 10;
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (-1, -1), (-2, -2), (-3, -3), (-4, -4);
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Disable expression checks on indexes and partial index predicates.
+ALTER TABLE t SET (expression_checks = false);
+
+-- Before and after values of a are outside the predicate of the index and
+-- the indexed value of b hasn't changed however we've disabled expression
+-- checks so this should not be a HOT update.
+-- (-1, -1) -> (-5, -1)
+UPDATE t SET a = -5, b = -1 WHERE a = -1;
+SELECT * FROM check_hot_updates(0);
+
+-- Enable expression checks on indexes, but not on predicates yet.
+ALTER TABLE t SET (expression_checks = true);
+
+-- The indexed value of b hasn't changed, this should be a HOT update.
+-- (-5, -1) -> (-5, 1)
+UPDATE t SET b = 1 WHERE a = -5;
+SELECT * FROM check_hot_updates(1);
+
+-- Now that we're not checking the predicate of the partial index, this
+-- update of a from -5 to 5 should be HOT because we should ignore the
+-- predicate and check the expression and find it unchanged.
+-- (-5, 1) -> (5, 1)
+UPDATE t SET a = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(2);
+
+-- This update meets the critera for the partial index and should not
+-- be HOT.  Let's make sure of that and check the index as well.
+-- (-4, -4) -> (-11, -4)
+UPDATE t SET a = -11 WHERE a = -4;
+SELECT * FROM check_hot_updates(2);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+-- (-11, -4) -> (11, -4)
+UPDATE t SET a = 11 WHERE a = -11;
+SELECT * FROM check_hot_updates(3);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+-- (11, -4) -> (-4, -4)
+UPDATE t SET a = -4 WHERE a = 11;
+SELECT * FROM check_hot_updates(3);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+-- This update of a from 5 to -1 is HOT despite that attribute
+-- being indexed because the before and after values for the
+-- partial index predicate are outside the index definition.
+-- (5, 1) -> (-1, 1)
+UPDATE t SET a = -1 WHERE a = 5;
+SELECT * FROM check_hot_updates(4);
+
+-- This update of a from -2 to -1 with predicate checks enabled should be
+-- HOT because the before/after values of a are both outside the predicate
+-- of the partial index.
+-- (-1, 1) -> (-2, 1)
+UPDATE t SET a = -2 WHERE a = -1;
+SELECT * FROM check_hot_updates(5);
+
+-- The indexed value for b isn't changing, this should be HOT.
+-- (-2, -2) -> (-2, 2)
+UPDATE t SET b = 2 WHERE b = -2;
+SELECT * FROM check_hot_updates(6);
+EXPLAIN (COSTS OFF) SELECT abs(b) FROM t;
+SELECT abs(b) FROM t;
+
+-- Before and after values for a are outside the predicate of the index,
+-- and because we're checking this should be HOT.
+-- (-2, 1) -> (5, 1)
+-- (-2, -2) -> (5, -2)
+UPDATE t SET a = 5 WHERE a = -2;
+SELECT * FROM check_hot_updates(8);
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10;
+SELECT * FROM t WHERE abs(a) > 10;
+
+SELECT * FROM t;
+
+DROP TABLE t;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- The tests here examines the behavior of HOT updates when the relation
+-- has a JSONB column with an index on the field 'a' and the partial index
+-- expression on a different JSONB field 'b'.
+CREATE TABLE t(docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'a')) WHERE (docs->'b')::integer = 1;
+INSERT INTO t VALUES ('{"a": 1, "b": 1}');
+
+EXPLAIN (COSTS OFF) SELECT * FROM t;
+SELECT * FROM t;
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::integer = 1;
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+
+SELECT * FROM check_hot_updates(0);
+
+UPDATE t SET docs='{"a": 1, "b": 0}';
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE t;
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Tests for multi-column indexes
+--
+CREATE TABLE t(id INT, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t(id, (docs->'a'));
+INSERT INTO t VALUES (1, '{"a": 1, "b": 1}');
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+SELECT * FROM check_hot_updates(0);
+
+-- Changing the id attribute which is an indexed attribute should
+-- prevent HOT updates.
+UPDATE t SET id = 2;
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+-- Changing the docs->'a' field in the indexed attribute 'docs'
+-- should prevent HOT updates.
+UPDATE t SET docs='{"a": -2, "b": 1}';
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+
+-- Leaving the docs->'a' attribute unchanged means that the expression
+-- is unchanged and because the 'id' attribute isn't in the modified
+-- set the indexed tuple is unchanged, this can go HOT.
+UPDATE t SET docs='{"a": -2, "b": 2}';
+SELECT * FROM check_hot_updates(1);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+
+-- Here we change the 'id' attribute and the 'docs' attribute setting
+-- the expression docs->'a' to a new value, this cannot be a HOT update.
+UPDATE t SET id = 3, docs='{"a": 3, "b": 3}';
+SELECT * FROM check_hot_updates(1);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE t;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This table has a single column 'email' and a unique constraint on it that
+-- should preclude HOT updates.
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT * FROM check_hot_updates(0, 'users');
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT * FROM check_hot_updates(1, 'users');
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+
+DROP TABLE users;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- Another test of constraints spoiling HOT updates, this time with a range.
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'events');
+
+DROP TABLE events;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that only modified summarizing indexes are updated, not
+-- all of them.
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+
+SELECT * FROM ex;
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT * FROM check_hot_updates(1, 'ex');
+SELECT * FROM ex;
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(1, 'ex');
+SELECT * FROM ex;
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT * FROM check_hot_updates(2, 'ex');
+SELECT * FROM ex;
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT * FROM check_hot_updates(3, 'ex');
+SELECT * FROM ex;
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- A test to ensure that summarizing indexes are not updated when they don't
+-- change, but are updated when they do while not prefent HOT updates.
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT * FROM check_hot_updates(1, 'ex');
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT * FROM check_hot_updates(2, 'ex');
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(3, 'ex');
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT * FROM check_hot_updates(4, 'ex');
+
+DROP TABLE ex;
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- This test is for a table with a custom type and a custom operators on
+-- the BTREE index.  The question is, when comparing values for equality
+-- to determine if there are changes on the index or not... shouldn't we
+-- be using the custom operators?
+
+-- Create a type
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT pg_stat_get_xact_tuples_hot_updated('my_table'::regclass);
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Query using the index
+EXPLAIN (COSTS OFF) SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
+
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37f26f6c6b7..91e0beb2874 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2981,6 +2981,7 @@ TSVectorStat
 TState
 TStatus
 TStoreState
+UpdateContext
 TU_UpdateIndexes
 TXNEntryFile
 TYPCATEGORY
@@ -3168,7 +3169,6 @@ UniqueState
 UnlistenStmt
 UnresolvedTup
 UnresolvedTupData
-UpdateContext
 UpdateStmt
 UpgradeTask
 UpgradeTaskProcessCB
-- 
2.49.0

#26Nathan Bossart
nathandbossart@gmail.com
In reply to: Greg Burd (#25)
Re: Expanding HOT updates for expression and partial indexes

On Tue, Oct 07, 2025 at 05:36:11PM -0400, Greg Burd wrote:

I put the patch aside for a while, then this past week at PGConf.dev/NYC
I heard interest from a few people (Jeff Davis, Nathan Bossart) who
encouraged me to move the code executing the expressions to just before
acquiring the lock but after pinning the buffer. The theory being that
my new code using the old/new tts to form and test the index tuples
resulting from executing expressions was using the resultsRelInfo struct
created during plan execution, not the information found on the page,
and so was safe without the lock.

An open question (at least from me) is whether this is safe. I'm not
familiar enough with this area of code yet to confidently determine that.

After reviewing how updates work in the executor, I discovered that
during execution the new tuple slot is populated with the information
from ExecBuildUpdateProjection() and the old tuple, but that most
importantly for this use case that function created a bitmap of the
modified columns (the columns specified in the update). This bitmap
isn't the same as the one produced by HeapDetermineColumnsInfo() as the
latter excludes attributes that are not changed after testing equality
with the helper function heap_attr_equals() where as the former will
include attributes that appear in the update but are the same value as
before. This, happily, is immaterial for the purposes of my function
ExecExprIndexesRequireUpdates() which simply needs to check to see if
index tuples generated are unchanged. So I had all I needed to run the
checks ahead of acquiring the lock on the buffer.

Nice.

There is much room for improvement, and your suggestions are welcome.

A general and predictable suggestion is to find ways to break this into
smaller pieces. As-is, this patch would take me an enormous amount of time
to review in any depth. If we can break off some smaller pieces that we
can scrutinize and commit independently, we can start making forward
progress sooner. The UpdateContext and reloption stuff are examples of
things that might be possible to split into independent patches.

I'll find time to quantify the benefit of this patch for the targeted
use cases and to ensure that all other cases see no regressions.

Looking forward to these results. This should also help us decide whether
to set expression_checks by default.

I added a reloption "expression_checks" to disable this new code path.
Good idea or bad precedent?

If there are cases where the added overhead outweighs the benefits (which
seems like it must be true some of the time), then I think we must have a
way to opt-out (or maybe even opt-in). In fact, I'd advise adding a GUC to
complement the reloption so that users can configure it at higher levels.

In execIndexing I special case for IsolationIsSerializable() and I can't
remember why now but I do recall one isolation test failing... I'll
check on this and get back to the thread. Or maybe you know why that

I didn't follow this.

I'd like not to build, then rebuild index tuples for these expressions
but I can't think of a way to do that without a palloc(), this is
avoided today.

Is the avoidance of palloc() a strict rule? Is this discussed in the code
anywhere?

--
nathan

#27Jeff Davis
pgsql@j-davis.com
In reply to: Nathan Bossart (#26)
Re: Expanding HOT updates for expression and partial indexes

On Wed, 2025-10-08 at 15:48 -0500, Nathan Bossart wrote:

The theory being that
my new code using the old/new tts to form and test the index tuples
resulting from executing expressions was using the resultsRelInfo
struct
created during plan execution, not the information found on the
page,
and so was safe without the lock.

An open question (at least from me) is whether this is safe.  I'm not
familiar enough with this area of code yet to confidently determine
that.

The optimization requires that the expression evaluates to the same
thing on the old and new tuples. That determination doesn't have
anything to do with a lock on the buffer, so long as the old tuple
isn't pruned away or something. And clearly it won't be pruned, because
we're in the process of updating it, so we have a snapshot that can see
it.

There might be subtleties in other parts of the proposal, but the above
determination can be made safely without a buffer lock.

I added a reloption "expression_checks" to disable this new code
path.
Good idea or bad precedent?

If there are cases where the added overhead outweighs the benefits
(which
seems like it must be true some of the time), then I think we must
have a
way to opt-out (or maybe even opt-in).  In fact, I'd advise adding a
GUC to
complement the reloption so that users can configure it at higher
levels.

I'll push back against this. For now I'm fine with developer options to
make testing easier, but we should find a way to make this work well
without tuning.

Regards,
Jeff Davis

#28Jeff Davis
pgsql@j-davis.com
In reply to: Greg Burd (#25)
Re: Expanding HOT updates for expression and partial indexes

On Tue, 2025-10-07 at 17:36 -0400, Greg Burd wrote:

After reviewing how updates work in the executor, I discovered that
during execution the new tuple slot is populated with the information
from ExecBuildUpdateProjection() and the old tuple, but that most
importantly for this use case that function created a bitmap of the
modified columns (the columns specified in the update).  This bitmap
isn't the same as the one produced by HeapDetermineColumnsInfo() as
the
latter excludes attributes that are not changed after testing
equality
with the helper function heap_attr_equals() where as the former will
include attributes that appear in the update but are the same value
as
before.  This, happily, is immaterial for the purposes of my function
ExecExprIndexesRequireUpdates() which simply needs to check to see if
index tuples generated are unchanged.  So I had all I needed to run
the
checks ahead of acquiring the lock on the buffer.

You're still calling ExecExprIndexesRequireUpdates() from within
heap_update(). Can't you do that inside of ExecUpdatePrologue() or
thereabouts?

Regards,
Jeff Davis

#29Greg Burd
greg@burd.me
In reply to: Nathan Bossart (#26)
Re: Expanding HOT updates for expression and partial indexes

On Oct 8, 2025, at 4:48 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Tue, Oct 07, 2025 at 05:36:11PM -0400, Greg Burd wrote:

I put the patch aside for a while, then this past week at PGConf.dev/NYC
I heard interest from a few people (Jeff Davis, Nathan Bossart) who
encouraged me to move the code executing the expressions to just before
acquiring the lock but after pinning the buffer. The theory being that
my new code using the old/new tts to form and test the index tuples
resulting from executing expressions was using the resultsRelInfo struct
created during plan execution, not the information found on the page,
and so was safe without the lock.

Thanks for taking a look Nathan.

An open question (at least from me) is whether this is safe. I'm not
familiar enough with this area of code yet to confidently determine that.

My read is that it is safe because we're testing the content of two
TupleTableSlots both formed in the executor. The function uses only
that information and doesn't reference data on the page at all.

After reviewing how updates work in the executor, I discovered that
during execution the new tuple slot is populated with the information
from ExecBuildUpdateProjection() and the old tuple, but that most
importantly for this use case that function created a bitmap of the
modified columns (the columns specified in the update). This bitmap
isn't the same as the one produced by HeapDetermineColumnsInfo() as the
latter excludes attributes that are not changed after testing equality
with the helper function heap_attr_equals() where as the former will
include attributes that appear in the update but are the same value as
before. This, happily, is immaterial for the purposes of my function
ExecExprIndexesRequireUpdates() which simply needs to check to see if
index tuples generated are unchanged. So I had all I needed to run the
checks ahead of acquiring the lock on the buffer.

Nice.

Handy indeed. I'm not at all a fan of increasing the size of a plan node
but it's only by a little... and for a good cause.

There is much room for improvement, and your suggestions are welcome.

A general and predictable suggestion is to find ways to break this into
smaller pieces. As-is, this patch would take me an enormous amount of time
to review in any depth. If we can break off some smaller pieces that we
can scrutinize and commit independently, we can start making forward
progress sooner. The UpdateContext and reloption stuff are examples of
things that might be possible to split into independent patches.

Fair, I'll try.

I'll find time to quantify the benefit of this patch for the targeted
use cases and to ensure that all other cases see no regressions.

Looking forward to these results. This should also help us decide whether
to set expression_checks by default.

In past my test results were very positive for cases where this helped avoid
heap and index bloat and almost immeasurably small even for cases where we
were doing the work to test but ultimately unable to take the HOT path.

This will require new tests as the code has changed quite a bit.

I added a reloption "expression_checks" to disable this new code path.
Good idea or bad precedent?

If there are cases where the added overhead outweighs the benefits (which
seems like it must be true some of the time), then I think we must have a
way to opt-out (or maybe even opt-in). In fact, I'd advise adding a GUC to
complement the reloption so that users can configure it at higher levels.

This evolved from a GUC to a reloption and I'd rather it go away entirely.
I hear your concern, but I've yet to measure a perceptable impact and I'll
try hard to keep it that way as this matures. Assuming that's the case,
I'd like to eliminate the potentially confusing tuning knob.

In execIndexing I special case for IsolationIsSerializable() and I can't
remember why now but I do recall one isolation test failing... I'll
check on this and get back to the thread. Or maybe you know why that

I didn't follow this.

More later if/when I can reproduce it and understand it better myself.

I'd like not to build, then rebuild index tuples for these expressions
but I can't think of a way to do that without a palloc(), this is
avoided today.

Is the avoidance of palloc() a strict rule? Is this discussed in the code
anywhere?

Not that I know of, just my paranoid self trying to avoid it on a path that
didn't have it before.

--
nathan

best.

-greg

#30Greg Burd
greg@burd.me
In reply to: Jeff Davis (#27)
Re: Expanding HOT updates for expression and partial indexes

On Oct 9, 2025, at 3:08 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Wed, 2025-10-08 at 15:48 -0500, Nathan Bossart wrote:

The theory being that
my new code using the old/new tts to form and test the index tuples
resulting from executing expressions was using the resultsRelInfo
struct
created during plan execution, not the information found on the
page,
and so was safe without the lock.

An open question (at least from me) is whether this is safe. I'm not
familiar enough with this area of code yet to confidently determine
that.

Hey Jeff,

Thanks for the nudge at PGConf.dev in NYC and for the follow-up here.

The optimization requires that the expression evaluates to the same
thing on the old and new tuples. That determination doesn't have
anything to do with a lock on the buffer, so long as the old tuple
isn't pruned away or something. And clearly it won't be pruned, because
we're in the process of updating it, so we have a snapshot that can see
it.

Right, I test that the expression on the index evaluates to the same
value when forming an index tuple for old/new slots.

There might be subtleties in other parts of the proposal, but the above
determination can be made safely without a buffer lock.

I added a reloption "expression_checks" to disable this new code
path.
Good idea or bad precedent?

If there are cases where the added overhead outweighs the benefits
(which
seems like it must be true some of the time), then I think we must
have a
way to opt-out (or maybe even opt-in). In fact, I'd advise adding a
GUC to
complement the reloption so that users can configure it at higher
levels.

I'll push back against this. For now I'm fine with developer options to
make testing easier, but we should find a way to make this work well
without tuning.

I'm aligned with this, the reloption evolved from a GUC and I'm more of
the opinion that neither should exist and that the overhead of this be
minimized and so require no tuning or consideration by the end user.

best.

-greg

Show quoted text

Regards,
Jeff Davis

#31Greg Burd
greg@burd.me
In reply to: Jeff Davis (#28)
Re: Expanding HOT updates for expression and partial indexes

On Oct 9, 2025, at 3:27 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Tue, 2025-10-07 at 17:36 -0400, Greg Burd wrote:

After reviewing how updates work in the executor, I discovered that
during execution the new tuple slot is populated with the information
from ExecBuildUpdateProjection() and the old tuple, but that most
importantly for this use case that function created a bitmap of the
modified columns (the columns specified in the update). This bitmap
isn't the same as the one produced by HeapDetermineColumnsInfo() as
the
latter excludes attributes that are not changed after testing
equality
with the helper function heap_attr_equals() where as the former will
include attributes that appear in the update but are the same value
as
before. This, happily, is immaterial for the purposes of my function
ExecExprIndexesRequireUpdates() which simply needs to check to see if
index tuples generated are unchanged. So I had all I needed to run
the
checks ahead of acquiring the lock on the buffer.

You're still calling ExecExprIndexesRequireUpdates() from within
heap_update(). Can't you do that inside of ExecUpdatePrologue() or
thereabouts?

Hey Jeff,

I'm trying to knit this into the executor layer but that is tricky because
the concept of HOT is very heap-specific, so the executor should be
ignorant of the heap's specific needs (right?). Right now, I am considering
adding a step in ExecUpdatePrologue() just after opening the indexes.

The idea I'm toying with is to have a new function on all TupleTableSlots
that examines the before/after slots for an update and the set of updated
attributes and returns a Bitmapset of the changed attributes that overlap
with indexes and so should trigger index updates in ExecUpdateEpilogue().

That way for heap we'd have something like:
Bitmapset *tts_heap_getidxattr(ResultRelInfo *info,
TupleTableSlot *updated,
TupleTableSlot *existing,
Bitmapset *updated_attrs)
{
some combo of HeapDeterminColumnsInfo() and
ExecExprIndexesRequireUpdates()

returns the set of indexed attrs that this update changed
}

So, attributes only referenced by expressions where the expression
produces the same value for the updated and existing slots would be
removed from the set.

Interestingly, summarizing indexes that don't overlap with changed
attributes won't be updated (and that's a good thing).

Problem is we're not yet accounting for what is about to happen in
ExecUpdateAct() when calling into the heap_update(). That's where
heap tries to fit the new tuple onto the same page. That might be
possible with large tuples thanks to TOAST, it's impossible to say
before getting into this function with the page locked.

So, for updates we include the modified_attrs in the UpdateContext
which is available to heap_update(). If the heap code decides to
go HOT, great unset all attributes in the modified_attrs except any
that are only summarizing. If the heap can't go HOT, fine, add
the indexed attrs back into modified_attrs which should trigger all
indexes to be updated.

This gets rid of TU_UpdateIndexes enum and allows only modified
summarizing indexes to be updated on the HOT path. Two additional
benefits IMO.

at least, that's what I'm trying out now,

-greg

Show quoted text

Regards,
Jeff Davis

#32Jeff Davis
pgsql@j-davis.com
In reply to: Greg Burd (#31)
Re: Expanding HOT updates for expression and partial indexes

On Tue, 2025-10-14 at 13:46 -0400, Greg Burd wrote:

I'm trying to knit this into the executor layer but that is tricky
because
the concept of HOT is very heap-specific, so the executor should be
ignorant of the heap's specific needs (right?).

It's wrong for the executor to say "do a HOT update" but it's OK for
the executor to say "this is the set of indexes that might have a new
key after the update". If that set is empty, then the heap can choose
to do a HOT update.

Right now, I am considering
adding a step in ExecUpdatePrologue() just after opening the indexes.

Seems like a reasonable place.

The idea I'm toying with is to have a new function on all
TupleTableSlots...

That way for heap we'd have something like:
Bitmapset *tts_heap_getidxattr(ResultRelInfo *info,
TupleTableSlot *updated,
TupleTableSlot *existing,
Bitmapset *updated_attrs)
{
some combo of HeapDeterminColumnsInfo() and
ExecExprIndexesRequireUpdates()

returns the set of indexed attrs that this update changed
}

Why is this a generic method for all slots? Do we need to reuse it
somewhere else? I would have expected just a static method in
nodeModifyTable.c that does just what's needed.

And to be precise, it's the set of indexed attrs where the update might
have created a new key, right? The whole point is that we don't care if
the indexed attr has been changed, so long as it doesn't create a new
index key.

Interestingly, summarizing indexes that don't overlap with changed
attributes won't be updated (and that's a good thing).

Nice.

Problem is we're not yet accounting for what is about to happen in
ExecUpdateAct() when calling into the heap_update().  That's where
heap tries to fit the new tuple onto the same page.  That might be
possible with large tuples thanks to TOAST, it's impossible to say
before getting into this function with the page locked.

I don't see why that's a problem. The executor can pass down the list
of indexed attrs that might have created new keys after the update,
then heap_update uses that information (along with other factors, like
if it fits on the same page) to determine whether to perform a HOT
update or not.

So, for updates we include the modified_attrs in the UpdateContext
which is available to heap_update().

It doesn't look like UpdateContext is currently available to
heap_update(). We might need to change the signature. But I think it's
fine to change the signature if it results in a cleaner design --
tableam extensions often need source changes when new major versions
are released.

  If the heap code decides to
go HOT, great unset all attributes in the modified_attrs except any
that are only summarizing.  If the heap can't go HOT, fine, add
the indexed attrs back into modified_attrs which should trigger all
indexes to be updated.

IIUC, that sounds like a good plan.

This gets rid of TU_UpdateIndexes enum and allows only modified
summarizing indexes to be updated on the HOT path.  Two additional
benefits IMO.

I'm not sure that I understand, but I'll look at that after we sort out
some of the other details.

Regards,
Jeff Davis

#33Greg Burd
greg@burd.me
In reply to: Jeff Davis (#28)
4 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Hello again.

This idea started over a year ago for me while working on a project that
used JSONB and had horrible "bloat" with update times that were not
fantastic. The root cause, expression indexes prevent HOT updates and
all indexes on JSONB are by definition, expressions. That's the backstory.

The idea for the solution came from a patch [1]/messages/by-id/4d9928ee-a9e6-15f9-9c82-5981f13ffca6@postgrespro.ru applied [2]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8, then later
reverted [3]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256, that basically evaluated the before/after tuple
expressions in heap_update() at the point where newbuff == buffer just
before deciding to use_hot_update or not. When the evaluated
expressions produced equal results using a binary comparison the index
didn't need to be updated. While this approach sorta worked, but it was
reverted for a few reasons, here's Tom's summary: "The problem here is
that [the code that checks for equality] thinks that the type of the
index column is identical to the type of the source datum for it, which
is not true for any opclass making use of the opckeytype property. [4]/messages/by-id/2877.1541538838@sss.pgh.pa.us"

Still, expanding the domain of what might go HOT seems like a good goal
to me and it was at the heart of the issues I was facing on the project
using JSONB, so I kept at it.

The patches I've sent on this thread have evolved from that first idea.
First they addressed the specific issue raised by Tom. Then they
expanded to include partial indexes as well. This worked, and I helped
to ship a fork of Postgres used this approach and solved customer issues
with the JSONB use case. Customer experience was better, no unnecessary
index updates when indexed data wasn't modified meant no more heap/index
bloat and faster update times. Vacuum could finally keep up and
performance and storage overhead was much better.

For this patch set v1 through v17 were essentially work that
refined/reworked that original approach, but there was a serious flaw
that I didn't fully appreciate until around v16/17. I was evaluating
the expressions while holding a lock on the buffer page which a) expands
the time the lock is held, and b) opens the door to self-deadlock. No bueno.

Then there was v18, a quick work-around for that. I moved the call that
invokes the executor to the beginning of heap_update() before taking the
lock on the page. To do this I had to find the set of updated
attributes, which I discovered was available in the executor as the
updated tuple is created. This was a viable fix, but didn't really go
far enough and was a bit hackish IMO. Jeff Davis and others challenged
me to move the work to identify what's changed into the executor and
clean it up. I'm a sucker for a challenge.

More generally, this idea made sense. While Postgres has many places
where logic is tightly coupled to the way the heap works this didn't
have to be one. To make this work I needed the update path in the
executor to be interested in:

a) knowing what columns were specified in the UPDATE statement and those
impacted by before/after triggers,
b) reducing that set to those attributes known to be both indexed and to
have changed value,
c) finding which of those (and possibly other) attributes that force new
index updates.

Why? We'll, that code already exists in a few places and in some cases
is replicated; for (a) there is ExecGetAllUpdatedCols(), for (b)
HeapDetermineColumnsInfo() and index_unchanged_by_update().

An interesting thing to note is that HeapDetermineColumnsInfo() might
return a set that includes columns not returned by
ExecGetAllUpdatedCols() because HeapDetermineColumnsInfo() iterates over
all indexed attributes looking for changes and that might find an
indexed attribute that was changed by heap_modify_tuple() but not
knowable by ExecGetAllUpdatedCols(). This happens in tsvector code, see
tsvector_op.c tsvector_update_trigger() where if (update_needed)
heap_modify_tuple_by_cols(). That column isn't known to ExecGetAllUpdatedCols().

HeapDetermineColumnsInfo() is also critical when modifying catalog
tuples. Catalog tuples are modified using either Form/GETSTRUCT or
values/nulls/replaces then using heap_modify_tuple() and calling into
CatalogTupleUpdate() which calls simple_heap_update() that calls
heap_update() where we find HeapDetermineColumnsInfo(). The interesting
thing here is that when modifying catalog tuples there is knowledge of
what attributes are changed, but that knowledge isn't preserved and
passed into CatalogTupleUpdate(), rather it is re-discovered in
HeapDetermineColumnsInfo(). That's how catalog tuples are able to take
the HOT path, they re-use that same logic. There is a fix for that [5]/messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me
too (and I really hope that lands in master ASAP), but that's not the
subject of this thread.

HeapDetermineColumnsInfo() also helps inform a few other decisions in
heap_update(), but these have to happen after taking the buffer lock and
are very heap-specific, namely:

1. Do either the replica identity key attributes overlap with the
modified index attributes or do they need to be stored externally, this
is passed on to ExtractReplicaIdentity() to find out if we must augment
the WAL log or not.

2. Are there any modified indexed attributes that intersect with the
primary keys of the relation, if not lower the lock mode to enable
multixact to work.

HeapDetermineColumnsInfo() also takes a pragmatic approach to testing
for equality when looking for modified indexed attributes, it uses
datumIsEqual() which boils down to a simple memcmp() of the before/after
HeapTuple datum. This is fine in most cases, but limits the scope of
what can be HOT.

Interestingly, this requirement of binary equality has leaked into other
parts of the code, namely nbtree's deduplication of TIDs on page split.
That code uses binary equality as well. A nbtree index with collation
for case insensitive must store both "A" and "a" despite those being
type-equal because they are not binary equivalent. More on this later.

At this point, I had my sights set on HeapDetermineColumnsInfo(). I
felt that what it was doing should move into the executor, well as much
of that work as possible, and outside of the buffer lock. This would
also open the door for removal of redundant code. My thought was that
the table AM update API should have an additional argument, the
"modified indexed attributes" or "mix_attrs", passed in.

So, here we are at the door of v19... let's begin.

0001 - Reorganize heap update logic

This is preparatory work for the larger goal in that heap_update()
serves two masters: CatalogTupleUpdate()/simple_heap_update() and
heap_tuple_update() and in reality, they were different but needed most
of the same logic that happens at the start of heap_update(). This
patch splits that logic out and moves it into heap_tuple_update() and
simple_heap_update(). Functionally nothing changes. That's the meat of
this patch.Reorganize heap update logic

0002 - Track changed indexed columns in the executor during UPDATEs

This is the first core set of changes, it doesn't expand HOT updates but
it does restructure where HeapDetermineColumnsInfo()'s core work happens.

A new function ExecCheckIndexedAttrsForChanges() in nodeModifyTable.c is
now responsible for checking for changes between Datum in the old/new
TupleTableSlots. This is different from before in that we're not
checking the new HeapTuple Datum verses the HeapTuple we read from the
buffer page while holding the lock on that page.

An update starts off by reading the existing tuple using the table AM.
Then a new updated tuple is created as the set of changes to the old.
Then the new TupleTableSlot is the combination of the existing one we
just read and the changes we just recorded. So, in the executor before
calling into the table AM's update function we have a pin on the buffer
and the before/after TupleTableSlots for this update. So, I've put the
call to my new ExecCheckIndexedAttrsForChanges() function just before
calling table_tuple_update() and I've added the "mix_attrs" into that
call which get passed on to the heap in heap_tuple_update() and then
heap_update() and all is well.

Why is this safe? The way I read heap_update() is that it has always
historically had code to deal with cases where the tuple is concurrently
updated and react accordingly thanks to HeapTupleSatisfiesUpdate() which
remains where it was in heap_update(). Visibility checks happened when
we first read the tuple to form the updated tuple and later in
heap_update() when we call HeapTupleSatisfiesVisibility() to check for
transaction-snapshot mode RI updates.

So, this new update path from the executor into the table AM seems to me
to be okay and almost functionally equivalent. But there is one big
change to discuss before moving to the simple_heap_update() path.

In nodeModifyTable.c tts_attr_equal() replaces heap_attr_equal()
changing the test for equality when calling into heap_tuple_update().
In the past we used datumIsEqual(), essentially a binary comparison
using memcmp(), now the comparison code in tts_attr_equal uses
type-specific equality function when available and falls back to
datumIsEqual() when not.

The other parts of HeapDetermineColumnsInfo() remain in the code, but
they still happen within the simple_heap_update() and
heap_tuple_update() code. That's where you'll find that after the
buffer is locked we do (1) and (2) from above. This keeps the
heap-specific work in the heap, but we've moved some work up into the
executor and outside the buffer lock.

While this accomplishes the goal of removing HeapDetermineColumnsInfo()
from heap_update() on the path that uses the table AM API
heap_tuple_update() but it doesn't on the simple_heap_update() path.
That remains the same as it was in the previous patch. Ideally, my
patch to restructure how catalog tuples are updated [5]/messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me is committed and
we can fully remove HeapDetermineColumnsInfo() and likely speed up all
catalog updates in the process. That's what motivated [5]/messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me, please take
a look, it required a huge number of changes so I thought it deserved a
life/thread of its own.

Finally, there is the ExecSimpleRelationUpdate() path and
slot_modify_data(). On this path we know what attributes are being
updated, so we just check to see if they changed and then intersect that
with the set of indexed attributes and we have our modified indexed
attributes set to pass into simple_heap_update().

0003 - Replace index_unchanged_by_update with ri_ChangedIndexedCols

This patch removes the function index_unchanged_by_update() in
execIndexing.c and simply re-uses the modified indexed attributes that
we've stashed away in ResultRelInfo as ri_ChangedIndexedCols. This
provides a hint when calling into the index AM's index_insert() function
indicating if the UPDATE was without logical change to the data or not.
We've done that check, we don't need to do it again.

0004 - Enable HOT updates for expression and partial indexes

This finally gets us back to where this project started, but on much
more firm ground than before because we're not going to self-deadlock.
The idea has grown from a small function into something larger, but only
out of necessity.

In this patch I add ExecWhichIndexesRequireUpdates() in execIndexing.c
which implements (c) finding the set of attributes that force new index
updates. This set can be very different from the modified indexed
attributes. We know that some attributes are not equal to their
previous versions, but does that mean that the index that references
that attribute needs a new index tuple? It may, or it may not. Here's
the comment on that function that explains:

/*
* ExecWhichIndexesRequireUpdates
*
* Determine which indexes need updating given modified indexed attributes.
* This function is a companion to ExecCheckIndexedAttrsForChanges().
On the
* surface, they appear similar but they are doing two very different things.
*
* For a standard index on a set of attributes this is the intersection of
* the mix_attrs and the index attrs (key, expression, but not predicate).
*
* For expression indexes and indexes which implement the amcomparedatums()
* index AM API we'll need to form index datum and compare each
attribute to
* see if any actually changed.
*
* For expression indexes the result of the expression might not change
at all,
* this is common with JSONB columns which require expression indexes
and where
* it is commonplace to index a field within a document and have updates that
* generally don't update that field.
*
* Partial indexes won't trigger index tuples when the old/new tuples
are both
* outside of the predicate range.
*
* For nbtree the amcomparedatums() API is critical as it requires that key
* attributes are equal when they memcmp(), which might not be the case when
* using type-specific comparison or factoring in collation which might make
* an index case insensitive.
*
* All of this is to say that the goal is for the executor to know,
ahead of
* calling into the table AM for the update and before calling into the index
* AM for inserting new index tuples, which attributes at a minimum will
* necessitate a new index tuple.
*
...
*/

Whereas before we were comparing Datum in the table relation, now we're
comparing Datum in the index relation. Index AMs are free to store what
they want, we need to know if what's changed and referenced by the index
means that the index needs a new tuple or not.

In the case of a JSONB expression index (or any expression index) the
expression is evaluated when calling FormIndexDatum(). The result of
the expression is the Datum in the values/isnull arrays. Then we need
to compare them to see if they changed. This can be done using the same
tts_attr_equal() function, but with the attribute desc of the index, not
table relation.

In some cases that's not enough, for instance nbtree and it's more
stringent equality requirements. For that reason (and another coming up
in a second) we need a new optional index AM API, amcomparedatums().
Indexes implementing this function have the ability to compare two
Datums for equality in what ever way they want. For nbtree, that's a
binary comparison.

The new case here that this also supports is for indexes like GIN/RUM
where there is an opclass that can extract zero or more pieces from the
attribute and form multiple index entries when needed. Those extracted
pieces might have odd equality rules as well. This opens the door for
index implementations to provide information that will help inform heap
when making HOT decisions that didn't exist before.

Take for example the Linux Foundation's DocumentDB [6]https://www.linuxfoundation.org/press/linux-foundation-welcomes-documentdb-to-advance-open-developer-first-nosql-innovation project [7]https://github.com/documentdb/documentdb which
aims to be an open source alternative to MongoDB built on top of
PostgreSQL. One of the pieces of that project is its "extended RUM"
index AM implementation. This index extracts portions of the BSON
documents stored and forms index keys from that. Here is an example:

CREATE INDEX documents_rum_index_14002
ON documentdb_data.documents_14001
USING documentdb_rum
(document bson_rum_single_path_ops (path=a, iswildcard='true', tl='2699'))

An index on the "document" column which uses the
"bson_rum_single_path_ops" opclass to extract a portion of the BSON
document that matches "path=a".

For this to potentially be stored by heap as a HOT update we need to
know that what changed within that document didn't intersect with the
"path=a" and if it did that the new value(s) were all equal to the old
values. Equality in DocumentDB isn't what you think, it's quite odd and
specific to BSON and rules defined by MongoDB so it's important to allow
the index AM to execute what's necessary for it's use case.

For the more common JSONB use case we can now have heap make an informed
decision about HOT updates after evaluating the expression. For
GIN/RUM/etc. implementation there is a path to HOT. For nbtree there is
a way to maintain its requirements.

Is there a cost to all this? Yes, of course. There is net new work
being done on some paths. IndexFormDatum() will be called more
frequently and sometimes twice for the same thing. This could be
improved, there might be a way to cache that information. But to have
tests of old/new we'll have to do that work at least twice.

Is there a benefit? Yes, of course. Some redundant code paths are gone
and in the end we've increased the number of cases where HOT updates are
a possibility. This especially helps out users of JSONB, but not only them.

What's left undone?

* I need to check code coverage so that I might
* create tests covering all the new cases
* update the README.HOT documentation, wiki, etc.
* performance...

For performance I'd like to examine some worst cases as in lots of
indexes that have a lot of new code to exercise and all but the last
index would allow for a HOT update. That should represent the maximum
amount of new overhead for this code. Then, the other side of the
equation, how much does this help JSONB? I think that is something to
measure in terms of TPS as well as "bloat" avoided and time spent vacuuming.

I also don't like the TU_Updating enum, I think it's a leaky abstraction
and really pointless now. I'd like to remove it in favor of the bitmap
of attributes known to force index tuples to be inserted. Maybe I'll
layer that into the next set.

In the end, this is a lot of work and I believe that it moves the ball
forward. I'll have more metrics on that soon I hope, but I wanted to
get the conversation re-started ASAP as we're late in the v19 cycle.

Finally, the "elephant" in the room (ha!) is PHOT[9]/messages/by-id/2ECBBCA0-4D8D-4841-8872-4A5BBDC063D2@amazon.com/WARM[10]/messages/by-id/CABOikdMop5Rb_RnS2xFdAXMZGSqcJ-P-BY2ruMd+buUkJ4iDPw@mail.gmail.com[11]/messages/by-id/CABOikdMNy6yowA+wTGK9RVd8iw+CzqHeQSGpW7Yka_4RSZ_LOQ@mail.gmail.com. Yes,
some of this work does help make a solution to allow HOT updates when
only updating a subset of indexes closer to reality, I'd be lying not to
mention that here, it is a big part of my overall plan for my next year
and (I hope) v20 and I need this work to get there. Specifically, PHOT
is in part based on HeapDetermineColumnsInfo() and I needed to make that
more truthful for it to work.

I hope you see the value in this work and will partner with me to
finalize it and get it into master.

best.

-greg

[1]: /messages/by-id/4d9928ee-a9e6-15f9-9c82-5981f13ffca6@postgrespro.ru
[2]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c203d6cf8
[3]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=05f84605dbeb9cf8279a157234b24bbb706c5256
[4]: /messages/by-id/2877.1541538838@sss.pgh.pa.us
[5]: /messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me
[6]: https://www.linuxfoundation.org/press/linux-foundation-welcomes-documentdb-to-advance-open-developer-first-nosql-innovation
[7]: https://github.com/documentdb/documentdb
[8]: https://github.com/documentdb/documentdb/tree/main/pg_documentdb_extended_rum
[9]: /messages/by-id/2ECBBCA0-4D8D-4841-8872-4A5BBDC063D2@amazon.com
[10]: /messages/by-id/CABOikdMop5Rb_RnS2xFdAXMZGSqcJ-P-BY2ruMd+buUkJ4iDPw@mail.gmail.com
[11]: /messages/by-id/CABOikdMNy6yowA+wTGK9RVd8iw+CzqHeQSGpW7Yka_4RSZ_LOQ@mail.gmail.com
/messages/by-id/CABOikdMNy6yowA+wTGK9RVd8iw+CzqHeQSGpW7Yka_4RSZ_LOQ@mail.gmail.com

Attachments:

v19-0001-Reorganize-heap-update-logic.patchapplication/octet-streamDownload
From 6d76d6ad5bc7077727f92bdb9c33b78ca3d3c14e Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v19 1/4] Reorganize heap update logic

This commit refactors the interaction between heap_tuple_update(),
heap_update(), and simple_heap_update() to improve code organization
and flexibility. The changes are functionally equivalent to the
previous implementation and have no performance impact.

The primary motivation is to prepare for upcoming modifications to
how and where modified attributes are identified during the update
path, particularly for catalog updates.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().

No user-visible changes.
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4b0c49f4bb0..aff47481345 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -103,10 +101,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2799,6 +2797,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2811,6 +2810,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3014,6 +3015,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3035,7 +3037,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3247,7 +3252,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3255,30 +3263,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3292,7 +3291,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3300,144 +3298,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3449,7 +3316,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3473,17 +3340,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3515,8 +3375,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3556,13 +3416,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3572,9 +3431,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3599,8 +3458,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3641,9 +3500,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3653,20 +3512,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3679,39 +3538,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3724,10 +3577,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3738,9 +3591,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3752,12 +3605,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3772,7 +3625,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3800,7 +3653,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3817,12 +3670,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3855,9 +3708,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3867,18 +3720,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3887,7 +3740,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3901,10 +3754,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3926,7 +3779,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3962,20 +3815,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4013,7 +3866,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4021,7 +3874,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4029,7 +3881,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4040,7 +3892,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4057,10 +3909,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4082,7 +3932,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4091,7 +3941,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4100,17 +3950,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4118,7 +3968,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4143,12 +3993,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4173,7 +4023,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4181,14 +4031,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4221,13 +4071,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4236,7 +4079,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4408,7 +4251,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4491,25 +4334,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9149,12 +9142,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9185,17 +9177,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9208,7 +9196,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9217,8 +9205,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..1cf9a18775d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 909db73b7bb..41d541aa6b2 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -321,11 +321,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -391,6 +393,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.49.0

v19-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From 69d226a736a77ca83e8df2e617226552c431da13 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v19 2/4] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo in heap_update. This enables the comparison
to happen without taking a lock on the page and opens the door to reuse
in other code paths.

Because heap_update now requires the caller to provide the modified
indexed columns simple_heap_update has become a tad more complex.  It is
frequently called from CatalogTupleUpdate which either updates heap
tuples via their form or using heap_modify_tuple.  In both cases the
caller does know the modified set of attributes, but sadly those
attributes are lost before being provided to simple_heap_update.  Due to
that the "simple" path has to retain the HeapDetermineColumnsInfo logic
of old (for now).  In order for that to work it was necessary to split
the (overly large) heap_update call itself up.  This moves up into
simple_heap_update and heap_tuple_update a bit of what existed in
heap_update itself.  Ideally this will be cleaned up once
CatalogTupleUpdate paths are all recording modified attributes
correctly, when that happens the "simple" path can be simplified again.

ExecCheckIndexedAttrsForChanges replaces HeapDeterminesColumnsInfo and
tts_attr_equal replaces heap_attr_equal changing the test for equality
when calling into heap_tuple_update (but not simple_heap_update).  In
the past we used datumIsEqual(), essentially a binary comparison using
memcmp(), now the comparison code in tts_attr_equal uses type-specific
equality function when available and falls back to datumIsEqual() when
not.  This change in equality testing has some intended implications and
opens the door for more HOT updates (foreshadowing).  For instance,
indexes with collation information allowing more HOT updates when the
index is specified to be case insensitive.

This change forced some logic changes in execReplication on the update
paths is now it is required to have knowledge of the set of attributes
that are both changed and referenced by indexes.  Luckilly, the this is
available within calls to slot_modify_data() where LogicalRepTupleData
is processed and has a set of updated attributes.  In this case rather
than using ExecCheckIndexedAttrsForChanges we can preseve what
slot_modify_data() identifies as the modified set and then intersect
that with the set of indexes on the relation and get the correct set of
modified indexed attributes required on heap_update().
---
 src/backend/access/heap/heapam.c         |  12 +-
 src/backend/access/heap/heapam_handler.c |  72 +++++--
 src/backend/access/table/tableam.c       |   5 +-
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/execReplication.c   |   7 +
 src/backend/executor/nodeModifyTable.c   | 247 ++++++++++++++++++++++-
 src/backend/nodes/bitmapset.c            |   4 +
 src/backend/replication/logical/worker.c |  72 ++++++-
 src/backend/utils/cache/relcache.c       |  15 ++
 src/include/access/tableam.h             |   8 +-
 src/include/executor/executor.h          |   5 +
 src/include/nodes/execnodes.h            |   1 +
 src/include/utils/rel.h                  |   1 +
 src/include/utils/relcache.h             |   1 +
 14 files changed, 415 insertions(+), 36 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index aff47481345..1cdb72b3a7a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3263,12 +3263,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1cf9a18775d..ef08e1d3e10 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -414,16 +416,61 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +484,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 5e41404937e..dadcf03ed24 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,6 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  Bitmapset *modified_indexed_cols,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -346,7 +347,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								modified_indexed_cols,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 27c9eec697b..6b7b6bc8019 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index def32774c90..2709e2db0f2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4c5647ac38a..4d1cf50e369 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -54,11 +55,14 @@
 
 #include "access/htup_details.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,6 +72,8 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
@@ -176,6 +182,219 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   bool canSetTag);
 
 
+/*
+ * Compare two datums using the type's default equality operator.
+ *
+ * Returns true if the values are equal according to the type's equality
+ * operator, false otherwise. Falls back to binary comparison if no
+ * type-specific operator is available.
+ *
+ * This function uses the TypeCache infrastructure which caches operator
+ * lookups for efficiency.
+ */
+bool
+tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+			   Datum value1, Datum value2)
+{
+	TypeCacheEntry *typentry;
+
+	LOCAL_FCINFO(fcinfo, 2);
+	Datum		result;
+
+	/*
+	 * Fast path for common types to avoid even the type cache lookup. These
+	 * types have simple equality semantics.
+	 */
+	switch (typid)
+	{
+		case INT2OID:
+			return DatumGetInt16(value1) == DatumGetInt16(value2);
+		case INT4OID:
+			return DatumGetInt32(value1) == DatumGetInt32(value2);
+		case INT8OID:
+			return DatumGetInt64(value1) == DatumGetInt64(value2);
+		case FLOAT4OID:
+			return !float4_cmp_internal(DatumGetFloat4(value1), DatumGetFloat4(value2));
+		case FLOAT8OID:
+			return !float8_cmp_internal(DatumGetFloat8(value1), DatumGetFloat8(value2));
+		case BOOLOID:
+			return DatumGetBool(value1) == DatumGetBool(value2);
+		case OIDOID:
+		case REGPROCOID:
+		case REGPROCEDUREOID:
+		case REGOPEROID:
+		case REGOPERATOROID:
+		case REGCLASSOID:
+		case REGTYPEOID:
+		case REGROLEOID:
+		case REGNAMESPACEOID:
+		case REGCONFIGOID:
+		case REGDICTIONARYOID:
+			return DatumGetObjectId(value1) == DatumGetObjectId(value2);
+		case CHAROID:
+			return DatumGetChar(value1) == DatumGetChar(value2);
+		default:
+			/* Continue to type cache lookup */
+			break;
+	}
+
+	/*
+	 * Look up the type's equality operator using the type cache. Request both
+	 * the operator OID and the function info for efficiency.
+	 */
+	typentry = lookup_type_cache(typid,
+								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
+
+	/*
+	 * If no equality operator is available, fall back to binary comparison.
+	 * This handles types that don't have proper equality operators defined.
+	 */
+	if (!OidIsValid(typentry->eq_opr))
+		return datumIsEqual(value1, value2, typbyval, typlen);
+
+	/*
+	 * Use the cached function info if available, otherwise look it up. The
+	 * type cache keeps this around so subsequent calls are fast.
+	 */
+	if (typentry->eq_opr_finfo.fn_addr == NULL)
+	{
+		Oid			eq_proc = get_opcode(typentry->eq_opr);
+
+		if (!OidIsValid(eq_proc))
+			/* Shouldn't happen, but fall back to binary comparison */
+			return datumIsEqual(value1, value2, typbyval, typlen);
+
+		fmgr_info_cxt(eq_proc, &typentry->eq_opr_finfo,
+					  CacheMemoryContext);
+	}
+
+	/* Set up function call */
+	InitFunctionCallInfoData(*fcinfo, &typentry->eq_opr_finfo, 2,
+							 collation, NULL, NULL);
+
+	fcinfo->args[0].value = value1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = value2;
+	fcinfo->args[1].isnull = false;
+
+	/* Invoke the equality operator */
+	result = FunctionCallInvoke(fcinfo);
+
+	/*
+	 * If the function returned NULL (shouldn't happen for equality ops),
+	 * treat as not equal for safety.
+	 */
+	if (fcinfo->isnull)
+		return false;
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Determine which updated attributes actually changed values between old and
+ * new tuples and are referenced by indexes on the relation.
+ *
+ * Returns a Bitmapset of attribute offsets (0-based, adjusted by
+ * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								TupleTableSlot *tts_old,
+								TupleTableSlot *tts_new)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *indexed_attrs;
+	Bitmapset  *modified = NULL;
+	int			attidx;
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/*
+	 * Get the set of index key attributes.  This includes summarizing,
+	 * expression indexes and attributes mentioned in the predicate of a
+	 * partition but not those in INCLUDING.
+	 */
+	indexed_attrs = RelationGetIndexAttrBitmap(relation,
+											   INDEX_ATTR_BITMAP_INDEXED);
+	Assert(!bms_is_empty(indexed_attrs));
+
+	/*
+	 * NOTE: It is important to scan all indexed attributes in the tuples
+	 * because ExecGetAllUpdatedCols won't include columns that may have been
+	 * modified via heap_modify_tuple_by_col which is the case in
+	 * tsvector_update_trigger.
+	 */
+	attidx = -1;
+	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	{
+		/* attidx is zero-based, attrnum is the normal attribute number */
+		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+		Form_pg_attribute attr;
+		bool		oldnull,
+					newnull;
+		Datum		oldval,
+					newval;
+
+		/*
+		 * If it's a whole-tuple reference, record as modified.  It's not
+		 * really worth supporting this case, since it could only succeed
+		 * after a no-op update, which is hardly a case worth optimizing for.
+		 */
+		if (attrnum == 0)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/*
+		 * Likewise, include in the modified set any system attribute other
+		 * than tableOID; we cannot expect these to be consistent in a HOT
+		 * chain, or even to be set correctly yet in the new tuple.
+		 */
+		if (attrnum < 0)
+		{
+			if (attrnum != TableOidAttributeNumber)
+				modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* Extract values from both slots */
+		oldval = slot_getattr(tts_old, attrnum, &oldnull);
+		newval = slot_getattr(tts_new, attrnum, &newnull);
+
+		/* If one value is NULL and the other is not, they are not equal */
+		if (oldnull != newnull)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* If both are NULL, consider them equal */
+		if (oldnull)
+			continue;
+
+		/* Get attribute metadata */
+		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
+		attr = TupleDescAttr(tupdesc, attrnum - 1);
+
+		/* Compare using type-specific equality operator */
+		if (!tts_attr_equal(attr->atttypid,
+							attr->attcollation,
+							attr->attbyval,
+							attr->attlen,
+							oldval,
+							newval))
+			modified = bms_add_member(modified, attidx);
+	}
+
+	bms_free(indexed_attrs);
+
+	return modified;
+}
+
 /*
  * Verify that the tuples to be produced by INSERT match the
  * target relation's rowtype
@@ -2168,8 +2387,8 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2291,6 +2510,16 @@ lreplace:
 	if (resultRelationDesc->rd_att->constr)
 		ExecConstraints(resultRelInfo, slot, estate);
 
+	/*
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	resultRelInfo->ri_ChangedIndexedCols =
+		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+
 	/*
 	 * replace the heap tuple
 	 *
@@ -2306,6 +2535,7 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								resultRelInfo->ri_ChangedIndexedCols,
 								&updateCxt->updateIndexes);
 
 	return result;
@@ -2524,8 +2754,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3222,8 +3453,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3248,6 +3479,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4333,7 +4565,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4509,6 +4741,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index b4ecf0b0390..9014990267a 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 93970c6af29..b363eaa49cc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -243,6 +243,8 @@
  */
 
 #include "postgres.h"
+#include "access/sysattr.h"
+#include "nodes/bitmapset.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
@@ -275,7 +277,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -291,6 +292,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1112,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1200,28 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+				if (!tts_attr_equal(att->atttypid,
+									att->attcollation,
+									att->attbyval,
+									att->attlen,
+									srcslot->tts_values[i],
+									slot->tts_values[i]))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1229,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2947,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2964,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2989,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3501,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3571,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3597,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..32825596be1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2482,6 +2482,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5284,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5307,6 +5309,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5332,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5373,6 +5378,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5505,10 +5511,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5531,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5535,6 +5547,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5551,6 +5564,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index e16bf025692..8a5931a3118 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1502,12 +1503,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 updated_cols, update_indexes);
 }
 
 /*
@@ -2010,6 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  Bitmapset *modified_indexe_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..993dc0e6ced 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -800,5 +800,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
+												  TupleTableSlot *tts_old,
+												  TupleTableSlot *tts_new);
+extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+						   Datum value1, Datum value2);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..8b08e0045ba 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -498,6 +498,7 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 80286076a11..b23a7306e69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..d3fbb8b093a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
-- 
2.49.0

v19-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From 8687e77751d0ee16d4d9495828099b918426423b Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v19 3/4] Replace index_unchanged_by_update with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update with the same information
found earlier in the update path.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 874a8fc89ad..5d819bda54a 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -300,8 +300,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 401606f840a..fb1bc3a480d 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -138,11 +138,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -440,10 +435,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -993,152 +985,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..d69dc090aa4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8b08e0045ba..898368fb8cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -202,10 +202,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.49.0

v19-0004-Enable-HOT-updates-for-expression-and-partial-in.patchapplication/octet-streamDownload
From 05c4e601b85be2fd79b642de2e1c194b5ad7ea80 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v19 4/4] Enable HOT updates for expression and partial indexes

Currently, PostgreSQL conservatively prevents HOT (Heap-Only Tuple)
updates whenever any indexed column changes, even if the indexed
portion of that column remains identical. This is overly restrictive
for expression indexes (where f(column) might not change even when
column changes) and partial indexes (where both old and new tuples
might fall outside the predicate).

This patch introduces several improvements to enable HOT updates in
these cases:

Add amcomparedatums callback to IndexAmRoutine. This allows index
access methods like GIN to provide custom logic for comparing datums
by extracting and comparing index keys rather than comparing the raw
datums. GIN indexes now implement gincomparedatums() which extracts
keys from both datums and compares the resulting key sets.

Add ExecWhichIndexesRequireUpdates() to refine the set of modified
attributes and determine precisely which indexes need updating. For
partial indexes, this checks whether both old and new tuples satisfy
or fail the predicate. For expression indexes, this uses type-specific
equality operators to compare computed values. For extraction-based
indexes (GIN/RUM), this delegates to amcomparedatums.

Modify heap update paths to use the refined modified indexed attrs
bitmapset returned by ExecWhichIndexesRequireUpdates(). This allows
HOT updates when indexes don't actually require updating, while still
preventing HOT updates when they do.

Importantly, table access methods can still signal using TU_Update if
all, none, or only summarizing indexes should be updated.  While the
executor layer now owns determining what has changed due to an update
and is interested in only updating the minimum number of indexes
possible, the table AM can override that while performing
table_tuple_update(), which is what heap does.

This optimization significantly improves update performance for tables
with expression indexes, partial indexes, and GIN/GiST indexes on
complex data types like JSONB and tsvector, while maintaining correct
index semantics.  Minimal additional overhead due to type-specific
equality checking should be washed out by the benefits of updating
indexes fewer times.
---
 src/backend/access/gin/ginutil.c              |  94 ++-
 src/backend/access/heap/heapam.c              |  10 +-
 src/backend/access/heap/heapam_handler.c      |   6 +-
 src/backend/access/nbtree/nbtree.c            |  38 ++
 src/backend/access/table/tableam.c            |   4 +-
 src/backend/bootstrap/bootstrap.c             |   8 +
 src/backend/catalog/index.c                   |  57 ++
 src/backend/catalog/indexing.c                |  16 +-
 src/backend/catalog/toasting.c                |   4 +
 src/backend/executor/execIndexing.c           | 326 ++++++++-
 src/backend/executor/nodeModifyTable.c        |  61 +-
 src/backend/nodes/makefuncs.c                 |   4 +
 src/include/access/amapi.h                    |  28 +
 src/include/access/gin.h                      |   3 +
 src/include/access/heapam.h                   |   6 +-
 src/include/access/nbtree.h                   |   4 +
 src/include/access/tableam.h                  |   8 +-
 src/include/catalog/index.h                   |   1 +
 src/include/executor/executor.h               |   5 +
 src/include/nodes/execnodes.h                 |  19 +
 .../expected/hot_expression_indexes.out       | 644 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   6 +
 .../regress/sql/hot_expression_indexes.sql    | 491 +++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 24 files changed, 1794 insertions(+), 50 deletions(-)
 create mode 100644 src/test/regress/expected/hot_expression_indexes.out
 create mode 100644 src/test/regress/sql/hot_expression_indexes.sql

diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 78f7b7a2495..85e25ed73e8 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = ginbuildphasename;
 	amroutine->amvalidate = ginvalidate;
+	amroutine->amcomparedatums = gincomparedatums;
 	amroutine->amadjustmembers = ginadjustmembers;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -729,3 +724,88 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare two datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/*
+	 * Extract keys from both datums using existing GIN infrastructure.
+	 */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys → definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		int			cmp = ginCompareEntries(&ginstate, attnum,
+											old_keys[i], old_categories[i],
+											new_keys[i], new_categories[i]);
+
+		if (cmp != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1cdb72b3a7a..5b0ff13b13d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3268,7 +3268,7 @@ heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			Buffer buffer, Page page, BlockNumber block, ItemId lp,
 			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
-			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4337,8 +4337,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4467,7 +4468,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4500,7 +4501,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4526,6 +4526,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ef08e1d3e10..7527809ec08 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -319,7 +319,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 					Snapshot crosscheck, bool wait,
 					TM_FailureData *tmfd,
 					LockTupleMode *lockmode,
-					Bitmapset *mix_attrs,
+					const Bitmapset *mix_attrs,
 					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
@@ -407,10 +407,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fdff960c130..73cc3208757 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -155,6 +155,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
+	amroutine->amcomparedatums = btcomparedatums;
 	amroutine->amadjustmembers = btadjustmembers;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
@@ -1795,3 +1796,40 @@ bttranslatecmptype(CompareType cmptype, Oid opfamily)
 			return InvalidStrategy;
 	}
 }
+
+/*
+ * btcomparedatums - Compare two datums for equality
+ *
+ * This function is necessary because nbtree requires that keys that are not
+ * binary identical not be "equal".  Other indexes might allow "A" and "a" to
+ * be "equal" when collation is case insensative, but not nbtree.  Why?  Well,
+ * nbtree deduplicates TIDs on page split and the way it accomplish that is by
+ * doing a binary comparison of the keys.
+ */
+
+bool
+btcomparedatums(Relation index, int attrnum,
+				Datum old_datum, bool old_isnull,
+				Datum new_datum, bool new_isnull)
+{
+	TupleDesc	desc = RelationGetDescr(index);
+	CompactAttribute *att;
+
+	/*
+	 * If one value is NULL and other is not, then they are certainly not
+	 * equal
+	 */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * If both are NULL, they can be considered equal.
+	 */
+	if (old_isnull)
+		return true;
+
+	/* We do simple binary comparison of the two datums */
+	Assert(attrnum <= desc->natts);
+	att = TupleDescCompactAttr(desc, attrnum - 1);
+	return datumIsEqual(old_datum, new_datum, att->attbyval, att->attlen);
+}
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dadcf03ed24..ef7736bfa76 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,7 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  Bitmapset *modified_indexed_cols,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -348,7 +348,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode,
-								modified_indexed_cols,
+								mix_attrs,
 								update_indexes);
 
 	switch (result)
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index fc8638c1b61..329c110d0bf 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -961,10 +961,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..29b8cc4badd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2414,6 +2416,61 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnkeyatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		/*
+		 * Expressions are not allowed on non-key attributes, so we can skip
+		 * them as they should show up in the index HOT-blocking attributes.
+		 */
+		indnkeyatts = ii->ii_NumIndexKeyAttrs;
+
+		/* Collect key attributes used by the index */
+		for (i = 0; i < indnkeyatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..a361c215490 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 5d819bda54a..c665aa744b3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index fb1bc3a480d..736543147e7 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
@@ -318,8 +322,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -343,13 +347,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -366,7 +370,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -387,8 +391,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -396,11 +407,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -435,7 +445,9 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
+		indexUnchanged = update &&
+			!bms_overlap(indexInfo->ii_IndexedAttrs,
+						 resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -604,7 +616,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
@@ -1018,3 +1035,284 @@ ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char t
 				 errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
 						NameStr(attname), RelationGetRelationName(rel))));
 }
+
+/*
+ * ExecWhichIndexesRequireUpdates
+ *
+ * Determine which indexes need updating given modified indexed attributes.
+ * This function is a companion to ExecCheckIndexedAttrsForChanges().  On the
+ * surface, they appear similar but they are doing two very different things.
+ *
+ * For a standard index on a set of attributes this is the intersection of
+ * the mix_attrs and the index attrs (key, expression, but not predicate).
+ *
+ * For expression indexes and indexes which implement the amcomparedatums()
+ * index AM API we'll need to form index datum and compare each attribute to
+ * see if any actually changed.
+ *
+ * For expression indexes the result of the expression might not change at all,
+ * this is common with JSONB columns which require expression indexes and where
+ * it is commonplace to index a field within a document and have updates that
+ * generally don't update that field.
+ *
+ * Partial indexes won't trigger index tuples when the old/new tuples are both
+ * outside of the predicate range.
+ *
+ * For nbtree the amcomparedatums() API is critical as it requires that key
+ * attributes are equal when they memcmp(), which might not be the case when
+ * using type-specific comparison or factoring in collation which might make
+ * an index case insensitive.
+ *
+ * All of this is to say that the goal is for the executor to know, ahead of
+ * calling into the table AM for the update and before calling into the index
+ * AM for inserting new index tuples, which attributes at a minimum will
+ * necessitate a new index tuple.
+ *
+ * The mix_attrs parameter contains attributes that:
+ *  1. Were refereced by the UPDATE statement and are known to have been modified
+ *  2. Are referenced by at least one index (expression, predicate, or otherwise)
+ *
+ * This function refines that set by determining which indexes actually
+ * need updates and only retaining the attributes that indicate that:
+ *   - Partial index predicates: If both tuples fall outside, no update needed
+ *   - Expression indexes: If expression results are identical, no update needed
+ *   - Extraction indexes (GIN, RUM, etc): If extracted values are identical, no update needed
+ *
+ * Returns a refined Bitmapset of attributes that force index updates.
+ */
+Bitmapset *
+ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+							   Bitmapset *mix_attrs,
+							   EState *estate,
+							   TupleTableSlot *old_tts,
+							   TupleTableSlot *new_tts)
+{
+	Bitmapset  *result_attrs = NULL;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+	TupleTableSlot *save_scantuple;
+	int			i;
+
+	if (relinfo->ri_NumIndices == 0 || bms_is_empty(mix_attrs))
+		return NULL;
+
+	/* Check each index */
+	for (i = 0; i < relinfo->ri_NumIndices; i++)
+	{
+		Relation	indexRel = relinfo->ri_IndexRelationDescs[i];
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		IndexAmRoutine *amroutine = indexRel->rd_indam;
+		bool		has_expressions = (indexInfo->ii_Expressions != NIL);
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		Bitmapset  *idx_attrs = indexInfo->ii_IndexedAttrs;
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
+
+		/* Check partial index predicate iff attrs overlap with modified */
+		if (is_partial &&
+			!bms_is_empty((pre_attrs = bms_intersect(pre_attrs, mix_attrs))))
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+			{
+				bms_free(pre_attrs);
+				continue;
+			}
+
+			/* Predicate transition, must update index, add predicate attrs */
+			if (old_qualifies != new_qualifies)
+			{
+				/*
+				 * We can say for sure that the index needs update, so we can
+				 * add in the attributes from the predicate that are also in
+				 * the mix_attrs set, but we don't yet know if there are other
+				 * attributes this index references that are modified (in the
+				 * mix_attrs) and force index updates.  The only way to know
+				 * if to test them one by one.
+				 */
+				result_attrs = bms_add_members(result_attrs, pre_attrs);
+				bms_free(pre_attrs);
+			}
+		}
+
+		/*
+		 * If we've got a result set equal to our modified set then we've
+		 * identified that all the attributes on the index need to trigger new
+		 * index tuples.  No need to keep checking, we're done not just with
+		 * this index but in general, break out of the loop here.
+		 */
+		if (bms_equal(result_attrs, mix_attrs))
+			break;
+
+		/*
+		 * NOTE: While it feels like we could avoid checking any further when
+		 * the indexed attributes (key and expression) do not overlap with the
+		 * modified indexed attributes (mix_attrs) we can't.  Why?  Well, it
+		 * turns out that it is allowable for index AMs to have a different
+		 * notion of equality.  For instance, nbtree requires that datum
+		 * equality be based on binary comparison, not anything type-specific.
+		 * So, it is possible that the set of mix_attrs from
+		 * ExecCheckIndexedAttrsForChanges() found that the new value of an
+		 * attribute was equal to the old value despite it having a different
+		 * binary representation.
+		 *
+		 * XXX: maybe that's something we should enforce and change nbtree?
+		 */
+
+		/*
+		 * Expression index or extraction-based index require us to form index
+		 * datums/tuples and compare.  We've done all we can to avoid this
+		 * overhead now it's time to bite the bullet and get it done.
+		 */
+		if (has_expressions || has_am_compare)
+		{
+			Datum		old_values[INDEX_MAX_KEYS];
+			bool		old_isnull[INDEX_MAX_KEYS];
+			Datum		new_values[INDEX_MAX_KEYS];
+			bool		new_isnull[INDEX_MAX_KEYS];
+			TupleDesc	indexdesc = RelationGetDescr(indexRel);
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			/* Evaluate expressions (if any) to get base datums */
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo, old_tts, estate,
+						   old_values, old_isnull);
+
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo, new_tts, estate,
+						   new_values, new_isnull);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			/* Compare the index key datums */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				AttrNumber	attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				bool		values_equal;
+
+				/*
+				 * Skip attributes that we've already identified as triggering
+				 * an index update.
+				 */
+				if (attrnum > 0 &&
+					bms_is_member(attrnum - FirstLowInvalidHeapAttributeNumber, result_attrs))
+					continue;
+
+				/* A change to/from NULL, record this attribute */
+				if (old_isnull[j] != new_isnull[j])
+				{
+					if (attrnum == 0)
+						result_attrs = bms_add_members(result_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						result_attrs = bms_add_member(result_attrs, attrnum - FirstLowInvalidHeapAttributeNumber);
+					continue;
+				}
+				/* Both NULL, no change */
+				if (old_isnull[j])
+					continue;
+
+				/*
+				 * Use index AM's comparison function if present when
+				 * comparing the datum formed when creating an index key. This
+				 * is different from the comparison of datum in the tuple
+				 * destined for a storage AM, which is why we only need to use
+				 * this here.
+				 */
+				if (has_am_compare)
+				{
+					/*
+					 * For nbtree to properly deduplicate TIDs on page split
+					 * it must treat equality as binary comparison.  So it is
+					 * vital that we call it's comparedatums() function.
+					 *
+					 * In the case of GIN/RUM indexes they too behave
+					 * differently and can even extract one or more portions
+					 * of the datum when forming index tuples.  We'd like to
+					 * know if this update needs to trigger one or more index
+					 * tuples, so we let the index AM perform their extraction
+					 * and compare the results.
+					 *
+					 * There may be other similar index AM implementation with
+					 * extraction where indexes are built using only part(s)
+					 * of the Datum and might even need to invoke
+					 * type-specific equality operators.
+					 *
+					 * NOTE: For AM comparison, pass the 1-based index
+					 * attribute number. The AM's compare function expects the
+					 * same numbering as used internally by the AM.
+					 */
+					values_equal = amroutine->amcomparedatums(indexRel, j + 1,
+															  old_values[j], old_isnull[j],
+															  new_values[j], new_isnull[j]);
+				}
+				else
+				{
+					/*
+					 * Expression index without custom AM comparison. Compare
+					 * the expression results using type-specific equality via
+					 * the TypeCache.
+					 */
+					Form_pg_attribute attr = TupleDescAttr(indexdesc, j);
+
+					values_equal = tts_attr_equal(attr->atttypid,
+												  attr->attcollation,
+												  attr->attbyval,
+												  attr->attlen,
+												  old_values[j],
+												  new_values[j]);
+				}
+
+				if (!values_equal)
+				{
+					if (attrnum == 0)
+						result_attrs = bms_add_members(result_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						result_attrs = bms_add_member(result_attrs, attrnum - FirstLowInvalidHeapAttributeNumber);
+				}
+			}
+		}
+		else
+		{
+			/*
+			 * Here we know that we're reviewing an index that doesn't have a
+			 * partial predicate, isn't an expression index, and doesn't have
+			 * a amcomparedatums() implementation.  Attributes that overlap
+			 * with those known to have changed are the ones we need to
+			 * record.
+			 */
+
+			/*
+			 * NOTE: we intentionally copy via intersect and then free rather
+			 * than modify on the set we're pointing at in IndexInfo.
+			 */
+			idx_attrs = bms_intersect(idx_attrs, mix_attrs);
+			result_attrs = bms_add_members(result_attrs, idx_attrs);
+			bms_free(idx_attrs);
+		}
+	}
+
+	return result_attrs;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4d1cf50e369..191748cdce8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -54,10 +54,12 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -75,6 +77,7 @@
 #include "utils/float.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -2395,6 +2398,12 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* the set of modified indexed attributes */
+	Bitmapset  *mix_attrs = NULL;
+
+	/* the set of index attributes that trigger new index datum */
+	Bitmapset  *iru_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2517,13 +2526,48 @@ lreplace:
 	bms_free(resultRelInfo->ri_ChangedIndexedCols);
 	resultRelInfo->ri_ChangedIndexedCols = NULL;
 
-	resultRelInfo->ri_ChangedIndexedCols =
-		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+	/*
+	 * At this point we know the set of attributes specified in the UPDATE
+	 * statement and those referenced by triggers, so we have a complete view
+	 * of the UPDATE attributes on the table.  We could get this set via the
+	 * ExecGetUpdatedCols() function, but we'll need to review all indexed
+	 * attributes because extensions could just directly heap_modify_tuple()
+	 * an attribute not known to ExecGetUpdatedCols().
+	 *
+	 * We want to know which, if any, attributes that are referenced by an
+	 * index have changed value.  This set of attributes will dictate the
+	 * minimum number of indexes we need to update.
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
 
 	/*
-	 * replace the heap tuple
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * The next step is to identify which indexes, at a minimum, require new
+	 * index tuples.  You might think that you could simply intersect the
+	 * index key attributes with the modified attributes and be done, but then
+	 * you'd have missed a few cases (expressions, partial, indexes with
+	 * operators that index only a portion of a datum or many different
+	 * portions of it).
+	 */
+	iru_attrs = ExecWhichIndexesRequireUpdates(resultRelInfo, mix_attrs, estate,
+											   oldSlot, slot);
+
+	/*
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2535,9 +2579,14 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								resultRelInfo->ri_ChangedIndexedCols,
+								iru_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = iru_attrs;
+
+	bms_free(mix_attrs);
+
 	return result;
 }
 
@@ -2555,7 +2604,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index d69dc090aa4..e9a53b95caf 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -855,10 +855,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 63dd41c1f21..9bdf73eda59 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 13ea91922ef..2f265f4816c 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 41d541aa6b2..59db389a546 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -326,7 +326,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -361,8 +361,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 16be5c7a9c1..42bd329eaad 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1210,6 +1210,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 8a5931a3118..2b9206ff24a 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,7 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 Bitmapset *updated_cols,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1503,12 +1503,12 @@ 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,
-				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
 										 wait, tmfd, lockmode,
-										 updated_cols, update_indexes);
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2011,7 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  Bitmapset *modified_indexe_attrs,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index dda95e54903..8d364f8b30f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 993dc0e6ced..dda48f17605 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 898368fb8cb..d8e88817206 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -494,6 +508,11 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
 	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
diff --git a/src/test/regress/expected/hot_expression_indexes.out b/src/test/regress/expected/hot_expression_indexes.out
new file mode 100644
index 00000000000..d3eb07742c6
--- /dev/null
+++ b/src/test/regress/expected/hot_expression_indexes.out
@@ -0,0 +1,644 @@
+-- ================================================================
+-- Test Suite for HOT Updates with Expression and Partial Indexes
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             5 |           2 |                 40.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->>'status'))
+    WHERE (docs->>'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           2 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- BRIN Index
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN tracks min/max per block range
+CREATE INDEX t_brin_idx ON t USING brin(value);
+INSERT INTO t VALUES (1, 100, 'initial');
+-- Update non-indexed column - BRIN doesn't care, should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column but stay in same range - should still be HOT
+-- (Note: BRIN tracks ranges, not exact values)
+UPDATE t SET value = 105 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Change to significantly different value - still HOT for single row
+-- (BRIN summary won't change for single-row updates in same block)
+UPDATE t SET value = 1000 WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->>'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression with COLLATION
+-- ================================================================
+CREATE TABLE t(id INT, name TEXT COLLATE "C")
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_lower_idx ON t(lower(name));
+INSERT INTO t VALUES (1, 'ALICE');
+-- Change case but not lowercase value - should be HOT
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change lowercase value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Nested JSONB Expression
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->>'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->>'status'))
+    WHERE (data->>'priority')::int > 5
+      AND (data->>'active')::boolean = true;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT update
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: Still 1 HOT
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+ count 
+-------
+     0
+(1 row)
+
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..4459625a59b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: hot_expression_indexes
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/hot_expression_indexes.sql b/src/test/regress/sql/hot_expression_indexes.sql
new file mode 100644
index 00000000000..5dcadbde465
--- /dev/null
+++ b/src/test/regress/sql/hot_expression_indexes.sql
@@ -0,0 +1,491 @@
+-- ================================================================
+-- Test Suite for HOT Updates with Expression and Partial Indexes
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->>'status'))
+    WHERE (docs->>'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- BRIN Index
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN tracks min/max per block range
+CREATE INDEX t_brin_idx ON t USING brin(value);
+INSERT INTO t VALUES (1, 100, 'initial');
+
+-- Update non-indexed column - BRIN doesn't care, should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed column but stay in same range - should still be HOT
+-- (Note: BRIN tracks ranges, not exact values)
+UPDATE t SET value = 105 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+-- Change to significantly different value - still HOT for single row
+-- (BRIN summary won't change for single-row updates in same block)
+UPDATE t SET value = 1000 WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->>'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression with COLLATION
+-- ================================================================
+CREATE TABLE t(id INT, name TEXT COLLATE "C")
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_lower_idx ON t(lower(name));
+INSERT INTO t VALUES (1, 'ALICE');
+
+-- Change case but not lowercase value - should be HOT
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change lowercase value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Nested JSONB Expression
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->>'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->>'status'))
+    WHERE (data->>'priority')::int > 5
+      AND (data->>'active')::boolean = true;
+
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT update
+
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: Still 1 HOT
+
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 23bce72ae64..52ef8f10b35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -390,6 +390,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.49.0

#34Greg Burd
greg@burd.me
In reply to: Greg Burd (#33)
4 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

On Nov 16 2025, at 1:53 pm, Greg Burd <greg@burd.me> wrote:

0004 - Enable HOT updates for expression and partial indexes

This finally gets us back to where this project started, but on much
more firm ground than before because we're not going to self-deadlock.
The idea has grown from a small function into something larger, but only
out of necessity.

In this patch I add ExecWhichIndexesRequireUpdates() in execIndexing.c
which implements (c) finding the set of attributes that force new index
updates. This set can be very different from the modified indexed
attributes. We know that some attributes are not equal to their
previous versions, but does that mean that the index that references
that attribute needs a new index tuple? It may, or it may not. Here's
the comment on that function that explains:

/*
* ExecWhichIndexesRequireUpdates
*
* Determine which indexes need updating given modified indexed attributes.
* This function is a companion to ExecCheckIndexedAttrsForChanges().
On the
* surface, they appear similar but they are doing two very different things.
*
* For a standard index on a set of attributes this is the intersection of
* the mix_attrs and the index attrs (key, expression, but not predicate).
*
* For expression indexes and indexes which implement the amcomparedatums()
* index AM API we'll need to form index datum and compare each
attribute to
* see if any actually changed.
*
* For expression indexes the result of the expression might not change
at all,
* this is common with JSONB columns which require expression indexes
and where
* it is commonplace to index a field within a document and have
updates that
* generally don't update that field.
*
* Partial indexes won't trigger index tuples when the old/new tuples
are both
* outside of the predicate range.
*
* For nbtree the amcomparedatums() API is critical as it requires that key
* attributes are equal when they memcmp(), which might not be the case when
* using type-specific comparison or factoring in collation which might make
* an index case insensitive.
*
* All of this is to say that the goal is for the executor to know,
ahead of
* calling into the table AM for the update and before calling into the index
* AM for inserting new index tuples, which attributes at a minimum will
* necessitate a new index tuple.
*
...
*/

Attached are rebased (d5b4f3a6d4e) patches with the only changes
happening in the last patch in the series.

0004 - Enable HOT updates for expression and partial indexes

I was never happy with the dual functions
ExecCheckIndexedAttrsForChanges() and ExecWhichIndexesRequireUpdates(),
it felt like too much overhead and duplication of effort. While
updating my tests, adding a few cases, I found that there was also a
flaw in the logic. So, time to rewrite and combine them.

What did I discover? Before the logic was to find the set of modified
indexed attributes then review all the indexes for changed attributes
using FormIndexDatum() and comparing before/after to see if expressions
really changed the value to be indexed or not. The first pass didn't
take into account expressions, the second did. So, an expression index
over JSONB data wouldn't extract and test the field within the document,
it was just comparing the entire document before/after using the jsonb
comparison function, no bueno.

This approach wraps both functions into one somewhat simplified
function. The logic is basically, iterate over the indexes reviewing
indexed attributes for changes. Along the way we call into the new
index AM's comparison function when present, otherwise we find and use
the proper type-specific comparison function for the datum. At the end
of the function we have our Bitmapset of attributes that should trigger
new index tuples.

What's left undone?

* I need to check code coverage so that I might

I did this and it was quite good, I'll do it again for this new series
but it's nice to see that the tests are exercising the vast majority of
the code paths.

* create tests covering all the new cases

I think the coverage is good, maybe even redundant or overly complex in places.

* update the README.HOT documentation, wiki, etc.

Soon, I hope to have this approach solid and under review before
solidifying the docs.

* performance...

Still as yet unmeasured, I know that there is more work per-update to
perform these checks, so some overhead, but I don't know if that
overhead is more than before with HeapDetermineColumnsInfo() and
index_unchanged_by_update(). Those two functions did essentially the
same thing, only with binary comparison (datumIsEqual()). I need to
measure that. What about doing all this work outside of the buffer lock
in heap_update()? Surely that'll give back a bit or at least add to
concurrency. Forming index tuples a few extra times and evaluating the
expressions 3 times rather than 1 is going to hurt, I think I can come
up with a way to cache the formed datum and use it later on, but is that
worth it? Complex expressions, yes. Also, what about expressions that
expect to be executed once... and now are 3x? That's what forced my
update to the insert-conflict-specconflict.out test, but AFAICT there is
no way to test if an expression's value is going to change on update
without exercising it once for the old tuple and once for the new tuple.
Even if it were possible for an index to provide the key it might have
changed after the expression evaluation (as is the case in hash), so I
don't think this is avoidable. Maybe that's reason enough to add a
reloption to disable the expression evaluation piece of this? Given
that it might create a logic or performance regression. The flip side
is the potential to use the HOT path, that's a real savings.

One concerning thing is that nbtree's assumption that key attributes for
TIDs must use binary comparison for equality. This means that for our
common case (heap/btree) there is more work per-update than before,
which is why I need to measure. I could look into eliminating the
nbtree requirement, I don't understand it too well as yet by I believe
that on page split there is an attempt to deduplicate TIDs into a
TIDBitmap and the test for when that's possible is datumIsEqual(). If
that were the same as in this new code, possibly evening using
tts_attr_equal(), then... I don't know, I'll have to investigate. Chime
in here if you can educate me on this one. :)

best.

-greg

Attachments:

v21-0001-Reorganize-heap-update-logic.patchapplication/octet-streamDownload
From 0de059ae17042a76594610e1b7b35dbb2db9415c Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v21 1/4] Reorganize heap update logic

This commit refactors the interaction between heap_tuple_update(),
heap_update(), and simple_heap_update() to improve code organization
and flexibility. The changes are functionally equivalent to the
previous implementation and have no performance impact.

The primary motivation is to prepare for upcoming modifications to
how and where modified attributes are identified during the update
path, particularly for catalog updates.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().

No user-visible changes.
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4b0c49f4bb0..aff47481345 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -103,10 +101,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2799,6 +2797,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2811,6 +2810,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3014,6 +3015,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3035,7 +3037,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3247,7 +3252,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3255,30 +3263,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3292,7 +3291,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3300,144 +3298,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3449,7 +3316,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3473,17 +3340,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3515,8 +3375,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3556,13 +3416,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3572,9 +3431,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3599,8 +3458,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3641,9 +3500,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3653,20 +3512,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3679,39 +3538,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3724,10 +3577,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3738,9 +3591,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3752,12 +3605,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3772,7 +3625,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3800,7 +3653,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3817,12 +3670,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3855,9 +3708,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3867,18 +3720,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3887,7 +3740,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3901,10 +3754,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3926,7 +3779,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3962,20 +3815,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4013,7 +3866,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4021,7 +3874,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4029,7 +3881,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4040,7 +3892,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4057,10 +3909,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4082,7 +3932,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4091,7 +3941,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4100,17 +3950,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4118,7 +3968,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4143,12 +3993,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4173,7 +4023,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4181,14 +4031,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4221,13 +4071,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4236,7 +4079,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4408,7 +4251,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4491,25 +4334,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9149,12 +9142,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9185,17 +9177,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9208,7 +9196,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9217,8 +9205,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..1cf9a18775d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 909db73b7bb..41d541aa6b2 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -321,11 +321,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -391,6 +393,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.49.0

v21-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From edc170a3f61de2141b383134ae40f105ee90aebe Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v21 2/4] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo in heap_update. This enables the comparison
to happen without taking a lock on the page and opens the door to reuse
in other code paths.

Because heap_update now requires the caller to provide the modified
indexed columns simple_heap_update has become a tad more complex.  It is
frequently called from CatalogTupleUpdate which either updates heap
tuples via their form or using heap_modify_tuple.  In both cases the
caller does know the modified set of attributes, but sadly those
attributes are lost before being provided to simple_heap_update.  Due to
that the "simple" path has to retain the HeapDetermineColumnsInfo logic
of old (for now).  In order for that to work it was necessary to split
the (overly large) heap_update call itself up.  This moves up into
simple_heap_update and heap_tuple_update a bit of what existed in
heap_update itself.  Ideally this will be cleaned up once
CatalogTupleUpdate paths are all recording modified attributes
correctly, when that happens the "simple" path can be simplified again.

ExecCheckIndexedAttrsForChanges replaces HeapDeterminesColumnsInfo and
tts_attr_equal replaces heap_attr_equal changing the test for equality
when calling into heap_tuple_update (but not simple_heap_update).  In
the past we used datumIsEqual(), essentially a binary comparison using
memcmp(), now the comparison code in tts_attr_equal uses type-specific
equality function when available and falls back to datumIsEqual() when
not.  This change in equality testing has some intended implications and
opens the door for more HOT updates (foreshadowing).  For instance,
indexes with collation information allowing more HOT updates when the
index is specified to be case insensitive.

This change forced some logic changes in execReplication on the update
paths is now it is required to have knowledge of the set of attributes
that are both changed and referenced by indexes.  Luckilly, the this is
available within calls to slot_modify_data() where LogicalRepTupleData
is processed and has a set of updated attributes.  In this case rather
than using ExecCheckIndexedAttrsForChanges we can preseve what
slot_modify_data() identifies as the modified set and then intersect
that with the set of indexes on the relation and get the correct set of
modified indexed attributes required on heap_update().
---
 src/backend/access/heap/heapam.c         |  12 +-
 src/backend/access/heap/heapam_handler.c |  72 +++++--
 src/backend/access/table/tableam.c       |   5 +-
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/execReplication.c   |   7 +
 src/backend/executor/nodeModifyTable.c   | 247 ++++++++++++++++++++++-
 src/backend/nodes/bitmapset.c            |   4 +
 src/backend/replication/logical/worker.c |  72 ++++++-
 src/backend/utils/cache/relcache.c       |  15 ++
 src/include/access/tableam.h             |   8 +-
 src/include/executor/executor.h          |   5 +
 src/include/nodes/execnodes.h            |   1 +
 src/include/utils/rel.h                  |   1 +
 src/include/utils/relcache.h             |   1 +
 14 files changed, 415 insertions(+), 36 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index aff47481345..1cdb72b3a7a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3263,12 +3263,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1cf9a18775d..ef08e1d3e10 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -414,16 +416,61 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +484,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 5e41404937e..dadcf03ed24 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,6 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  Bitmapset *modified_indexed_cols,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -346,7 +347,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								modified_indexed_cols,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 27c9eec697b..6b7b6bc8019 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index def32774c90..2709e2db0f2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 00429326c34..34f86546fc9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -54,11 +55,14 @@
 
 #include "access/htup_details.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,6 +72,8 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
@@ -176,6 +182,219 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   bool canSetTag);
 
 
+/*
+ * Compare two datums using the type's default equality operator.
+ *
+ * Returns true if the values are equal according to the type's equality
+ * operator, false otherwise. Falls back to binary comparison if no
+ * type-specific operator is available.
+ *
+ * This function uses the TypeCache infrastructure which caches operator
+ * lookups for efficiency.
+ */
+bool
+tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+			   Datum value1, Datum value2)
+{
+	TypeCacheEntry *typentry;
+
+	LOCAL_FCINFO(fcinfo, 2);
+	Datum		result;
+
+	/*
+	 * Fast path for common types to avoid even the type cache lookup. These
+	 * types have simple equality semantics.
+	 */
+	switch (typid)
+	{
+		case INT2OID:
+			return DatumGetInt16(value1) == DatumGetInt16(value2);
+		case INT4OID:
+			return DatumGetInt32(value1) == DatumGetInt32(value2);
+		case INT8OID:
+			return DatumGetInt64(value1) == DatumGetInt64(value2);
+		case FLOAT4OID:
+			return !float4_cmp_internal(DatumGetFloat4(value1), DatumGetFloat4(value2));
+		case FLOAT8OID:
+			return !float8_cmp_internal(DatumGetFloat8(value1), DatumGetFloat8(value2));
+		case BOOLOID:
+			return DatumGetBool(value1) == DatumGetBool(value2);
+		case OIDOID:
+		case REGPROCOID:
+		case REGPROCEDUREOID:
+		case REGOPEROID:
+		case REGOPERATOROID:
+		case REGCLASSOID:
+		case REGTYPEOID:
+		case REGROLEOID:
+		case REGNAMESPACEOID:
+		case REGCONFIGOID:
+		case REGDICTIONARYOID:
+			return DatumGetObjectId(value1) == DatumGetObjectId(value2);
+		case CHAROID:
+			return DatumGetChar(value1) == DatumGetChar(value2);
+		default:
+			/* Continue to type cache lookup */
+			break;
+	}
+
+	/*
+	 * Look up the type's equality operator using the type cache. Request both
+	 * the operator OID and the function info for efficiency.
+	 */
+	typentry = lookup_type_cache(typid,
+								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
+
+	/*
+	 * If no equality operator is available, fall back to binary comparison.
+	 * This handles types that don't have proper equality operators defined.
+	 */
+	if (!OidIsValid(typentry->eq_opr))
+		return datumIsEqual(value1, value2, typbyval, typlen);
+
+	/*
+	 * Use the cached function info if available, otherwise look it up. The
+	 * type cache keeps this around so subsequent calls are fast.
+	 */
+	if (typentry->eq_opr_finfo.fn_addr == NULL)
+	{
+		Oid			eq_proc = get_opcode(typentry->eq_opr);
+
+		if (!OidIsValid(eq_proc))
+			/* Shouldn't happen, but fall back to binary comparison */
+			return datumIsEqual(value1, value2, typbyval, typlen);
+
+		fmgr_info_cxt(eq_proc, &typentry->eq_opr_finfo,
+					  CacheMemoryContext);
+	}
+
+	/* Set up function call */
+	InitFunctionCallInfoData(*fcinfo, &typentry->eq_opr_finfo, 2,
+							 collation, NULL, NULL);
+
+	fcinfo->args[0].value = value1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = value2;
+	fcinfo->args[1].isnull = false;
+
+	/* Invoke the equality operator */
+	result = FunctionCallInvoke(fcinfo);
+
+	/*
+	 * If the function returned NULL (shouldn't happen for equality ops),
+	 * treat as not equal for safety.
+	 */
+	if (fcinfo->isnull)
+		return false;
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Determine which updated attributes actually changed values between old and
+ * new tuples and are referenced by indexes on the relation.
+ *
+ * Returns a Bitmapset of attribute offsets (0-based, adjusted by
+ * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								TupleTableSlot *tts_old,
+								TupleTableSlot *tts_new)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *indexed_attrs;
+	Bitmapset  *modified = NULL;
+	int			attidx;
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/*
+	 * Get the set of index key attributes.  This includes summarizing,
+	 * expression indexes and attributes mentioned in the predicate of a
+	 * partition but not those in INCLUDING.
+	 */
+	indexed_attrs = RelationGetIndexAttrBitmap(relation,
+											   INDEX_ATTR_BITMAP_INDEXED);
+	Assert(!bms_is_empty(indexed_attrs));
+
+	/*
+	 * NOTE: It is important to scan all indexed attributes in the tuples
+	 * because ExecGetAllUpdatedCols won't include columns that may have been
+	 * modified via heap_modify_tuple_by_col which is the case in
+	 * tsvector_update_trigger.
+	 */
+	attidx = -1;
+	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	{
+		/* attidx is zero-based, attrnum is the normal attribute number */
+		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+		Form_pg_attribute attr;
+		bool		oldnull,
+					newnull;
+		Datum		oldval,
+					newval;
+
+		/*
+		 * If it's a whole-tuple reference, record as modified.  It's not
+		 * really worth supporting this case, since it could only succeed
+		 * after a no-op update, which is hardly a case worth optimizing for.
+		 */
+		if (attrnum == 0)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/*
+		 * Likewise, include in the modified set any system attribute other
+		 * than tableOID; we cannot expect these to be consistent in a HOT
+		 * chain, or even to be set correctly yet in the new tuple.
+		 */
+		if (attrnum < 0)
+		{
+			if (attrnum != TableOidAttributeNumber)
+				modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* Extract values from both slots */
+		oldval = slot_getattr(tts_old, attrnum, &oldnull);
+		newval = slot_getattr(tts_new, attrnum, &newnull);
+
+		/* If one value is NULL and the other is not, they are not equal */
+		if (oldnull != newnull)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* If both are NULL, consider them equal */
+		if (oldnull)
+			continue;
+
+		/* Get attribute metadata */
+		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
+		attr = TupleDescAttr(tupdesc, attrnum - 1);
+
+		/* Compare using type-specific equality operator */
+		if (!tts_attr_equal(attr->atttypid,
+							attr->attcollation,
+							attr->attbyval,
+							attr->attlen,
+							oldval,
+							newval))
+			modified = bms_add_member(modified, attidx);
+	}
+
+	bms_free(indexed_attrs);
+
+	return modified;
+}
+
 /*
  * Verify that the tuples to be produced by INSERT match the
  * target relation's rowtype
@@ -2168,8 +2387,8 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2291,6 +2510,16 @@ lreplace:
 	if (resultRelationDesc->rd_att->constr)
 		ExecConstraints(resultRelInfo, slot, estate);
 
+	/*
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	resultRelInfo->ri_ChangedIndexedCols =
+		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+
 	/*
 	 * replace the heap tuple
 	 *
@@ -2306,6 +2535,7 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								resultRelInfo->ri_ChangedIndexedCols,
 								&updateCxt->updateIndexes);
 
 	return result;
@@ -2524,8 +2754,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3222,8 +3453,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3248,6 +3479,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4354,7 +4586,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4530,6 +4762,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index b4ecf0b0390..9014990267a 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 93970c6af29..b363eaa49cc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -243,6 +243,8 @@
  */
 
 #include "postgres.h"
+#include "access/sysattr.h"
+#include "nodes/bitmapset.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
@@ -275,7 +277,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -291,6 +292,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1112,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1200,28 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+				if (!tts_attr_equal(att->atttypid,
+									att->attcollation,
+									att->attbyval,
+									att->attlen,
+									srcslot->tts_values[i],
+									slot->tts_values[i]))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1229,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2947,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2964,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2989,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3501,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3571,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3597,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..32825596be1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2482,6 +2482,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5284,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5307,6 +5309,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5332,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5373,6 +5378,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5505,10 +5511,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5531,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5535,6 +5547,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5551,6 +5564,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index e16bf025692..8a5931a3118 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1502,12 +1503,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 updated_cols, update_indexes);
 }
 
 /*
@@ -2010,6 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  Bitmapset *modified_indexe_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..993dc0e6ced 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -800,5 +800,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
+												  TupleTableSlot *tts_old,
+												  TupleTableSlot *tts_new);
+extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+						   Datum value1, Datum value2);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..8b08e0045ba 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -498,6 +498,7 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 80286076a11..b23a7306e69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..d3fbb8b093a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
-- 
2.49.0

v21-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From cf41827dd2ed13e5fa02763bddec570125af621e Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v21 3/4] Replace index_unchanged_by_update with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update with the same information
found earlier in the update path.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 874a8fc89ad..5d819bda54a 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -300,8 +300,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 401606f840a..fb1bc3a480d 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -138,11 +138,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -440,10 +435,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -993,152 +985,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..d69dc090aa4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8b08e0045ba..898368fb8cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -202,10 +202,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.49.0

v21-0004-Enable-HOT-updates-for-expression-and-partial-in.patchapplication/octet-streamDownload
From 632179fcd6802348e9424bae4b419d13feb24c0f Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v21 4/4] Enable HOT updates for expression and partial indexes

Currently, PostgreSQL conservatively prevents HOT (Heap-Only Tuple)
updates whenever any indexed column changes, even if the indexed
portion of that column remains identical. This is overly restrictive
for expression indexes (where f(column) might not change even when
column changes) and partial indexes (where both old and new tuples
might fall outside the predicate).  Finally, index AMs play no role
in deciding when they need a new index entry on update, the rules
regarding that are based on binary equality and the HEAP's model for
MVCC and related HOT optimization.  Here we open that door a bit so
as to enable more nuanced control over the process.  This enables
index AMs that require binary equality (as is the case for nbtree)
to do that without disallowing type-specific equality checking for
other indexes.

This patch introduces several improvements to enable HOT updates in
these cases:

Add amcomparedatums() callback to IndexAmRoutine. This allows index
access methods like GIN to provide custom logic for comparing datums by
extracting and comparing index keys rather than comparing the raw
datums. GIN indexes now implement gincomparedatums() which extracts keys
from both datums and compares the resulting key sets.  Also, as
mentioned earlier nbtree implements this API and uses datumIsEqual() for
equality so that the manner in which it deduplicates TIDs on page split
doesn't have to change.  This is not a required API, when not
implemented the executor will compare TupleTableSlot datum for equality
using type-specific operators and take into account collation so that an
update from "Apple" to "APPLE" on a case insensitive index can now be
HOT.

ExecWhichIndexesRequireUpdates() is re-written to find the set of
modified indexed attributes that trigger new index tuples on updated.
For partial indexes, this checks whether both old and new tuples satisfy
or fail the predicate. For expression indexes, this uses type-specific
equality operators to compare computed values. For extraction-based
indexes (GIN/RUM) that implement amcomparedatums() it uses that.

Importantly, table access methods can still signal using TU_Update if
all, none, or only summarizing indexes should be updated.  While the
executor layer now owns determining what has changed due to an update
and is interested in only updating the minimum number of indexes
possible, the table AM can override that while performing
table_tuple_update(), which is what heap does.  While this signal is
very specific to how the heap implements MVCC and its HOT optimization,
we'll leave replacing that for another day.

This optimization trades off some new overhead for the potential for
more updates to use the HOT optimized path and avoid index and heap
bloat.  This should significantly improve update performance for tables
with expression indexes, partial indexes, and GIN/GiST indexes on
complex data types like JSONB and tsvector, while maintaining correct
index semantics.  Minimal additional overhead due to type-specific
equality checking should be washed out by the benefits of updating
indexes fewer times.

One notable trade-off is that there are more calls to FormIndexDatum()
as a result.  Caching these might reduce some of that overhead, but not
all.  This lead to the change in the frequency for expressions in the
spec update test to output notice messages, but does not impact
correctness.
---
 src/backend/access/brin/brin.c                |    1 +
 src/backend/access/gin/ginutil.c              |   94 +-
 src/backend/access/heap/heapam.c              |   10 +-
 src/backend/access/heap/heapam_handler.c      |    6 +-
 src/backend/access/nbtree/nbtree.c            |   38 +
 src/backend/access/table/tableam.c            |    4 +-
 src/backend/bootstrap/bootstrap.c             |    8 +
 src/backend/catalog/index.c                   |   57 +
 src/backend/catalog/indexing.c                |   16 +-
 src/backend/catalog/toasting.c                |    4 +
 src/backend/executor/execIndexing.c           |   45 +-
 src/backend/executor/nodeModifyTable.c        |  437 +++++--
 src/backend/nodes/makefuncs.c                 |    4 +
 src/include/access/amapi.h                    |   28 +
 src/include/access/gin.h                      |    3 +
 src/include/access/heapam.h                   |    6 +-
 src/include/access/nbtree.h                   |    4 +
 src/include/access/tableam.h                  |    8 +-
 src/include/catalog/index.h                   |    1 +
 src/include/executor/executor.h               |   12 +-
 src/include/nodes/execnodes.h                 |   19 +
 .../expected/insert-conflict-specconflict.out |   20 +
 .../expected/hot_expression_indexes.out       | 1007 +++++++++++++++++
 src/test/regress/parallel_schedule            |    6 +
 .../regress/sql/hot_expression_indexes.sql    |  747 ++++++++++++
 src/tools/pgindent/typedefs.list              |    1 +
 26 files changed, 2461 insertions(+), 125 deletions(-)
 create mode 100644 src/test/regress/expected/hot_expression_indexes.out
 create mode 100644 src/test/regress/sql/hot_expression_indexes.sql

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index cb3331921cb..36e639552e6 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -290,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = NULL;
 	amroutine->ambeginscan = brinbeginscan;
 	amroutine->amrescan = brinrescan;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 78f7b7a2495..85e25ed73e8 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = ginbuildphasename;
 	amroutine->amvalidate = ginvalidate;
+	amroutine->amcomparedatums = gincomparedatums;
 	amroutine->amadjustmembers = ginadjustmembers;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -729,3 +724,88 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare two datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/*
+	 * Extract keys from both datums using existing GIN infrastructure.
+	 */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys → definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		int			cmp = ginCompareEntries(&ginstate, attnum,
+											old_keys[i], old_categories[i],
+											new_keys[i], new_categories[i]);
+
+		if (cmp != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1cdb72b3a7a..5b0ff13b13d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3268,7 +3268,7 @@ heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			Buffer buffer, Page page, BlockNumber block, ItemId lp,
 			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
-			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4337,8 +4337,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4467,7 +4468,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4500,7 +4501,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4526,6 +4526,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ef08e1d3e10..7527809ec08 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -319,7 +319,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 					Snapshot crosscheck, bool wait,
 					TM_FailureData *tmfd,
 					LockTupleMode *lockmode,
-					Bitmapset *mix_attrs,
+					const Bitmapset *mix_attrs,
 					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
@@ -407,10 +407,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fdff960c130..73cc3208757 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -155,6 +155,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
+	amroutine->amcomparedatums = btcomparedatums;
 	amroutine->amadjustmembers = btadjustmembers;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
@@ -1795,3 +1796,40 @@ bttranslatecmptype(CompareType cmptype, Oid opfamily)
 			return InvalidStrategy;
 	}
 }
+
+/*
+ * btcomparedatums - Compare two datums for equality
+ *
+ * This function is necessary because nbtree requires that keys that are not
+ * binary identical not be "equal".  Other indexes might allow "A" and "a" to
+ * be "equal" when collation is case insensative, but not nbtree.  Why?  Well,
+ * nbtree deduplicates TIDs on page split and the way it accomplish that is by
+ * doing a binary comparison of the keys.
+ */
+
+bool
+btcomparedatums(Relation index, int attrnum,
+				Datum old_datum, bool old_isnull,
+				Datum new_datum, bool new_isnull)
+{
+	TupleDesc	desc = RelationGetDescr(index);
+	CompactAttribute *att;
+
+	/*
+	 * If one value is NULL and other is not, then they are certainly not
+	 * equal
+	 */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * If both are NULL, they can be considered equal.
+	 */
+	if (old_isnull)
+		return true;
+
+	/* We do simple binary comparison of the two datums */
+	Assert(attrnum <= desc->natts);
+	att = TupleDescCompactAttr(desc, attrnum - 1);
+	return datumIsEqual(old_datum, new_datum, att->attbyval, att->attlen);
+}
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dadcf03ed24..ef7736bfa76 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,7 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  Bitmapset *modified_indexed_cols,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -348,7 +348,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode,
-								modified_indexed_cols,
+								mix_attrs,
 								update_indexes);
 
 	switch (result)
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index fc8638c1b61..329c110d0bf 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -961,10 +961,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..29b8cc4badd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2414,6 +2416,61 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnkeyatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		/*
+		 * Expressions are not allowed on non-key attributes, so we can skip
+		 * them as they should show up in the index HOT-blocking attributes.
+		 */
+		indnkeyatts = ii->ii_NumIndexKeyAttrs;
+
+		/* Collect key attributes used by the index */
+		for (i = 0; i < indnkeyatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..a361c215490 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 5d819bda54a..c665aa744b3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index fb1bc3a480d..20968a814d6 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
@@ -318,8 +322,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -343,13 +347,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -366,7 +370,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -387,8 +391,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -396,11 +407,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -435,7 +445,9 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
+		indexUnchanged = update &&
+			!bms_overlap(indexInfo->ii_IndexedAttrs,
+						 resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -604,7 +616,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 34f86546fc9..e4b2cd5a3e8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -54,10 +54,13 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/attnum.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -75,6 +78,7 @@
 #include "utils/float.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -245,6 +249,10 @@ tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 	typentry = lookup_type_cache(typid,
 								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
 
+	/* Use the type's collation if none provided */
+	if (collation == -1)
+		collation = typentry->typcollation;
+
 	/*
 	 * If no equality operator is available, fall back to binary comparison.
 	 * This handles types that don't have proper equality operators defined.
@@ -291,108 +299,356 @@ tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 }
 
 /*
- * Determine which updated attributes actually changed values between old and
- * new tuples and are referenced by indexes on the relation.
+ * ExecCheckIndexedAttrsForChanges
+ *
+ * Determine which indexes need updating by finding the set of modified
+ * indexed attributes.
+ *
+ * For expression indexes and indexes which implement the amcomparedatums()
+ * index AM API we'll need to form index datum and compare each attribute to
+ * see if any actually changed.
+ *
+ * For expression indexes the result of the expression might not change at all,
+ * this is common with JSONB columns which require expression indexes and where
+ * it is commonplace to index a field within a document and have updates that
+ * generally don't update that field.
+ *
+ * Partial indexes won't trigger index tuples when the old/new tuples are both
+ * outside of the predicate range.
+ *
+ * All other indexes require testing old/new datum for equality.  We do this
+ * by calling the type-specific equality operator when possible, otherwise we
+ * fall back to binary equality with datumIsEqual().
+ *
+ * For nbtree the amcomparedatums() API is critical as it requires that key
+ * attributes are equal when they memcmp(), which might not be the case when
+ * using type-specific comparison or factoring in collation which might make
+ * an index case insensitive.
  *
- * Returns a Bitmapset of attribute offsets (0-based, adjusted by
- * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ * All of this is to say that the goal is for the executor to know, ahead of
+ * calling into the table AM to process the update and before calling into the
+ * index AM for inserting new index tuples, which attributes truely necessitate
+ * a new index tuple.
+ *
+ * Returns a refined Bitmapset of attributes that force index updates.
  */
 Bitmapset *
 ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
-								TupleTableSlot *tts_old,
-								TupleTableSlot *tts_new)
+								EState *estate,
+								TupleTableSlot *old_tts,
+								TupleTableSlot *new_tts)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(relation);
-	Bitmapset  *indexed_attrs;
-	Bitmapset  *modified = NULL;
-	int			attidx;
+	Bitmapset  *mix_attrs = NULL;
 
 	/* If no indexes, we're done */
 	if (relinfo->ri_NumIndices == 0)
 		return NULL;
 
 	/*
-	 * Get the set of index key attributes.  This includes summarizing,
-	 * expression indexes and attributes mentioned in the predicate of a
-	 * partition but not those in INCLUDING.
+	 * NOTE: Expression and predicates that are observed to change will have
+	 * all their attributes added into the m_attrs set knowing that some of
+	 * those might not have changed.  Take for instance an index on (a + b)
+	 * followed by an index on (b) with an update that changes only the value
+	 * of 'a'.  We'll add both 'a' and 'b' to the m_attrs set then later when
+	 * reviewing the second index add 'b' to the u_attrs (unchanged) set.  In
+	 * the end, we'll remove all the unchanged from the m_attrs and get our
+	 * desired result.
 	 */
-	indexed_attrs = RelationGetIndexAttrBitmap(relation,
-											   INDEX_ATTR_BITMAP_INDEXED);
-	Assert(!bms_is_empty(indexed_attrs));
 
-	/*
-	 * NOTE: It is important to scan all indexed attributes in the tuples
-	 * because ExecGetAllUpdatedCols won't include columns that may have been
-	 * modified via heap_modify_tuple_by_col which is the case in
-	 * tsvector_update_trigger.
-	 */
-	attidx = -1;
-	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	/* Find the indexes that reference this attribute */
+	for (int i = 0; i < relinfo->ri_NumIndices; i++)
 	{
-		/* attidx is zero-based, attrnum is the normal attribute number */
-		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
-		Form_pg_attribute attr;
-		bool		oldnull,
-					newnull;
-		Datum		oldval,
-					newval;
+		Relation	indexRel = relinfo->ri_IndexRelationDescs[i];
+		IndexAmRoutine *amroutine = indexRel->rd_indam;
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		Bitmapset  *m_attrs = NULL; /* (possibly) modified key attributes */
+		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attributes */
+		Bitmapset  *u_attrs = NULL; /* unmodified attributes */
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
+		bool		has_expressions = (indexInfo->ii_Expressions != NIL);
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		TupleTableSlot *save_scantuple;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		Datum		old_values[INDEX_MAX_KEYS];
+		bool		old_isnull[INDEX_MAX_KEYS];
+		Datum		new_values[INDEX_MAX_KEYS];
+		bool		new_isnull[INDEX_MAX_KEYS];
+
+		/* If we've reviewed all the attributes on this index, move on */
+		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
+			continue;
 
-		/*
-		 * If it's a whole-tuple reference, record as modified.  It's not
-		 * really worth supporting this case, since it could only succeed
-		 * after a no-op update, which is hardly a case worth optimizing for.
-		 */
-		if (attrnum == 0)
+		/* Checking partial at this point isn't viable when we're serializable */
+		if (is_partial && IsolationIsSerializable())
 		{
-			modified = bms_add_member(modified, attidx);
-			continue;
+			p_attrs = bms_copy(pre_attrs);
+		}
+		/* Check partial index predicate */
+		else if (is_partial)
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+				continue;
+
+			/* A transition means we need to update the index */
+			if (old_qualifies != new_qualifies)
+				p_attrs = bms_copy(pre_attrs);
+
+			/*
+			 * When both are within the predicate we must update this index,
+			 * but only if one of the index key attributes changed.
+			 */
 		}
 
 		/*
-		 * Likewise, include in the modified set any system attribute other
-		 * than tableOID; we cannot expect these to be consistent in a HOT
-		 * chain, or even to be set correctly yet in the new tuple.
+		 * Expression indexes or extraction-based index require us to form
+		 * index datums and compare.  We've done all we can to avoid this
+		 * overhead, now it's time to bite the bullet and get it done.
+		 *
+		 * XXX: Caching the values/isnull might be a win and avoid one of the
+		 * added calls to FormIndexDatum().
 		 */
-		if (attrnum < 0)
+		if (has_expressions || has_am_compare)
 		{
-			if (attrnum != TableOidAttributeNumber)
-				modified = bms_add_member(modified, attidx);
-			continue;
-		}
+			save_scantuple = econtext->ecxt_scantuple;
 
-		/* Extract values from both slots */
-		oldval = slot_getattr(tts_old, attrnum, &oldnull);
-		newval = slot_getattr(tts_new, attrnum, &newnull);
+			/* Evaluate expressions (if any) to get base datums */
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo, old_tts, estate, old_values, old_isnull);
 
-		/* If one value is NULL and the other is not, they are not equal */
-		if (oldnull != newnull)
-		{
-			modified = bms_add_member(modified, attidx);
-			continue;
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo, new_tts, estate, new_values, new_isnull);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			/* Compare the index key datums for equality */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				AttrNumber	idx_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				int			idx_attridx = idx_attrnum - FirstLowInvalidHeapAttributeNumber;
+				int			nth_expr = 0;
+				bool		values_equal = false;
+
+				/*
+				 * We can't skip attributes that we've already identified as
+				 * triggering an index update because we may have added an
+				 * attribute from an expression index that didn't change but
+				 * the expression did and that unchanged attribute is
+				 * referenced in a subsequent index where we will discover that
+				 * fact.
+				 */
+
+				/* A change to/from NULL, record this attribute */
+				if (old_isnull[j] != new_isnull[j])
+				{
+					/* Expressions will have idx_attrnum == 0 */
+					if (idx_attrnum == 0)
+						m_attrs = bms_add_members(m_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						m_attrs = bms_add_member(m_attrs, idx_attridx);
+					continue;
+				}
+
+				/* Both NULL, no change */
+				if (old_isnull[j])
+				{
+					if (idx_attrnum != 0)
+						u_attrs = bms_add_member(u_attrs, idx_attridx);
+
+					continue;
+				}
+
+				/*
+				 * Use index AM's comparison function if present when comparing
+				 * the index datum formed when creating an index key.
+				 */
+				if (has_am_compare)
+				{
+					/*
+					 * For nbtree to properly deduplicate TIDs on page split it
+					 * must treat equality as binary comparison.  So it is
+					 * vital that we call it's comparedatums() function.
+					 *
+					 * In the case of GIN/RUM indexes they too behave
+					 * differently and can even extract one or more portions of
+					 * the datum when forming index tuples.  We'd like to know
+					 * if this update needs to trigger one or more index
+					 * tuples, so we let the index AM perform their extraction
+					 * and compare the results.
+					 *
+					 * There may be other similar index AM implementation with
+					 * extraction where indexes are built using only part(s) of
+					 * the Datum and might even need to invoke type-specific
+					 * equality operators.
+					 *
+					 * NOTE: For AM comparison, pass the 1-based index
+					 * attribute number. The AM's compare function expects the
+					 * same numbering as used internally by the AM.
+					 */
+					values_equal = amroutine->amcomparedatums(indexRel, j + 1,
+															  old_values[j], old_isnull[j],
+															  new_values[j], new_isnull[j]);
+				}
+				else
+				{
+					/*
+					 * Expression index without custom AM comparison. Compare
+					 * the expression results using type-specific equality
+					 * which at this point is the expression's type, not the
+					 * index's type. It is in index_form_tuple() that index
+					 * attributes are transformed, not FormIndexDatum().
+					 */
+					Oid			expr_type_oid;
+					int16		typlen; /* Output: type length */
+					bool		typbyval;
+					Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+
+					Assert(expr != NULL);
+
+					/* Get type OID from the expression */
+					expr_type_oid = exprType((Node *) expr);
+
+					/* Get type information from the OID */
+					get_typlenbyval(expr_type_oid, &typlen, &typbyval);
+
+					values_equal = tts_attr_equal(expr_type_oid,
+												  -1,	/* use TBD expr type */
+												  typbyval,
+												  typlen,
+												  old_values[j],
+												  new_values[j]);
+				}
+
+				if (!values_equal)
+				{
+					/* Expressions will have idx_attrnum == 0 */
+					if (idx_attrnum == 0)
+						m_attrs = bms_add_members(m_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						m_attrs = bms_add_member(m_attrs, idx_attridx);
+				}
+				else
+				{
+					if (idx_attrnum != 0)
+						u_attrs = bms_add_member(u_attrs, idx_attridx);
+				}
+
+				if (idx_attrnum == 0)
+					nth_expr++;
+			}
 		}
+		else
+		{
+			/*
+			 * Here we know that we're reviewing an index that doesn't have a
+			 * partial predicate, doesn't use expressions, and doesn't have a
+			 * amcomparedatums() implementation.
+			 */
 
-		/* If both are NULL, consider them equal */
-		if (oldnull)
-			continue;
+			/* Compare the index key datums for equality */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				Form_pg_attribute attr;
+				AttrNumber	rel_attrnum;
+				int			rel_attridx;
+				bool		values_equal = false;
+				bool		old_null,
+							new_null;
+				Datum		old_val,
+							new_val;
 
-		/* Get attribute metadata */
-		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
-		attr = TupleDescAttr(tupdesc, attrnum - 1);
-
-		/* Compare using type-specific equality operator */
-		if (!tts_attr_equal(attr->atttypid,
-							attr->attcollation,
-							attr->attbyval,
-							attr->attlen,
-							oldval,
-							newval))
-			modified = bms_add_member(modified, attidx);
-	}
+				rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
 
-	bms_free(indexed_attrs);
+				/* Zero would mean expression, something we don't expect here */
+				Assert(rel_attrnum > 0 && rel_attrnum <= tupdesc->natts);
 
-	return modified;
+				/* Extract values from both slots for this attribute */
+				old_val = slot_getattr(old_tts, rel_attrnum, &old_null);
+				new_val = slot_getattr(new_tts, rel_attrnum, &new_null);
+
+				/*
+				 * If one value is NULL and the other is not, they are not
+				 * equal
+				 */
+				if (old_null != new_null)
+				{
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+					continue;
+				}
+
+				/* If both are NULL, consider them equal */
+				if (old_null)
+				{
+					u_attrs = bms_add_member(u_attrs, rel_attridx);
+					continue;
+				}
+
+				attr = TupleDescAttr(tupdesc, rel_attrnum - 1);
+
+				/*
+				 * Compare using type-specific equality which at this point is
+				 * the relation's type because FormIndexDatum() will populate
+				 * the values/nulls but won't transform them into the final
+				 * values destined for the index tuple, that's left to
+				 * index_form_tuple() which we don't call (on purpose).
+				 */
+				values_equal = tts_attr_equal(attr->atttypid,
+											  attr->attcollation,
+											  attr->attbyval,
+											  attr->attlen,
+											  old_val,
+											  new_val);
+
+				if (!values_equal)
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+				else
+					u_attrs = bms_add_member(u_attrs, rel_attridx);
+			}
+		}
+
+		/*
+		 * Here we know all the attributes we thought might be modified and
+		 * all those we know haven't been.  Take the difference and add it to
+		 * the modified indexed attributes set.
+		 */
+		m_attrs = bms_del_members(m_attrs, u_attrs);
+		p_attrs = bms_del_members(p_attrs, u_attrs);
+		mix_attrs = bms_add_members(mix_attrs, m_attrs);
+		mix_attrs = bms_add_members(mix_attrs, p_attrs);
+
+		bms_free(m_attrs);
+		bms_free(u_attrs);
+		bms_free(p_attrs);
+	}
+
+	return mix_attrs;
 }
 
 /*
@@ -2395,6 +2651,9 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* The set of modified indexed attributes that trigger new index entries */
+	Bitmapset  *mix_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2517,13 +2776,32 @@ lreplace:
 	bms_free(resultRelInfo->ri_ChangedIndexedCols);
 	resultRelInfo->ri_ChangedIndexedCols = NULL;
 
-	resultRelInfo->ri_ChangedIndexedCols =
-		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+	/*
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * Next up we need to find out the set of indexed attributes that have
+	 * changed in value and should trigger a new index tuple.  We could start
+	 * with the set of updated columns via ExecGetUpdatedCols(), but if we do
+	 * we will overlook attributes directly modified by heap_modify_tuple()
+	 * which are not known to ExecGetUpdatedCols().
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot);
 
 	/*
-	 * replace the heap tuple
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2535,9 +2813,12 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								resultRelInfo->ri_ChangedIndexedCols,
+								mix_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = mix_attrs;
+
 	return result;
 }
 
@@ -2555,7 +2836,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index d69dc090aa4..e9a53b95caf 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -855,10 +855,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 63dd41c1f21..9bdf73eda59 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 13ea91922ef..2f265f4816c 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 41d541aa6b2..59db389a546 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -326,7 +326,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -361,8 +361,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 16be5c7a9c1..42bd329eaad 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1210,6 +1210,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 8a5931a3118..2b9206ff24a 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,7 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 Bitmapset *updated_cols,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1503,12 +1503,12 @@ 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,
-				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
 										 wait, tmfd, lockmode,
-										 updated_cols, update_indexes);
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2011,7 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  Bitmapset *modified_indexe_attrs,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index dda95e54903..8d364f8b30f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 993dc0e6ced..a19585ba065 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
@@ -800,9 +805,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
-extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
-												  TupleTableSlot *tts_old,
-												  TupleTableSlot *tts_new);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+												  EState *estate,
+												  TupleTableSlot *old_tts,
+												  TupleTableSlot *new_tts);
 extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 						   Datum value1, Datum value2);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 898368fb8cb..d8e88817206 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -494,6 +508,11 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
 	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
index e34a821c403..54b3981918c 100644
--- a/src/test/isolation/expected/insert-conflict-specconflict.out
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -80,6 +80,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
@@ -172,6 +176,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
@@ -369,6 +377,10 @@ key|data
 step s1_commit: COMMIT;
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 step s2_upsert: <... completed>
 step controller_show: SELECT * FROM upserttest;
 key|data       
@@ -530,6 +542,14 @@ isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
 step s2_commit: COMMIT;
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
 step s1_upsert: <... completed>
 step s1_noop: 
 step controller_show: SELECT * FROM upserttest;
diff --git a/src/test/regress/expected/hot_expression_indexes.out b/src/test/regress/expected/hot_expression_indexes.out
new file mode 100644
index 00000000000..ea856b5db63
--- /dev/null
+++ b/src/test/regress/expected/hot_expression_indexes.out
@@ -0,0 +1,1007 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             5 |           2 |                 40.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->>'status'))
+    WHERE (docs->>'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           2 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->>'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression with COLLATION and BTREE (nbtree) index
+-- ================================================================
+CREATE COLLATION case_insensitive (
+    provider = icu,
+    locale = 'und-u-ks-level2',
+    deterministic = false
+);
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    name TEXT COLLATE case_insensitive
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_lower_idx ON t USING BTREE (name COLLATE case_insensitive);
+INSERT INTO t VALUES (1, 'ALICE');
+-- Change case but not value - should NOT be HOT in BTREE
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change to new value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Nested JSONB Expression and JSONB equality '->' (not '->>')
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->>'status'))
+    WHERE (data->>'priority')::int > 5
+      AND (data->>'active')::boolean = true;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT update
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: Still 1 HOT
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+ count 
+-------
+     0
+(1 row)
+
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+INSERT INTO t VALUES (1, 50, 'below range');
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     150
+(1 row)
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     160
+(1 row)
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           4 |                100.00 | t
+(1 row)
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+SELECT id, value, description FROM t;
+ id | value |  description  
+----+-------+---------------
+  1 |    50 | updated again
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash((data->>'category'));
+INSERT INTO t VALUES (1, '{"category": "books", "title": "PostgreSQL Guide"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET data = '{"category": "books", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - NOT HOT
+UPDATE t SET data = '{"category": "videos", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET data = '{"category": "courses", "title": "PostgreSQL Basics"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_brin     |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT (BRIN allows it for single row)
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_hash     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (HASH blocks it)
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: 1 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT (BRIN permits single-row updates)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+-- Expected: 2 HOT (HASH blocks it)
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+-- Expected: 3 HOT
+DROP TABLE t CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..4459625a59b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: hot_expression_indexes
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/hot_expression_indexes.sql b/src/test/regress/sql/hot_expression_indexes.sql
new file mode 100644
index 00000000000..4929be144ae
--- /dev/null
+++ b/src/test/regress/sql/hot_expression_indexes.sql
@@ -0,0 +1,747 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->>'status'))
+    WHERE (docs->>'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->>'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression with COLLATION and BTREE (nbtree) index
+-- ================================================================
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    name TEXT COLLATE case_insensitive
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_lower_idx ON t USING BTREE (name COLLATE case_insensitive);
+
+INSERT INTO t VALUES (1, 'ALICE');
+
+-- Change case but not value - should NOT be HOT in BTREE
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Change to new value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Nested JSONB Expression and JSONB equality '->' (not '->>')
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->>'status'))
+    WHERE (data->>'priority')::int > 5
+      AND (data->>'active')::boolean = true;
+
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT update
+
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: Still 1 HOT
+
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+
+INSERT INTO t VALUES (1, 50, 'below range');
+
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4, 't');
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+SELECT id, value, description FROM t;
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash((data->>'category'));
+INSERT INTO t VALUES (1, '{"category": "books", "title": "PostgreSQL Guide"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET data = '{"category": "books", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed JSONB field - NOT HOT
+UPDATE t SET data = '{"category": "videos", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - NOT HOT
+UPDATE t SET data = '{"category": "courses", "title": "PostgreSQL Basics"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+-- Expected: 1 HOT (BRIN allows it for single row)
+
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+-- Expected: 0 HOT (HASH blocks it)
+
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT
+
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT
+
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+-- Expected: 2 HOT (BRIN permits single-row updates)
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT
+
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+-- Expected: 2 HOT
+
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+-- Expected: 2 HOT (HASH blocks it)
+
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+-- Expected: 3 HOT
+
+DROP TABLE t CASCADE;
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 23bce72ae64..52ef8f10b35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -390,6 +390,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.49.0

#35Greg Burd
greg@burd.me
In reply to: Greg Burd (#34)
4 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

On Nov 19 2025, at 1:00 pm, Greg Burd <greg@burd.me> wrote:

On Nov 16 2025, at 1:53 pm, Greg Burd <greg@burd.me> wrote:

0004 - Enable HOT updates for expression and partial indexes

This finally gets us back to where this project started, but on much
more firm ground than before because we're not going to
self-deadlock.
The idea has grown from a small function into something larger, but only
out of necessity.

In this patch I add ExecWhichIndexesRequireUpdates() in execIndexing.c
which implements (c) finding the set of attributes that force new index
updates. This set can be very different from the modified indexed
attributes. We know that some attributes are not equal to their
previous versions, but does that mean that the index that references
that attribute needs a new index tuple? It may, or it may not. Here's
the comment on that function that explains:

/*
* ExecWhichIndexesRequireUpdates
*
* Determine which indexes need updating given modified indexed attributes.
* This function is a companion to ExecCheckIndexedAttrsForChanges().
On the
* surface, they appear similar but they are doing two very different things.
*
* For a standard index on a set of attributes this is the
intersection of
* the mix_attrs and the index attrs (key, expression, but not predicate).
*
* For expression indexes and indexes which implement the amcomparedatums()
* index AM API we'll need to form index datum and compare each
attribute to
* see if any actually changed.
*
* For expression indexes the result of the expression might not change
at all,
* this is common with JSONB columns which require expression indexes
and where
* it is commonplace to index a field within a document and have
updates that
* generally don't update that field.
*
* Partial indexes won't trigger index tuples when the old/new tuples
are both
* outside of the predicate range.
*
* For nbtree the amcomparedatums() API is critical as it requires
that key
* attributes are equal when they memcmp(), which might not be the
case when
* using type-specific comparison or factoring in collation which
might make
* an index case insensitive.
*
* All of this is to say that the goal is for the executor to know,
ahead of
* calling into the table AM for the update and before calling into
the index
* AM for inserting new index tuples, which attributes at a minimum will
* necessitate a new index tuple.
*
...
*/

Attached are rebased (d5b4f3a6d4e) patches with the only changes
happening in the last patch in the series.

0004 - Enable HOT updates for expression and partial indexes

I was never happy with the dual functions
ExecCheckIndexedAttrsForChanges() and ExecWhichIndexesRequireUpdates(),
it felt like too much overhead and duplication of effort. While
updating my tests, adding a few cases, I found that there was also a
flaw in the logic. So, time to rewrite and combine them.

What did I discover? Before the logic was to find the set of modified
indexed attributes then review all the indexes for changed attributes
using FormIndexDatum() and comparing before/after to see if expressions
really changed the value to be indexed or not. The first pass didn't
take into account expressions, the second did. So, an expression index
over JSONB data wouldn't extract and test the field within the document,
it was just comparing the entire document before/after using the jsonb
comparison function, no bueno.

This approach wraps both functions into one somewhat simplified
function. The logic is basically, iterate over the indexes reviewing
indexed attributes for changes. Along the way we call into the new
index AM's comparison function when present, otherwise we find and use
the proper type-specific comparison function for the datum. At the end
of the function we have our Bitmapset of attributes that should trigger
new index tuples.

What's left undone?

* I need to check code coverage so that I might

I did this and it was quite good, I'll do it again for this new series
but it's nice to see that the tests are exercising the vast majority of
the code paths.

* create tests covering all the new cases

I think the coverage is good, maybe even redundant or overly complex
in places.

* update the README.HOT documentation, wiki, etc.

Soon, I hope to have this approach solid and under review before
solidifying the docs.

* performance...

Still as yet unmeasured, I know that there is more work per-update to
perform these checks, so some overhead, but I don't know if that
overhead is more than before with HeapDetermineColumnsInfo() and
index_unchanged_by_update(). Those two functions did essentially the
same thing, only with binary comparison (datumIsEqual()). I need to
measure that. What about doing all this work outside of the buffer lock
in heap_update()? Surely that'll give back a bit or at least add to
concurrency. Forming index tuples a few extra times and evaluating the
expressions 3 times rather than 1 is going to hurt, I think I can come
up with a way to cache the formed datum and use it later on, but is that
worth it? Complex expressions, yes. Also, what about expressions that
expect to be executed once... and now are 3x? That's what forced my
update to the insert-conflict-specconflict.out test, but AFAICT there is
no way to test if an expression's value is going to change on update
without exercising it once for the old tuple and once for the new tuple.
Even if it were possible for an index to provide the key it might have
changed after the expression evaluation (as is the case in hash), so I
don't think this is avoidable. Maybe that's reason enough to add a
reloption to disable the expression evaluation piece of this? Given
that it might create a logic or performance regression. The flip side
is the potential to use the HOT path, that's a real savings.

One concerning thing is that nbtree's assumption that key attributes for
TIDs must use binary comparison for equality. This means that for our
common case (heap/btree) there is more work per-update than before,
which is why I need to measure. I could look into eliminating the
nbtree requirement, I don't understand it too well as yet by I believe
that on page split there is an attempt to deduplicate TIDs into a
TIDBitmap and the test for when that's possible is datumIsEqual(). If
that were the same as in this new code, possibly evening using
tts_attr_equal(), then... I don't know, I'll have to investigate. Chime
in here if you can educate me on this one. :)

best.

-greg

Doh!

I forgot to commit the fixed regression test expected output before
formatting the patch set, here it is.

-greg

Attachments:

v22-0001-Reorganize-heap-update-logic.patchapplication/octet-streamDownload
From 0de059ae17042a76594610e1b7b35dbb2db9415c Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v22 1/4] Reorganize heap update logic

This commit refactors the interaction between heap_tuple_update(),
heap_update(), and simple_heap_update() to improve code organization
and flexibility. The changes are functionally equivalent to the
previous implementation and have no performance impact.

The primary motivation is to prepare for upcoming modifications to
how and where modified attributes are identified during the update
path, particularly for catalog updates.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().

No user-visible changes.
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4b0c49f4bb0..aff47481345 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -103,10 +101,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2799,6 +2797,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2811,6 +2810,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3014,6 +3015,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3035,7 +3037,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3247,7 +3252,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3255,30 +3263,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3292,7 +3291,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3300,144 +3298,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3449,7 +3316,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3473,17 +3340,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3515,8 +3375,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3556,13 +3416,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3572,9 +3431,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3599,8 +3458,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3641,9 +3500,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3653,20 +3512,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3679,39 +3538,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3724,10 +3577,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3738,9 +3591,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3752,12 +3605,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3772,7 +3625,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3800,7 +3653,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3817,12 +3670,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3855,9 +3708,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3867,18 +3720,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3887,7 +3740,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3901,10 +3754,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3926,7 +3779,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3962,20 +3815,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4013,7 +3866,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4021,7 +3874,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4029,7 +3881,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4040,7 +3892,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4057,10 +3909,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4082,7 +3932,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4091,7 +3941,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4100,17 +3950,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4118,7 +3968,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4143,12 +3993,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4173,7 +4023,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4181,14 +4031,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4221,13 +4071,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4236,7 +4079,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4408,7 +4251,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4491,25 +4334,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9149,12 +9142,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9185,17 +9177,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9208,7 +9196,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9217,8 +9205,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..1cf9a18775d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 909db73b7bb..41d541aa6b2 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -321,11 +321,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -391,6 +393,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.49.0

v22-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From edc170a3f61de2141b383134ae40f105ee90aebe Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v22 2/4] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo in heap_update. This enables the comparison
to happen without taking a lock on the page and opens the door to reuse
in other code paths.

Because heap_update now requires the caller to provide the modified
indexed columns simple_heap_update has become a tad more complex.  It is
frequently called from CatalogTupleUpdate which either updates heap
tuples via their form or using heap_modify_tuple.  In both cases the
caller does know the modified set of attributes, but sadly those
attributes are lost before being provided to simple_heap_update.  Due to
that the "simple" path has to retain the HeapDetermineColumnsInfo logic
of old (for now).  In order for that to work it was necessary to split
the (overly large) heap_update call itself up.  This moves up into
simple_heap_update and heap_tuple_update a bit of what existed in
heap_update itself.  Ideally this will be cleaned up once
CatalogTupleUpdate paths are all recording modified attributes
correctly, when that happens the "simple" path can be simplified again.

ExecCheckIndexedAttrsForChanges replaces HeapDeterminesColumnsInfo and
tts_attr_equal replaces heap_attr_equal changing the test for equality
when calling into heap_tuple_update (but not simple_heap_update).  In
the past we used datumIsEqual(), essentially a binary comparison using
memcmp(), now the comparison code in tts_attr_equal uses type-specific
equality function when available and falls back to datumIsEqual() when
not.  This change in equality testing has some intended implications and
opens the door for more HOT updates (foreshadowing).  For instance,
indexes with collation information allowing more HOT updates when the
index is specified to be case insensitive.

This change forced some logic changes in execReplication on the update
paths is now it is required to have knowledge of the set of attributes
that are both changed and referenced by indexes.  Luckilly, the this is
available within calls to slot_modify_data() where LogicalRepTupleData
is processed and has a set of updated attributes.  In this case rather
than using ExecCheckIndexedAttrsForChanges we can preseve what
slot_modify_data() identifies as the modified set and then intersect
that with the set of indexes on the relation and get the correct set of
modified indexed attributes required on heap_update().
---
 src/backend/access/heap/heapam.c         |  12 +-
 src/backend/access/heap/heapam_handler.c |  72 +++++--
 src/backend/access/table/tableam.c       |   5 +-
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/execReplication.c   |   7 +
 src/backend/executor/nodeModifyTable.c   | 247 ++++++++++++++++++++++-
 src/backend/nodes/bitmapset.c            |   4 +
 src/backend/replication/logical/worker.c |  72 ++++++-
 src/backend/utils/cache/relcache.c       |  15 ++
 src/include/access/tableam.h             |   8 +-
 src/include/executor/executor.h          |   5 +
 src/include/nodes/execnodes.h            |   1 +
 src/include/utils/rel.h                  |   1 +
 src/include/utils/relcache.h             |   1 +
 14 files changed, 415 insertions(+), 36 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index aff47481345..1cdb72b3a7a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3263,12 +3263,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1cf9a18775d..ef08e1d3e10 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -414,16 +416,61 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +484,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 5e41404937e..dadcf03ed24 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,6 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  Bitmapset *modified_indexed_cols,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -346,7 +347,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								modified_indexed_cols,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 27c9eec697b..6b7b6bc8019 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index def32774c90..2709e2db0f2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 00429326c34..34f86546fc9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -54,11 +55,14 @@
 
 #include "access/htup_details.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,6 +72,8 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
@@ -176,6 +182,219 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   bool canSetTag);
 
 
+/*
+ * Compare two datums using the type's default equality operator.
+ *
+ * Returns true if the values are equal according to the type's equality
+ * operator, false otherwise. Falls back to binary comparison if no
+ * type-specific operator is available.
+ *
+ * This function uses the TypeCache infrastructure which caches operator
+ * lookups for efficiency.
+ */
+bool
+tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+			   Datum value1, Datum value2)
+{
+	TypeCacheEntry *typentry;
+
+	LOCAL_FCINFO(fcinfo, 2);
+	Datum		result;
+
+	/*
+	 * Fast path for common types to avoid even the type cache lookup. These
+	 * types have simple equality semantics.
+	 */
+	switch (typid)
+	{
+		case INT2OID:
+			return DatumGetInt16(value1) == DatumGetInt16(value2);
+		case INT4OID:
+			return DatumGetInt32(value1) == DatumGetInt32(value2);
+		case INT8OID:
+			return DatumGetInt64(value1) == DatumGetInt64(value2);
+		case FLOAT4OID:
+			return !float4_cmp_internal(DatumGetFloat4(value1), DatumGetFloat4(value2));
+		case FLOAT8OID:
+			return !float8_cmp_internal(DatumGetFloat8(value1), DatumGetFloat8(value2));
+		case BOOLOID:
+			return DatumGetBool(value1) == DatumGetBool(value2);
+		case OIDOID:
+		case REGPROCOID:
+		case REGPROCEDUREOID:
+		case REGOPEROID:
+		case REGOPERATOROID:
+		case REGCLASSOID:
+		case REGTYPEOID:
+		case REGROLEOID:
+		case REGNAMESPACEOID:
+		case REGCONFIGOID:
+		case REGDICTIONARYOID:
+			return DatumGetObjectId(value1) == DatumGetObjectId(value2);
+		case CHAROID:
+			return DatumGetChar(value1) == DatumGetChar(value2);
+		default:
+			/* Continue to type cache lookup */
+			break;
+	}
+
+	/*
+	 * Look up the type's equality operator using the type cache. Request both
+	 * the operator OID and the function info for efficiency.
+	 */
+	typentry = lookup_type_cache(typid,
+								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
+
+	/*
+	 * If no equality operator is available, fall back to binary comparison.
+	 * This handles types that don't have proper equality operators defined.
+	 */
+	if (!OidIsValid(typentry->eq_opr))
+		return datumIsEqual(value1, value2, typbyval, typlen);
+
+	/*
+	 * Use the cached function info if available, otherwise look it up. The
+	 * type cache keeps this around so subsequent calls are fast.
+	 */
+	if (typentry->eq_opr_finfo.fn_addr == NULL)
+	{
+		Oid			eq_proc = get_opcode(typentry->eq_opr);
+
+		if (!OidIsValid(eq_proc))
+			/* Shouldn't happen, but fall back to binary comparison */
+			return datumIsEqual(value1, value2, typbyval, typlen);
+
+		fmgr_info_cxt(eq_proc, &typentry->eq_opr_finfo,
+					  CacheMemoryContext);
+	}
+
+	/* Set up function call */
+	InitFunctionCallInfoData(*fcinfo, &typentry->eq_opr_finfo, 2,
+							 collation, NULL, NULL);
+
+	fcinfo->args[0].value = value1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = value2;
+	fcinfo->args[1].isnull = false;
+
+	/* Invoke the equality operator */
+	result = FunctionCallInvoke(fcinfo);
+
+	/*
+	 * If the function returned NULL (shouldn't happen for equality ops),
+	 * treat as not equal for safety.
+	 */
+	if (fcinfo->isnull)
+		return false;
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Determine which updated attributes actually changed values between old and
+ * new tuples and are referenced by indexes on the relation.
+ *
+ * Returns a Bitmapset of attribute offsets (0-based, adjusted by
+ * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								TupleTableSlot *tts_old,
+								TupleTableSlot *tts_new)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *indexed_attrs;
+	Bitmapset  *modified = NULL;
+	int			attidx;
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/*
+	 * Get the set of index key attributes.  This includes summarizing,
+	 * expression indexes and attributes mentioned in the predicate of a
+	 * partition but not those in INCLUDING.
+	 */
+	indexed_attrs = RelationGetIndexAttrBitmap(relation,
+											   INDEX_ATTR_BITMAP_INDEXED);
+	Assert(!bms_is_empty(indexed_attrs));
+
+	/*
+	 * NOTE: It is important to scan all indexed attributes in the tuples
+	 * because ExecGetAllUpdatedCols won't include columns that may have been
+	 * modified via heap_modify_tuple_by_col which is the case in
+	 * tsvector_update_trigger.
+	 */
+	attidx = -1;
+	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	{
+		/* attidx is zero-based, attrnum is the normal attribute number */
+		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+		Form_pg_attribute attr;
+		bool		oldnull,
+					newnull;
+		Datum		oldval,
+					newval;
+
+		/*
+		 * If it's a whole-tuple reference, record as modified.  It's not
+		 * really worth supporting this case, since it could only succeed
+		 * after a no-op update, which is hardly a case worth optimizing for.
+		 */
+		if (attrnum == 0)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/*
+		 * Likewise, include in the modified set any system attribute other
+		 * than tableOID; we cannot expect these to be consistent in a HOT
+		 * chain, or even to be set correctly yet in the new tuple.
+		 */
+		if (attrnum < 0)
+		{
+			if (attrnum != TableOidAttributeNumber)
+				modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* Extract values from both slots */
+		oldval = slot_getattr(tts_old, attrnum, &oldnull);
+		newval = slot_getattr(tts_new, attrnum, &newnull);
+
+		/* If one value is NULL and the other is not, they are not equal */
+		if (oldnull != newnull)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* If both are NULL, consider them equal */
+		if (oldnull)
+			continue;
+
+		/* Get attribute metadata */
+		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
+		attr = TupleDescAttr(tupdesc, attrnum - 1);
+
+		/* Compare using type-specific equality operator */
+		if (!tts_attr_equal(attr->atttypid,
+							attr->attcollation,
+							attr->attbyval,
+							attr->attlen,
+							oldval,
+							newval))
+			modified = bms_add_member(modified, attidx);
+	}
+
+	bms_free(indexed_attrs);
+
+	return modified;
+}
+
 /*
  * Verify that the tuples to be produced by INSERT match the
  * target relation's rowtype
@@ -2168,8 +2387,8 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2291,6 +2510,16 @@ lreplace:
 	if (resultRelationDesc->rd_att->constr)
 		ExecConstraints(resultRelInfo, slot, estate);
 
+	/*
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	resultRelInfo->ri_ChangedIndexedCols =
+		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+
 	/*
 	 * replace the heap tuple
 	 *
@@ -2306,6 +2535,7 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								resultRelInfo->ri_ChangedIndexedCols,
 								&updateCxt->updateIndexes);
 
 	return result;
@@ -2524,8 +2754,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3222,8 +3453,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3248,6 +3479,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4354,7 +4586,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4530,6 +4762,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index b4ecf0b0390..9014990267a 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 93970c6af29..b363eaa49cc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -243,6 +243,8 @@
  */
 
 #include "postgres.h"
+#include "access/sysattr.h"
+#include "nodes/bitmapset.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
@@ -275,7 +277,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -291,6 +292,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1112,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1200,28 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+				if (!tts_attr_equal(att->atttypid,
+									att->attcollation,
+									att->attbyval,
+									att->attlen,
+									srcslot->tts_values[i],
+									slot->tts_values[i]))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1229,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2947,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2964,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2989,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3501,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3571,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3597,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..32825596be1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2482,6 +2482,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5284,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5307,6 +5309,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5332,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5373,6 +5378,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5505,10 +5511,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5531,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5535,6 +5547,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5551,6 +5564,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index e16bf025692..8a5931a3118 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1502,12 +1503,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 updated_cols, update_indexes);
 }
 
 /*
@@ -2010,6 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  Bitmapset *modified_indexe_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..993dc0e6ced 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -800,5 +800,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
+												  TupleTableSlot *tts_old,
+												  TupleTableSlot *tts_new);
+extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+						   Datum value1, Datum value2);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..8b08e0045ba 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -498,6 +498,7 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 80286076a11..b23a7306e69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..d3fbb8b093a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
-- 
2.49.0

v22-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From cf41827dd2ed13e5fa02763bddec570125af621e Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v22 3/4] Replace index_unchanged_by_update with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update with the same information
found earlier in the update path.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 874a8fc89ad..5d819bda54a 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -300,8 +300,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 401606f840a..fb1bc3a480d 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -138,11 +138,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -440,10 +435,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -993,152 +985,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..d69dc090aa4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8b08e0045ba..898368fb8cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -202,10 +202,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.49.0

v22-0004-Enable-HOT-updates-for-expression-and-partial-in.patchapplication/octet-streamDownload
From 9f584afc7f27ac8e93c6cf425248a24fba679a4e Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v22 4/4] Enable HOT updates for expression and partial indexes

Currently, PostgreSQL conservatively prevents HOT (Heap-Only Tuple)
updates whenever any indexed column changes, even if the indexed
portion of that column remains identical. This is overly restrictive
for expression indexes (where f(column) might not change even when
column changes) and partial indexes (where both old and new tuples
might fall outside the predicate).  Finally, index AMs play no role
in deciding when they need a new index entry on update, the rules
regarding that are based on binary equality and the HEAP's model for
MVCC and related HOT optimization.  Here we open that door a bit so
as to enable more nuanced control over the process.  This enables
index AMs that require binary equality (as is the case for nbtree)
to do that without disallowing type-specific equality checking for
other indexes.

This patch introduces several improvements to enable HOT updates in
these cases:

Add amcomparedatums() callback to IndexAmRoutine. This allows index
access methods like GIN to provide custom logic for comparing datums by
extracting and comparing index keys rather than comparing the raw
datums. GIN indexes now implement gincomparedatums() which extracts keys
from both datums and compares the resulting key sets.  Also, as
mentioned earlier nbtree implements this API and uses datumIsEqual() for
equality so that the manner in which it deduplicates TIDs on page split
doesn't have to change.  This is not a required API, when not
implemented the executor will compare TupleTableSlot datum for equality
using type-specific operators and take into account collation so that an
update from "Apple" to "APPLE" on a case insensitive index can now be
HOT.

ExecWhichIndexesRequireUpdates() is re-written to find the set of
modified indexed attributes that trigger new index tuples on updated.
For partial indexes, this checks whether both old and new tuples satisfy
or fail the predicate. For expression indexes, this uses type-specific
equality operators to compare computed values. For extraction-based
indexes (GIN/RUM) that implement amcomparedatums() it uses that.

Importantly, table access methods can still signal using TU_Update if
all, none, or only summarizing indexes should be updated.  While the
executor layer now owns determining what has changed due to an update
and is interested in only updating the minimum number of indexes
possible, the table AM can override that while performing
table_tuple_update(), which is what heap does.  While this signal is
very specific to how the heap implements MVCC and its HOT optimization,
we'll leave replacing that for another day.

This optimization trades off some new overhead for the potential for
more updates to use the HOT optimized path and avoid index and heap
bloat.  This should significantly improve update performance for tables
with expression indexes, partial indexes, and GIN/GiST indexes on
complex data types like JSONB and tsvector, while maintaining correct
index semantics.  Minimal additional overhead due to type-specific
equality checking should be washed out by the benefits of updating
indexes fewer times.

One notable trade-off is that there are more calls to FormIndexDatum()
as a result.  Caching these might reduce some of that overhead, but not
all.  This lead to the change in the frequency for expressions in the
spec update test to output notice messages, but does not impact
correctness.
---
 src/backend/access/brin/brin.c                |    1 +
 src/backend/access/gin/ginutil.c              |   94 +-
 src/backend/access/heap/heapam.c              |   10 +-
 src/backend/access/heap/heapam_handler.c      |    6 +-
 src/backend/access/nbtree/nbtree.c            |   38 +
 src/backend/access/table/tableam.c            |    4 +-
 src/backend/bootstrap/bootstrap.c             |    8 +
 src/backend/catalog/index.c                   |   57 +
 src/backend/catalog/indexing.c                |   16 +-
 src/backend/catalog/toasting.c                |    4 +
 src/backend/executor/execIndexing.c           |   45 +-
 src/backend/executor/nodeModifyTable.c        |  437 +++++--
 src/backend/nodes/makefuncs.c                 |    4 +
 src/include/access/amapi.h                    |   28 +
 src/include/access/gin.h                      |    3 +
 src/include/access/heapam.h                   |    6 +-
 src/include/access/nbtree.h                   |    4 +
 src/include/access/tableam.h                  |    8 +-
 src/include/catalog/index.h                   |    1 +
 src/include/executor/executor.h               |   12 +-
 src/include/nodes/execnodes.h                 |   19 +
 .../expected/insert-conflict-specconflict.out |   20 +
 .../expected/hot_expression_indexes.out       | 1006 +++++++++++++++++
 src/test/regress/parallel_schedule            |    6 +
 .../regress/sql/hot_expression_indexes.sql    |  747 ++++++++++++
 src/tools/pgindent/typedefs.list              |    1 +
 26 files changed, 2460 insertions(+), 125 deletions(-)
 create mode 100644 src/test/regress/expected/hot_expression_indexes.out
 create mode 100644 src/test/regress/sql/hot_expression_indexes.sql

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index cb3331921cb..36e639552e6 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -290,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = NULL;
 	amroutine->ambeginscan = brinbeginscan;
 	amroutine->amrescan = brinrescan;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 78f7b7a2495..85e25ed73e8 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = ginbuildphasename;
 	amroutine->amvalidate = ginvalidate;
+	amroutine->amcomparedatums = gincomparedatums;
 	amroutine->amadjustmembers = ginadjustmembers;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -729,3 +724,88 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare two datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/*
+	 * Extract keys from both datums using existing GIN infrastructure.
+	 */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys → definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		int			cmp = ginCompareEntries(&ginstate, attnum,
+											old_keys[i], old_categories[i],
+											new_keys[i], new_categories[i]);
+
+		if (cmp != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1cdb72b3a7a..5b0ff13b13d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3268,7 +3268,7 @@ heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			Buffer buffer, Page page, BlockNumber block, ItemId lp,
 			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
-			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4337,8 +4337,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4467,7 +4468,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4500,7 +4501,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4526,6 +4526,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ef08e1d3e10..7527809ec08 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -319,7 +319,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 					Snapshot crosscheck, bool wait,
 					TM_FailureData *tmfd,
 					LockTupleMode *lockmode,
-					Bitmapset *mix_attrs,
+					const Bitmapset *mix_attrs,
 					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
@@ -407,10 +407,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fdff960c130..73cc3208757 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -155,6 +155,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
+	amroutine->amcomparedatums = btcomparedatums;
 	amroutine->amadjustmembers = btadjustmembers;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
@@ -1795,3 +1796,40 @@ bttranslatecmptype(CompareType cmptype, Oid opfamily)
 			return InvalidStrategy;
 	}
 }
+
+/*
+ * btcomparedatums - Compare two datums for equality
+ *
+ * This function is necessary because nbtree requires that keys that are not
+ * binary identical not be "equal".  Other indexes might allow "A" and "a" to
+ * be "equal" when collation is case insensative, but not nbtree.  Why?  Well,
+ * nbtree deduplicates TIDs on page split and the way it accomplish that is by
+ * doing a binary comparison of the keys.
+ */
+
+bool
+btcomparedatums(Relation index, int attrnum,
+				Datum old_datum, bool old_isnull,
+				Datum new_datum, bool new_isnull)
+{
+	TupleDesc	desc = RelationGetDescr(index);
+	CompactAttribute *att;
+
+	/*
+	 * If one value is NULL and other is not, then they are certainly not
+	 * equal
+	 */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * If both are NULL, they can be considered equal.
+	 */
+	if (old_isnull)
+		return true;
+
+	/* We do simple binary comparison of the two datums */
+	Assert(attrnum <= desc->natts);
+	att = TupleDescCompactAttr(desc, attrnum - 1);
+	return datumIsEqual(old_datum, new_datum, att->attbyval, att->attlen);
+}
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dadcf03ed24..ef7736bfa76 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,7 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  Bitmapset *modified_indexed_cols,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -348,7 +348,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode,
-								modified_indexed_cols,
+								mix_attrs,
 								update_indexes);
 
 	switch (result)
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index fc8638c1b61..329c110d0bf 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -961,10 +961,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..29b8cc4badd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2414,6 +2416,61 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnkeyatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		/*
+		 * Expressions are not allowed on non-key attributes, so we can skip
+		 * them as they should show up in the index HOT-blocking attributes.
+		 */
+		indnkeyatts = ii->ii_NumIndexKeyAttrs;
+
+		/* Collect key attributes used by the index */
+		for (i = 0; i < indnkeyatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..a361c215490 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 5d819bda54a..c665aa744b3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index fb1bc3a480d..20968a814d6 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
@@ -318,8 +322,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -343,13 +347,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -366,7 +370,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -387,8 +391,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -396,11 +407,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -435,7 +445,9 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
+		indexUnchanged = update &&
+			!bms_overlap(indexInfo->ii_IndexedAttrs,
+						 resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -604,7 +616,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 34f86546fc9..e4b2cd5a3e8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -54,10 +54,13 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/attnum.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -75,6 +78,7 @@
 #include "utils/float.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -245,6 +249,10 @@ tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 	typentry = lookup_type_cache(typid,
 								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
 
+	/* Use the type's collation if none provided */
+	if (collation == -1)
+		collation = typentry->typcollation;
+
 	/*
 	 * If no equality operator is available, fall back to binary comparison.
 	 * This handles types that don't have proper equality operators defined.
@@ -291,108 +299,356 @@ tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 }
 
 /*
- * Determine which updated attributes actually changed values between old and
- * new tuples and are referenced by indexes on the relation.
+ * ExecCheckIndexedAttrsForChanges
+ *
+ * Determine which indexes need updating by finding the set of modified
+ * indexed attributes.
+ *
+ * For expression indexes and indexes which implement the amcomparedatums()
+ * index AM API we'll need to form index datum and compare each attribute to
+ * see if any actually changed.
+ *
+ * For expression indexes the result of the expression might not change at all,
+ * this is common with JSONB columns which require expression indexes and where
+ * it is commonplace to index a field within a document and have updates that
+ * generally don't update that field.
+ *
+ * Partial indexes won't trigger index tuples when the old/new tuples are both
+ * outside of the predicate range.
+ *
+ * All other indexes require testing old/new datum for equality.  We do this
+ * by calling the type-specific equality operator when possible, otherwise we
+ * fall back to binary equality with datumIsEqual().
+ *
+ * For nbtree the amcomparedatums() API is critical as it requires that key
+ * attributes are equal when they memcmp(), which might not be the case when
+ * using type-specific comparison or factoring in collation which might make
+ * an index case insensitive.
  *
- * Returns a Bitmapset of attribute offsets (0-based, adjusted by
- * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ * All of this is to say that the goal is for the executor to know, ahead of
+ * calling into the table AM to process the update and before calling into the
+ * index AM for inserting new index tuples, which attributes truely necessitate
+ * a new index tuple.
+ *
+ * Returns a refined Bitmapset of attributes that force index updates.
  */
 Bitmapset *
 ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
-								TupleTableSlot *tts_old,
-								TupleTableSlot *tts_new)
+								EState *estate,
+								TupleTableSlot *old_tts,
+								TupleTableSlot *new_tts)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(relation);
-	Bitmapset  *indexed_attrs;
-	Bitmapset  *modified = NULL;
-	int			attidx;
+	Bitmapset  *mix_attrs = NULL;
 
 	/* If no indexes, we're done */
 	if (relinfo->ri_NumIndices == 0)
 		return NULL;
 
 	/*
-	 * Get the set of index key attributes.  This includes summarizing,
-	 * expression indexes and attributes mentioned in the predicate of a
-	 * partition but not those in INCLUDING.
+	 * NOTE: Expression and predicates that are observed to change will have
+	 * all their attributes added into the m_attrs set knowing that some of
+	 * those might not have changed.  Take for instance an index on (a + b)
+	 * followed by an index on (b) with an update that changes only the value
+	 * of 'a'.  We'll add both 'a' and 'b' to the m_attrs set then later when
+	 * reviewing the second index add 'b' to the u_attrs (unchanged) set.  In
+	 * the end, we'll remove all the unchanged from the m_attrs and get our
+	 * desired result.
 	 */
-	indexed_attrs = RelationGetIndexAttrBitmap(relation,
-											   INDEX_ATTR_BITMAP_INDEXED);
-	Assert(!bms_is_empty(indexed_attrs));
 
-	/*
-	 * NOTE: It is important to scan all indexed attributes in the tuples
-	 * because ExecGetAllUpdatedCols won't include columns that may have been
-	 * modified via heap_modify_tuple_by_col which is the case in
-	 * tsvector_update_trigger.
-	 */
-	attidx = -1;
-	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	/* Find the indexes that reference this attribute */
+	for (int i = 0; i < relinfo->ri_NumIndices; i++)
 	{
-		/* attidx is zero-based, attrnum is the normal attribute number */
-		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
-		Form_pg_attribute attr;
-		bool		oldnull,
-					newnull;
-		Datum		oldval,
-					newval;
+		Relation	indexRel = relinfo->ri_IndexRelationDescs[i];
+		IndexAmRoutine *amroutine = indexRel->rd_indam;
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		Bitmapset  *m_attrs = NULL; /* (possibly) modified key attributes */
+		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attributes */
+		Bitmapset  *u_attrs = NULL; /* unmodified attributes */
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
+		bool		has_expressions = (indexInfo->ii_Expressions != NIL);
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		TupleTableSlot *save_scantuple;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		Datum		old_values[INDEX_MAX_KEYS];
+		bool		old_isnull[INDEX_MAX_KEYS];
+		Datum		new_values[INDEX_MAX_KEYS];
+		bool		new_isnull[INDEX_MAX_KEYS];
+
+		/* If we've reviewed all the attributes on this index, move on */
+		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
+			continue;
 
-		/*
-		 * If it's a whole-tuple reference, record as modified.  It's not
-		 * really worth supporting this case, since it could only succeed
-		 * after a no-op update, which is hardly a case worth optimizing for.
-		 */
-		if (attrnum == 0)
+		/* Checking partial at this point isn't viable when we're serializable */
+		if (is_partial && IsolationIsSerializable())
 		{
-			modified = bms_add_member(modified, attidx);
-			continue;
+			p_attrs = bms_copy(pre_attrs);
+		}
+		/* Check partial index predicate */
+		else if (is_partial)
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+				continue;
+
+			/* A transition means we need to update the index */
+			if (old_qualifies != new_qualifies)
+				p_attrs = bms_copy(pre_attrs);
+
+			/*
+			 * When both are within the predicate we must update this index,
+			 * but only if one of the index key attributes changed.
+			 */
 		}
 
 		/*
-		 * Likewise, include in the modified set any system attribute other
-		 * than tableOID; we cannot expect these to be consistent in a HOT
-		 * chain, or even to be set correctly yet in the new tuple.
+		 * Expression indexes or extraction-based index require us to form
+		 * index datums and compare.  We've done all we can to avoid this
+		 * overhead, now it's time to bite the bullet and get it done.
+		 *
+		 * XXX: Caching the values/isnull might be a win and avoid one of the
+		 * added calls to FormIndexDatum().
 		 */
-		if (attrnum < 0)
+		if (has_expressions || has_am_compare)
 		{
-			if (attrnum != TableOidAttributeNumber)
-				modified = bms_add_member(modified, attidx);
-			continue;
-		}
+			save_scantuple = econtext->ecxt_scantuple;
 
-		/* Extract values from both slots */
-		oldval = slot_getattr(tts_old, attrnum, &oldnull);
-		newval = slot_getattr(tts_new, attrnum, &newnull);
+			/* Evaluate expressions (if any) to get base datums */
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo, old_tts, estate, old_values, old_isnull);
 
-		/* If one value is NULL and the other is not, they are not equal */
-		if (oldnull != newnull)
-		{
-			modified = bms_add_member(modified, attidx);
-			continue;
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo, new_tts, estate, new_values, new_isnull);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			/* Compare the index key datums for equality */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				AttrNumber	idx_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				int			idx_attridx = idx_attrnum - FirstLowInvalidHeapAttributeNumber;
+				int			nth_expr = 0;
+				bool		values_equal = false;
+
+				/*
+				 * We can't skip attributes that we've already identified as
+				 * triggering an index update because we may have added an
+				 * attribute from an expression index that didn't change but
+				 * the expression did and that unchanged attribute is
+				 * referenced in a subsequent index where we will discover that
+				 * fact.
+				 */
+
+				/* A change to/from NULL, record this attribute */
+				if (old_isnull[j] != new_isnull[j])
+				{
+					/* Expressions will have idx_attrnum == 0 */
+					if (idx_attrnum == 0)
+						m_attrs = bms_add_members(m_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						m_attrs = bms_add_member(m_attrs, idx_attridx);
+					continue;
+				}
+
+				/* Both NULL, no change */
+				if (old_isnull[j])
+				{
+					if (idx_attrnum != 0)
+						u_attrs = bms_add_member(u_attrs, idx_attridx);
+
+					continue;
+				}
+
+				/*
+				 * Use index AM's comparison function if present when comparing
+				 * the index datum formed when creating an index key.
+				 */
+				if (has_am_compare)
+				{
+					/*
+					 * For nbtree to properly deduplicate TIDs on page split it
+					 * must treat equality as binary comparison.  So it is
+					 * vital that we call it's comparedatums() function.
+					 *
+					 * In the case of GIN/RUM indexes they too behave
+					 * differently and can even extract one or more portions of
+					 * the datum when forming index tuples.  We'd like to know
+					 * if this update needs to trigger one or more index
+					 * tuples, so we let the index AM perform their extraction
+					 * and compare the results.
+					 *
+					 * There may be other similar index AM implementation with
+					 * extraction where indexes are built using only part(s) of
+					 * the Datum and might even need to invoke type-specific
+					 * equality operators.
+					 *
+					 * NOTE: For AM comparison, pass the 1-based index
+					 * attribute number. The AM's compare function expects the
+					 * same numbering as used internally by the AM.
+					 */
+					values_equal = amroutine->amcomparedatums(indexRel, j + 1,
+															  old_values[j], old_isnull[j],
+															  new_values[j], new_isnull[j]);
+				}
+				else
+				{
+					/*
+					 * Expression index without custom AM comparison. Compare
+					 * the expression results using type-specific equality
+					 * which at this point is the expression's type, not the
+					 * index's type. It is in index_form_tuple() that index
+					 * attributes are transformed, not FormIndexDatum().
+					 */
+					Oid			expr_type_oid;
+					int16		typlen; /* Output: type length */
+					bool		typbyval;
+					Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+
+					Assert(expr != NULL);
+
+					/* Get type OID from the expression */
+					expr_type_oid = exprType((Node *) expr);
+
+					/* Get type information from the OID */
+					get_typlenbyval(expr_type_oid, &typlen, &typbyval);
+
+					values_equal = tts_attr_equal(expr_type_oid,
+												  -1,	/* use TBD expr type */
+												  typbyval,
+												  typlen,
+												  old_values[j],
+												  new_values[j]);
+				}
+
+				if (!values_equal)
+				{
+					/* Expressions will have idx_attrnum == 0 */
+					if (idx_attrnum == 0)
+						m_attrs = bms_add_members(m_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						m_attrs = bms_add_member(m_attrs, idx_attridx);
+				}
+				else
+				{
+					if (idx_attrnum != 0)
+						u_attrs = bms_add_member(u_attrs, idx_attridx);
+				}
+
+				if (idx_attrnum == 0)
+					nth_expr++;
+			}
 		}
+		else
+		{
+			/*
+			 * Here we know that we're reviewing an index that doesn't have a
+			 * partial predicate, doesn't use expressions, and doesn't have a
+			 * amcomparedatums() implementation.
+			 */
 
-		/* If both are NULL, consider them equal */
-		if (oldnull)
-			continue;
+			/* Compare the index key datums for equality */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				Form_pg_attribute attr;
+				AttrNumber	rel_attrnum;
+				int			rel_attridx;
+				bool		values_equal = false;
+				bool		old_null,
+							new_null;
+				Datum		old_val,
+							new_val;
 
-		/* Get attribute metadata */
-		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
-		attr = TupleDescAttr(tupdesc, attrnum - 1);
-
-		/* Compare using type-specific equality operator */
-		if (!tts_attr_equal(attr->atttypid,
-							attr->attcollation,
-							attr->attbyval,
-							attr->attlen,
-							oldval,
-							newval))
-			modified = bms_add_member(modified, attidx);
-	}
+				rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
 
-	bms_free(indexed_attrs);
+				/* Zero would mean expression, something we don't expect here */
+				Assert(rel_attrnum > 0 && rel_attrnum <= tupdesc->natts);
 
-	return modified;
+				/* Extract values from both slots for this attribute */
+				old_val = slot_getattr(old_tts, rel_attrnum, &old_null);
+				new_val = slot_getattr(new_tts, rel_attrnum, &new_null);
+
+				/*
+				 * If one value is NULL and the other is not, they are not
+				 * equal
+				 */
+				if (old_null != new_null)
+				{
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+					continue;
+				}
+
+				/* If both are NULL, consider them equal */
+				if (old_null)
+				{
+					u_attrs = bms_add_member(u_attrs, rel_attridx);
+					continue;
+				}
+
+				attr = TupleDescAttr(tupdesc, rel_attrnum - 1);
+
+				/*
+				 * Compare using type-specific equality which at this point is
+				 * the relation's type because FormIndexDatum() will populate
+				 * the values/nulls but won't transform them into the final
+				 * values destined for the index tuple, that's left to
+				 * index_form_tuple() which we don't call (on purpose).
+				 */
+				values_equal = tts_attr_equal(attr->atttypid,
+											  attr->attcollation,
+											  attr->attbyval,
+											  attr->attlen,
+											  old_val,
+											  new_val);
+
+				if (!values_equal)
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+				else
+					u_attrs = bms_add_member(u_attrs, rel_attridx);
+			}
+		}
+
+		/*
+		 * Here we know all the attributes we thought might be modified and
+		 * all those we know haven't been.  Take the difference and add it to
+		 * the modified indexed attributes set.
+		 */
+		m_attrs = bms_del_members(m_attrs, u_attrs);
+		p_attrs = bms_del_members(p_attrs, u_attrs);
+		mix_attrs = bms_add_members(mix_attrs, m_attrs);
+		mix_attrs = bms_add_members(mix_attrs, p_attrs);
+
+		bms_free(m_attrs);
+		bms_free(u_attrs);
+		bms_free(p_attrs);
+	}
+
+	return mix_attrs;
 }
 
 /*
@@ -2395,6 +2651,9 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* The set of modified indexed attributes that trigger new index entries */
+	Bitmapset  *mix_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2517,13 +2776,32 @@ lreplace:
 	bms_free(resultRelInfo->ri_ChangedIndexedCols);
 	resultRelInfo->ri_ChangedIndexedCols = NULL;
 
-	resultRelInfo->ri_ChangedIndexedCols =
-		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+	/*
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * Next up we need to find out the set of indexed attributes that have
+	 * changed in value and should trigger a new index tuple.  We could start
+	 * with the set of updated columns via ExecGetUpdatedCols(), but if we do
+	 * we will overlook attributes directly modified by heap_modify_tuple()
+	 * which are not known to ExecGetUpdatedCols().
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot);
 
 	/*
-	 * replace the heap tuple
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2535,9 +2813,12 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								resultRelInfo->ri_ChangedIndexedCols,
+								mix_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = mix_attrs;
+
 	return result;
 }
 
@@ -2555,7 +2836,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index d69dc090aa4..e9a53b95caf 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -855,10 +855,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 63dd41c1f21..9bdf73eda59 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 13ea91922ef..2f265f4816c 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 41d541aa6b2..59db389a546 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -326,7 +326,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -361,8 +361,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 16be5c7a9c1..42bd329eaad 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1210,6 +1210,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 8a5931a3118..2b9206ff24a 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,7 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 Bitmapset *updated_cols,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1503,12 +1503,12 @@ 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,
-				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
 										 wait, tmfd, lockmode,
-										 updated_cols, update_indexes);
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2011,7 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  Bitmapset *modified_indexe_attrs,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index dda95e54903..8d364f8b30f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 993dc0e6ced..a19585ba065 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
@@ -800,9 +805,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
-extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
-												  TupleTableSlot *tts_old,
-												  TupleTableSlot *tts_new);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+												  EState *estate,
+												  TupleTableSlot *old_tts,
+												  TupleTableSlot *new_tts);
 extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 						   Datum value1, Datum value2);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 898368fb8cb..d8e88817206 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -494,6 +508,11 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
 	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
index e34a821c403..54b3981918c 100644
--- a/src/test/isolation/expected/insert-conflict-specconflict.out
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -80,6 +80,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
@@ -172,6 +176,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
@@ -369,6 +377,10 @@ key|data
 step s1_commit: COMMIT;
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 step s2_upsert: <... completed>
 step controller_show: SELECT * FROM upserttest;
 key|data       
@@ -530,6 +542,14 @@ isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
 step s2_commit: COMMIT;
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
 step s1_upsert: <... completed>
 step s1_noop: 
 step controller_show: SELECT * FROM upserttest;
diff --git a/src/test/regress/expected/hot_expression_indexes.out b/src/test/regress/expected/hot_expression_indexes.out
new file mode 100644
index 00000000000..29aad70e2aa
--- /dev/null
+++ b/src/test/regress/expected/hot_expression_indexes.out
@@ -0,0 +1,1006 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             5 |           2 |                 40.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->>'status'))
+    WHERE (docs->>'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           2 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->>'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression with COLLATION and BTREE (nbtree) index
+-- ================================================================
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    name TEXT COLLATE case_insensitive
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_lower_idx ON t USING BTREE (name COLLATE case_insensitive);
+INSERT INTO t VALUES (1, 'ALICE');
+-- Change case but not value - should NOT be HOT in BTREE
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change to new value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Nested JSONB Expression and JSONB equality '->' (not '->>')
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->>'status'))
+    WHERE (data->>'priority')::int > 5
+      AND (data->>'active')::boolean = true;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT update
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: Still 1 HOT
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+ count 
+-------
+     0
+(1 row)
+
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+INSERT INTO t VALUES (1, 50, 'below range');
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     150
+(1 row)
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     160
+(1 row)
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           4 |                100.00 | t
+(1 row)
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+SELECT id, value, description FROM t;
+ id | value |  description  
+----+-------+---------------
+  1 |    50 | updated again
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash((data->>'category'));
+INSERT INTO t VALUES (1, '{"category": "books", "title": "PostgreSQL Guide"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET data = '{"category": "books", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - NOT HOT
+UPDATE t SET data = '{"category": "videos", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET data = '{"category": "courses", "title": "PostgreSQL Basics"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_brin     |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT (BRIN allows it for single row)
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_hash     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (HASH blocks it)
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: 1 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT (BRIN permits single-row updates)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+-- Expected: 2 HOT (HASH blocks it)
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+-- Expected: 3 HOT
+DROP TABLE t CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..4459625a59b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: hot_expression_indexes
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/hot_expression_indexes.sql b/src/test/regress/sql/hot_expression_indexes.sql
new file mode 100644
index 00000000000..4929be144ae
--- /dev/null
+++ b/src/test/regress/sql/hot_expression_indexes.sql
@@ -0,0 +1,747 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->>'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->>'status'))
+    WHERE (docs->>'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->>'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression with COLLATION and BTREE (nbtree) index
+-- ================================================================
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    name TEXT COLLATE case_insensitive
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_lower_idx ON t USING BTREE (name COLLATE case_insensitive);
+
+INSERT INTO t VALUES (1, 'ALICE');
+
+-- Change case but not value - should NOT be HOT in BTREE
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+-- Change to new value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Nested JSONB Expression and JSONB equality '->' (not '->>')
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->>'status'))
+    WHERE (data->>'priority')::int > 5
+      AND (data->>'active')::boolean = true;
+
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t;
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT update
+
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: Still 1 HOT
+
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+
+INSERT INTO t VALUES (1, 50, 'below range');
+
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4, 't');
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+SELECT id, value, description FROM t;
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash((data->>'category'));
+INSERT INTO t VALUES (1, '{"category": "books", "title": "PostgreSQL Guide"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET data = '{"category": "books", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update indexed JSONB field - NOT HOT
+UPDATE t SET data = '{"category": "videos", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both - NOT HOT
+UPDATE t SET data = '{"category": "courses", "title": "PostgreSQL Basics"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+-- Expected: 1 HOT (BRIN allows it for single row)
+
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+-- Expected: 0 HOT (HASH blocks it)
+
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT
+
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't');
+-- Expected: 0 HOT
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT
+
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+-- Expected: 2 HOT (BRIN permits single-row updates)
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't');
+-- Expected: 1 HOT
+
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+-- Expected: 2 HOT
+
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2, 't');
+-- Expected: 2 HOT (HASH blocks it)
+
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3, 't');
+-- Expected: 3 HOT
+
+DROP TABLE t CASCADE;
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 23bce72ae64..52ef8f10b35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -390,6 +390,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.49.0

#36Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Greg Burd (#34)
Re: Expanding HOT updates for expression and partial indexes

On Wed, 19 Nov 2025 at 19:00, Greg Burd <greg@burd.me> wrote:

Attached are rebased (d5b4f3a6d4e) patches with the only changes
happening in the last patch in the series.

Here's a high-level review of the patchset, extending on what I shared
offline. I haven't looked too closely at the code changes.

Re: Perf testing

Apart from the workload we've discussed offline, there's another
workload to consider: Right now, we only really consider HOT when we
know there's space on the page. This patch, however, will front-load a
lot more checks before we have access to the page, and that will
~always impact update performance.
I'm a bit worried that the cost of off-page updates (when the page of
the old tuple can't fit the new tuple) will be too significantly
increased, especially considering that we have a default fillfactor of
100 -- if a table's tuples only grow, it's quite likely the table
frequently can't apply HOT regardless of the updated columns. So, a
workload that's tuned to only update tuples in a way that excercises
'can we HOT or not' code for already-full pages would be appreciated.
A solution to the issue might be needed if we lose too much
performance on expensive checks; a solution like passing page space of
the old tuple to the checks, and short-circuiting the non-HOT path if
that page space is too small for the new tuple.

0001:

I'm not sure I understand why the code is near completely duplicated
here. Maybe you can rename the resulting heap_update to
heap_update_ext, and keep a heap_update around which wraps this
heap_update_ext, allowing old callers to keep their signature until
they need to use _ext's features? Then you can introduce the
duplication if and when needed in later patches -- though I don't
expect a lot of this duplication to be strictly necessary, though that
may need some new helper functions.

I also see that for every update we're now copying, passing 5
bitmapsets individually and then freeing those bitmaps just a moment
later. I'd like to avoid that overhead and duplication if possible.
Maybe we can store these in an 'update context' struct, passed by
reference down to table_tuple_update() from the calling code, and then
onward to heap_update? That might then also be a prime candidate to
contain the EState * of ExecCheckIndexedAttrsForChanges.

0002:

This patch seems to have some formatting updates to changes you made
in 0001, without actually changing the code (e.g. at heap_update's
definition). When updating code, please put it in the expected
formatting in the same patch.

---
In the patch subject:

For instance,
indexes with collation information allowing more HOT updates when the
index is specified to be case insensitive.

It is incorrect to assume that indexed "btree-equal" datums allow HOT
updates. The user can not be returned an old datum in index-only
scans, even if it's sorted the same as the new datum -- after all, a
function on the datum may return different results even if the datums
are otherwise equal. Think: count_uppercase(string). See also below at
"HOT, datum compare, etc".

---
With the addition of rd_indexedattr we now have 6 bitmaps in
RelationData, which generally only get accessed through
RelationGetIndexAttrBitmap by an enum value. Maybe it's now time to
bite the bullet and change that to a more general approach with
`Bitmapset *rd_bitmaps[NUM_INDEX_ATTR_BITMAP]`? That way, the hot
path in RelationGetIndexAttrBitmap would not depend on the compiler to
determine that the fast path can do simple offset arithmatic to get
the requested bitmap.

0003:
This looks like it's a cleanup patch for 0002, and doesn't have much
standing on its own. Maybe the changes for ri_ChangedIndexedCols can
all be moved into 0003? I think that gives this patch more weight and
standing.

0004:
This does two things:
1. Add index expression evaluation to the toolset to determine which
indexes were unchanged, and
2. Allow index access methods to say "this value has not changed"
even if the datum itself may have changed.

Could that be split up into two different patches?

(aside: 2 makes a lot of sense in some cases, like trgm indexes
strings if no new trigrams are added/removed, so I really like the
idea behind this change)

HOT, datum compare, etc.:

Note that for index-only scans on an index to return correct results,
you _must_ update the index (and thus, do a non-HOT update) whenever a
value changes its binary datum, even if the value has the same btree
sort location as the old value. Even for non-IOS-supporting indexes,
the index may need more information than what's used in btree
comparisons when it has a btree opclass.
As SP/GIST have IOS support, they also need to compare the image of
the datum and not use ordinary equality as defined in nbtree's compare
function: the value must be exactly equal to what the table AM
would've provided.

Primary example: Btree compares the `numeric` datums of `1.0` and
`1.00` as equal, but for a user there is an observable difference; the
following SQL must return `1.0` in every valid plan:

BEGIN;
INSERT INTO mytab (mynumeric) VALUES ('1.00');
UPDATE mytab SET mynumeric = '1.0';
SELECT mynumeric FROM mytab;

So, IMO, the default datum compare in ExecCheckIndexedAttrsForChanges
and friends should just use datumIsEqual, and not this new
tts_attr_equal.
Indexes without IOS support might be able to opt into using a more lax
datum comparator, but 1.) it should never be the default, as it'd be a
loaded footgun for IndexAM implementers, and 2.) should not depend on
another AM's understanding of attributes, as that is a very leaky
abstraction.

Kind regards,

Matthias van de Meent
Databricks (https://www.databricks.com)

#37Greg Burd
greg@burd.me
In reply to: Matthias van de Meent (#36)
4 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

On Nov 21 2025, at 10:25 am, Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:

On Wed, 19 Nov 2025 at 19:00, Greg Burd <greg@burd.me> wrote:

Attached are rebased (d5b4f3a6d4e) patches with the only changes
happening in the last patch in the series.

Here's a high-level review of the patchset, extending on what I shared
offline. I haven't looked too closely at the code changes.

Matthias, thanks for spending a bit of time writing up your thoughts and
for chatting a bit with me before doing so. I really appreciate your
point of view.

Re: Perf testing

Apart from the workload we've discussed offline, there's another
workload to consider: Right now, we only really consider HOT when we
know there's space on the page.

Yes, and no. In master every UPDATE triggers a call into
HeapDetermineColumnsInfo() in heap_update(). That function's job is to
examine all the indexed attributes checking each attribute for changes.
The set of indexed attributes is generated by combining a set of
Bitmapsets fetched (copied) from the relcache:

hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_HOT_BLOCKING);
sum_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_SUMMARIZED);
key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
id_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);

interesting_attrs = NULL;
interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
interesting_attrs = bms_add_members(interesting_attrs, id_attrs);

These "interesting attributes" are each checked comparing the newly
formed updated HeapTuple passed in from the executor against a HeapTuple
read from the page. This happens after taking the buffer lock on that
page. The comparison is done with heap_attr_compare() which calls
datumIsEqual() which boils down to a memcmp(). The function returns the
"modified_attrs".

This is the primary "work" that happens before considering HOT that I've
moved outside the buffer lock and into the executor. The rest of the
HOT decision is 1) will it fit (possibly after being TOASTed), and b) do
the HOT blocking attributes overlap with the modified_attrs? If they
do, unless they are all summarizing you can't go HOT.

Net new work with my approach is only related to the more complicated
checks that have to be done when there are expressions, partial indexes,
or an index implements the new index AM API for comparing datums. In
those cases there is a bit more work, especially when it comes to
expression indexes. Evaluating partial index predicate twice rather
than once is a bit more overhead. Using type-specific comparison when
the index doesn't support index-only scans is a tad more. Overall, not
a whole lot of new work. Certainly a much more complex code path, and
maybe that'll show up in performance tests. I don't know yet.

This patch, however, will front-load a
lot more checks before we have access to the page, and that will
~always impact update performance.

Disagree, 90% of that work happens today on that same path after the
page lock. Even in the case that the HeapTuple can't fit into the page
that work will happen, there's not much net new "wasted" effort.

Concurrency under load may be better because buffer locks will be held
for less time.

I'm a bit worried that the cost of off-page updates (when the page of
the old tuple can't fit the new tuple) will be too significantly
increased, especially considering that we have a default fillfactor of
100 -- if a table's tuples only grow, it's quite likely the table
frequently can't apply HOT regardless of the updated columns. So, a
workload that's tuned to only update tuples in a way that excercises
'can we HOT or not' code for already-full pages would be appreciated.
A solution to the issue might be needed if we lose too much
performance on expensive checks; a solution like passing page space of
the old tuple to the checks, and short-circuiting the non-HOT path if
that page space is too small for the new tuple.

Here's the problem, there's not much that can be known about what
ultimate size the HeapTuple will take or if the page can hold it until
after the lock.

Second, I feel that this signal would be specific to the heap, it's
particular MVCC implementation, and it's optimization (HOT). I really
wanted this solution to be non-heap-specific, but heap-enabling.

For me that meant that in the general case the executor should be
concerned with updating only the set of indexes that it must and no
more. So performing this work in the executor ahead of calling into the
table AM or index AM makes sense.

It is only due to heap's "special" model that we have other concerns
related to HOT. Sure, everyone/everything today uses heap so we should
pay attention to this, but I set out not to create yet another thing
that depends on heap's specific operational model. I think I did that.

0001:

I'm not sure I understand why the code is near completely duplicated
here. Maybe you can rename the resulting heap_update to
heap_update_ext, and keep a heap_update around which wraps this
heap_update_ext, allowing old callers to keep their signature until
they need to use _ext's features? Then you can introduce the
duplication if and when needed in later patches -- though I don't
expect a lot of this duplication to be strictly necessary, though that
may need some new helper functions.

This is just a split where I move the top portion of heap_update() into
the two paths that use it. Sure I could have pulled that into another
function, but in this series the next step is to obliterate one half and
(if I get my other patch in cf-6221) then I can completely remove
HeapDetermineColumnsInfo() and vastly simplify simple_heap_update().

I also see that for every update we're now copying, passing 5
bitmapsets individually and then freeing those bitmaps just a moment
later. I'd like to avoid that overhead and duplication if possible.

We do this today, nothing new here. They are passed by reference, not
value into heap_update().

Maybe we can store these in an 'update context' struct, passed by
reference down to table_tuple_update() from the calling code, and then
onward to heap_update? That might then also be a prime candidate to
contain the EState * of ExecCheckIndexedAttrsForChanges.

I've explored using UpdateContext before, not a bad idea but again this
is just a setup commit. It could be cleaner on it's own, but it doesn't
really take a step backward on any dimension.

0002:

This patch seems to have some formatting updates to changes you made
in 0001, without actually changing the code (e.g. at heap_update's
definition). When updating code, please put it in the expected
formatting in the same patch.

I'll find/fix those after v23 attached, sorry for the noise.

---
In the patch subject:

For instance,
indexes with collation information allowing more HOT updates when the
index is specified to be case insensitive.

It is incorrect to assume that indexed "btree-equal" datums allow HOT
updates. The user can not be returned an old datum in index-only
scans, even if it's sorted the same as the new datum -- after all, a
function on the datum may return different results even if the datums
are otherwise equal. Think: count_uppercase(string). See also below at
"HOT, datum compare, etc".

Thanks for pointing out the oversight for index-oriented scans (IOS),
you're right that the code in v22 doesn't handle that correctly. I'll
fix that. I still think that indexes that don't support IOS can and
should use the type-specific equality checks. This opens the door to
HOT with custom types that have unusual equality rules (see BSON).

With the addition of rd_indexedattr we now have 6 bitmaps in
RelationData, which generally only get accessed through
RelationGetIndexAttrBitmap by an enum value. Maybe it's now time to
bite the bullet and change that to a more general approach with
`Bitmapset *rd_bitmaps[NUM_INDEX_ATTR_BITMAP]`? That way, the hot
path in RelationGetIndexAttrBitmap would not depend on the compiler to
determine that the fast path can do simple offset arithmatic to get
the requested bitmap.

More than a few of those bitmaps in RelationData are purely for the HOT
tests, yes. I'll review those and see if I can shrink the set
meaningfully. It was something that had occurred to me too. I'll
review and consider ways to consolidate them.

0003:
This looks like it's a cleanup patch for 0002, and doesn't have much
standing on its own. Maybe the changes for ri_ChangedIndexedCols can
all be moved into 0003? I think that gives this patch more weight and
standing.

The goal in this patch was to show that we could eliminate the redundant
set work of index_unchanged_by_update() in execIndexing's update path.
Separating it out was done to make it more easily reviewed and to prove
that before/after tests passed making the change safe.

0004:
This does two things:
1. Add index expression evaluation to the toolset to determine which
indexes were unchanged, and
2. Allow index access methods to say "this value has not changed"
even if the datum itself may have changed.

Yes, that's what it does. :)

Could that be split up into two different patches?

Maybe, I might be able to add the index AM piece first and then the
expression piece.

(aside: 2 makes a lot of sense in some cases, like trgm indexes
strings if no new trigrams are added/removed, so I really like the
idea behind this change).

Nice, I appreciate that.

HOT, datum compare, etc.:

Note that for index-only scans on an index to return correct results,
you _must_ update the index (and thus, do a non-HOT update) whenever a
value changes its binary datum, even if the value has the same btree
sort location as the old value.

Yes, I get this now. I also wasn't testing the INCLUDING (non-key)
columns. I've fixed both of those in v23.

Even for non-IOS-supporting indexes,
the index may need more information than what's used in btree
comparisons when it has a btree opclass.

It's not always just the btree opclass for equality, but that is common.

As SP/GIST have IOS support, they also need to compare the image of
the datum and not use ordinary equality as defined in nbtree's compare
function: the value must be exactly equal to what the table AM
would've provided.

In v23 I've changed the logic to use datumIsEqual() for any index that
supports IOS and doesn't supply a custom amcomparedatums() function.

Primary example: Btree compares the `numeric` datums of `1.0` and
`1.00` as equal, but for a user there is an observable difference; the
following SQL must return `1.0` in every valid plan:

BEGIN;
INSERT INTO mytab (mynumeric) VALUES ('1.00');
UPDATE mytab SET mynumeric = '1.0';
SELECT mynumeric FROM mytab;

I'll try to reproduce this and add a test if I can.

So, IMO, the default datum compare in ExecCheckIndexedAttrsForChanges
and friends should just use datumIsEqual, and not this new
tts_attr_equal.

Sure, and that's the case in v23. tts_attr_equal() still has value in
other cases so it's not gone.

Indexes without IOS support might be able to opt into using a more lax
datum comparator, but 1.) it should never be the default, as it'd be a
loaded footgun for IndexAM implementers, and 2.) should not depend on
another AM's understanding of attributes, as that is a very leaky
abstraction.

Agree, which is why the default for IOS indexes is datumIsEqual() now.

Kind regards,

Matthias van de Meent
Databricks (https://www.databricks.com)

Thanks again for the time and renewed interest in the patch! I've also
added a lot more tests into the heap_hot_updates.sql regression suite,
likely too many, but for now it's good to be testing the corners.

v23 attached, changes are all in 0004, best.

-greg

Attachments:

v23-0001-Reorganize-heap-update-logic.patchapplication/octet-streamDownload
From 1acc8057e389b32e55915336d88070958a0ddd8c Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v23 1/4] Reorganize heap update logic

This commit refactors the interaction between heap_tuple_update(),
heap_update(), and simple_heap_update() to improve code organization
and flexibility. The changes are functionally equivalent to the
previous implementation and have no performance impact.

The primary motivation is to prepare for upcoming modifications to
how and where modified attributes are identified during the update
path, particularly for catalog updates.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().

No user-visible changes.
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4b0c49f4bb0..aff47481345 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -103,10 +101,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2799,6 +2797,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2811,6 +2810,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3014,6 +3015,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3035,7 +3037,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3247,7 +3252,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3255,30 +3263,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3292,7 +3291,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3300,144 +3298,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3449,7 +3316,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3473,17 +3340,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3515,8 +3375,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3556,13 +3416,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3572,9 +3431,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3599,8 +3458,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3641,9 +3500,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3653,20 +3512,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3679,39 +3538,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3724,10 +3577,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3738,9 +3591,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3752,12 +3605,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3772,7 +3625,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3800,7 +3653,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3817,12 +3670,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3855,9 +3708,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3867,18 +3720,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3887,7 +3740,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3901,10 +3754,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3926,7 +3779,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3962,20 +3815,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4013,7 +3866,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4021,7 +3874,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4029,7 +3881,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4040,7 +3892,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4057,10 +3909,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4082,7 +3932,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4091,7 +3941,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4100,17 +3950,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4118,7 +3968,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4143,12 +3993,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4173,7 +4023,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4181,14 +4031,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4221,13 +4071,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4236,7 +4079,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4408,7 +4251,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4491,25 +4334,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9149,12 +9142,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9185,17 +9177,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9208,7 +9196,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9217,8 +9205,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..1cf9a18775d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 632c4332a8c..2f9a2b069cd 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -364,11 +364,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -430,6 +432,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.49.0

v23-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From 6a6c28b8ee25a0ef04d65aa54c4792fa8ada03fc Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v23 2/4] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo in heap_update. This enables the comparison
to happen without taking a lock on the page and opens the door to reuse
in other code paths.

Because heap_update now requires the caller to provide the modified
indexed columns simple_heap_update has become a tad more complex.  It is
frequently called from CatalogTupleUpdate which either updates heap
tuples via their form or using heap_modify_tuple.  In both cases the
caller does know the modified set of attributes, but sadly those
attributes are lost before being provided to simple_heap_update.  Due to
that the "simple" path has to retain the HeapDetermineColumnsInfo logic
of old (for now).  In order for that to work it was necessary to split
the (overly large) heap_update call itself up.  This moves up into
simple_heap_update and heap_tuple_update a bit of what existed in
heap_update itself.  Ideally this will be cleaned up once
CatalogTupleUpdate paths are all recording modified attributes
correctly, when that happens the "simple" path can be simplified again.

ExecCheckIndexedAttrsForChanges replaces HeapDeterminesColumnsInfo and
tts_attr_equal replaces heap_attr_equal changing the test for equality
when calling into heap_tuple_update (but not simple_heap_update).  In
the past we used datumIsEqual(), essentially a binary comparison using
memcmp(), now the comparison code in tts_attr_equal uses type-specific
equality function when available and falls back to datumIsEqual() when
not.  This change in equality testing has some intended implications and
opens the door for more HOT updates (foreshadowing).  For instance,
indexes with collation information allowing more HOT updates when the
index is specified to be case insensitive.

This change forced some logic changes in execReplication on the update
paths is now it is required to have knowledge of the set of attributes
that are both changed and referenced by indexes.  Luckilly, the this is
available within calls to slot_modify_data() where LogicalRepTupleData
is processed and has a set of updated attributes.  In this case rather
than using ExecCheckIndexedAttrsForChanges we can preseve what
slot_modify_data() identifies as the modified set and then intersect
that with the set of indexes on the relation and get the correct set of
modified indexed attributes required on heap_update().
---
 src/backend/access/heap/heapam.c         |  12 +-
 src/backend/access/heap/heapam_handler.c |  72 +++++--
 src/backend/access/table/tableam.c       |   5 +-
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/execReplication.c   |   7 +
 src/backend/executor/nodeModifyTable.c   | 247 ++++++++++++++++++++++-
 src/backend/nodes/bitmapset.c            |   4 +
 src/backend/replication/logical/worker.c |  72 ++++++-
 src/backend/utils/cache/relcache.c       |  15 ++
 src/include/access/tableam.h             |   8 +-
 src/include/executor/executor.h          |   5 +
 src/include/nodes/execnodes.h            |   1 +
 src/include/utils/rel.h                  |   1 +
 src/include/utils/relcache.h             |   1 +
 14 files changed, 415 insertions(+), 36 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index aff47481345..1cdb72b3a7a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3263,12 +3263,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1cf9a18775d..ef08e1d3e10 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -414,16 +416,61 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +484,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 5e41404937e..dadcf03ed24 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,6 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  Bitmapset *modified_indexed_cols,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -346,7 +347,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								modified_indexed_cols,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 27c9eec697b..6b7b6bc8019 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index def32774c90..2709e2db0f2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 00429326c34..34f86546fc9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -54,11 +55,14 @@
 
 #include "access/htup_details.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,6 +72,8 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
@@ -176,6 +182,219 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   bool canSetTag);
 
 
+/*
+ * Compare two datums using the type's default equality operator.
+ *
+ * Returns true if the values are equal according to the type's equality
+ * operator, false otherwise. Falls back to binary comparison if no
+ * type-specific operator is available.
+ *
+ * This function uses the TypeCache infrastructure which caches operator
+ * lookups for efficiency.
+ */
+bool
+tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+			   Datum value1, Datum value2)
+{
+	TypeCacheEntry *typentry;
+
+	LOCAL_FCINFO(fcinfo, 2);
+	Datum		result;
+
+	/*
+	 * Fast path for common types to avoid even the type cache lookup. These
+	 * types have simple equality semantics.
+	 */
+	switch (typid)
+	{
+		case INT2OID:
+			return DatumGetInt16(value1) == DatumGetInt16(value2);
+		case INT4OID:
+			return DatumGetInt32(value1) == DatumGetInt32(value2);
+		case INT8OID:
+			return DatumGetInt64(value1) == DatumGetInt64(value2);
+		case FLOAT4OID:
+			return !float4_cmp_internal(DatumGetFloat4(value1), DatumGetFloat4(value2));
+		case FLOAT8OID:
+			return !float8_cmp_internal(DatumGetFloat8(value1), DatumGetFloat8(value2));
+		case BOOLOID:
+			return DatumGetBool(value1) == DatumGetBool(value2);
+		case OIDOID:
+		case REGPROCOID:
+		case REGPROCEDUREOID:
+		case REGOPEROID:
+		case REGOPERATOROID:
+		case REGCLASSOID:
+		case REGTYPEOID:
+		case REGROLEOID:
+		case REGNAMESPACEOID:
+		case REGCONFIGOID:
+		case REGDICTIONARYOID:
+			return DatumGetObjectId(value1) == DatumGetObjectId(value2);
+		case CHAROID:
+			return DatumGetChar(value1) == DatumGetChar(value2);
+		default:
+			/* Continue to type cache lookup */
+			break;
+	}
+
+	/*
+	 * Look up the type's equality operator using the type cache. Request both
+	 * the operator OID and the function info for efficiency.
+	 */
+	typentry = lookup_type_cache(typid,
+								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
+
+	/*
+	 * If no equality operator is available, fall back to binary comparison.
+	 * This handles types that don't have proper equality operators defined.
+	 */
+	if (!OidIsValid(typentry->eq_opr))
+		return datumIsEqual(value1, value2, typbyval, typlen);
+
+	/*
+	 * Use the cached function info if available, otherwise look it up. The
+	 * type cache keeps this around so subsequent calls are fast.
+	 */
+	if (typentry->eq_opr_finfo.fn_addr == NULL)
+	{
+		Oid			eq_proc = get_opcode(typentry->eq_opr);
+
+		if (!OidIsValid(eq_proc))
+			/* Shouldn't happen, but fall back to binary comparison */
+			return datumIsEqual(value1, value2, typbyval, typlen);
+
+		fmgr_info_cxt(eq_proc, &typentry->eq_opr_finfo,
+					  CacheMemoryContext);
+	}
+
+	/* Set up function call */
+	InitFunctionCallInfoData(*fcinfo, &typentry->eq_opr_finfo, 2,
+							 collation, NULL, NULL);
+
+	fcinfo->args[0].value = value1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = value2;
+	fcinfo->args[1].isnull = false;
+
+	/* Invoke the equality operator */
+	result = FunctionCallInvoke(fcinfo);
+
+	/*
+	 * If the function returned NULL (shouldn't happen for equality ops),
+	 * treat as not equal for safety.
+	 */
+	if (fcinfo->isnull)
+		return false;
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Determine which updated attributes actually changed values between old and
+ * new tuples and are referenced by indexes on the relation.
+ *
+ * Returns a Bitmapset of attribute offsets (0-based, adjusted by
+ * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								TupleTableSlot *tts_old,
+								TupleTableSlot *tts_new)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *indexed_attrs;
+	Bitmapset  *modified = NULL;
+	int			attidx;
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/*
+	 * Get the set of index key attributes.  This includes summarizing,
+	 * expression indexes and attributes mentioned in the predicate of a
+	 * partition but not those in INCLUDING.
+	 */
+	indexed_attrs = RelationGetIndexAttrBitmap(relation,
+											   INDEX_ATTR_BITMAP_INDEXED);
+	Assert(!bms_is_empty(indexed_attrs));
+
+	/*
+	 * NOTE: It is important to scan all indexed attributes in the tuples
+	 * because ExecGetAllUpdatedCols won't include columns that may have been
+	 * modified via heap_modify_tuple_by_col which is the case in
+	 * tsvector_update_trigger.
+	 */
+	attidx = -1;
+	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	{
+		/* attidx is zero-based, attrnum is the normal attribute number */
+		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+		Form_pg_attribute attr;
+		bool		oldnull,
+					newnull;
+		Datum		oldval,
+					newval;
+
+		/*
+		 * If it's a whole-tuple reference, record as modified.  It's not
+		 * really worth supporting this case, since it could only succeed
+		 * after a no-op update, which is hardly a case worth optimizing for.
+		 */
+		if (attrnum == 0)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/*
+		 * Likewise, include in the modified set any system attribute other
+		 * than tableOID; we cannot expect these to be consistent in a HOT
+		 * chain, or even to be set correctly yet in the new tuple.
+		 */
+		if (attrnum < 0)
+		{
+			if (attrnum != TableOidAttributeNumber)
+				modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* Extract values from both slots */
+		oldval = slot_getattr(tts_old, attrnum, &oldnull);
+		newval = slot_getattr(tts_new, attrnum, &newnull);
+
+		/* If one value is NULL and the other is not, they are not equal */
+		if (oldnull != newnull)
+		{
+			modified = bms_add_member(modified, attidx);
+			continue;
+		}
+
+		/* If both are NULL, consider them equal */
+		if (oldnull)
+			continue;
+
+		/* Get attribute metadata */
+		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
+		attr = TupleDescAttr(tupdesc, attrnum - 1);
+
+		/* Compare using type-specific equality operator */
+		if (!tts_attr_equal(attr->atttypid,
+							attr->attcollation,
+							attr->attbyval,
+							attr->attlen,
+							oldval,
+							newval))
+			modified = bms_add_member(modified, attidx);
+	}
+
+	bms_free(indexed_attrs);
+
+	return modified;
+}
+
 /*
  * Verify that the tuples to be produced by INSERT match the
  * target relation's rowtype
@@ -2168,8 +2387,8 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2291,6 +2510,16 @@ lreplace:
 	if (resultRelationDesc->rd_att->constr)
 		ExecConstraints(resultRelInfo, slot, estate);
 
+	/*
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	resultRelInfo->ri_ChangedIndexedCols =
+		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+
 	/*
 	 * replace the heap tuple
 	 *
@@ -2306,6 +2535,7 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								resultRelInfo->ri_ChangedIndexedCols,
 								&updateCxt->updateIndexes);
 
 	return result;
@@ -2524,8 +2754,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3222,8 +3453,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3248,6 +3479,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4354,7 +4586,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4530,6 +4762,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index b4ecf0b0390..9014990267a 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 93970c6af29..b363eaa49cc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -243,6 +243,8 @@
  */
 
 #include "postgres.h"
+#include "access/sysattr.h"
+#include "nodes/bitmapset.h"
 
 #include <sys/stat.h>
 #include <unistd.h>
@@ -275,7 +277,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -291,6 +292,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1112,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1200,28 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+				if (!tts_attr_equal(att->atttypid,
+									att->attcollation,
+									att->attbyval,
+									att->attlen,
+									srcslot->tts_values[i],
+									slot->tts_values[i]))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1229,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2947,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2964,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2989,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3501,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3571,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3597,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..32825596be1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2482,6 +2482,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5284,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5307,6 +5309,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5332,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5373,6 +5378,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5505,10 +5511,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5531,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5535,6 +5547,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5551,6 +5564,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index e16bf025692..8a5931a3118 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1502,12 +1503,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 updated_cols, update_indexes);
 }
 
 /*
@@ -2010,6 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  Bitmapset *modified_indexe_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..993dc0e6ced 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -800,5 +800,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
+												  TupleTableSlot *tts_old,
+												  TupleTableSlot *tts_new);
+extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
+						   Datum value1, Datum value2);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..8b08e0045ba 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -498,6 +498,7 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 80286076a11..b23a7306e69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..d3fbb8b093a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
-- 
2.49.0

v23-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From ce0e50aa8b14f838f04b8b45af8a47f5c4e73510 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v23 3/4] Replace index_unchanged_by_update with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update with the same information
found earlier in the update path.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 874a8fc89ad..5d819bda54a 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -300,8 +300,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 401606f840a..fb1bc3a480d 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -138,11 +138,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -440,10 +435,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -993,152 +985,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..d69dc090aa4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8b08e0045ba..898368fb8cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -202,10 +202,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.49.0

v23-0004-Enable-HOT-updates-for-expression-and-partial-in.patchapplication/octet-streamDownload
From cad236149af2d2a4f5762334d0d96ba13443b022 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v23 4/4] Enable HOT updates for expression and partial indexes

Currently, PostgreSQL conservatively prevents HOT (Heap-Only Tuple)
updates whenever any indexed column changes, even if the indexed
portion of that column remains identical. This is overly restrictive
for expression indexes (where f(column) might not change even when
column changes) and partial indexes (where both old and new tuples
might fall outside the predicate).  Finally, index AMs play no role
in deciding when they need a new index entry on update, the rules
regarding that are based on binary equality and the HEAP's model for
MVCC and related HOT optimization.  Here we open that door a bit so
as to enable more nuanced control over the process.  This enables
index AMs that require binary equality (as is the case for nbtree)
to do that without disallowing type-specific equality checking for
other indexes.

This patch introduces several improvements to enable HOT updates in
these cases:

Add amcomparedatums() callback to IndexAmRoutine. This allows index
access methods like GIN to provide custom logic for comparing datums by
extracting and comparing index keys rather than comparing the raw
datums. GIN indexes now implement gincomparedatums() which extracts keys
from both datums and compares the resulting key sets.  Also, as
mentioned earlier nbtree implements this API and uses datumIsEqual() for
equality so that the manner in which it deduplicates TIDs on page split
doesn't have to change.  This is not a required API, when not
implemented the executor will compare TupleTableSlot datum for equality
using type-specific operators and take into account collation so that an
update from "Apple" to "APPLE" on a case insensitive index can now be
HOT.

ExecWhichIndexesRequireUpdates() is re-written to find the set of
modified indexed attributes that trigger new index tuples on updated.
For partial indexes, this checks whether both old and new tuples satisfy
or fail the predicate. For expression indexes, this uses type-specific
equality operators to compare computed values. For extraction-based
indexes (GIN/RUM) that implement amcomparedatums() it uses that.

Importantly, table access methods can still signal using TU_Update if
all, none, or only summarizing indexes should be updated.  While the
executor layer now owns determining what has changed due to an update
and is interested in only updating the minimum number of indexes
possible, the table AM can override that while performing
table_tuple_update(), which is what heap does.  While this signal is
very specific to how the heap implements MVCC and its HOT optimization,
we'll leave replacing that for another day.

This optimization trades off some new overhead for the potential for
more updates to use the HOT optimized path and avoid index and heap
bloat.  This should significantly improve update performance for tables
with expression indexes, partial indexes, and GIN/GiST indexes on
complex data types like JSONB and tsvector, while maintaining correct
index semantics.  Minimal additional overhead due to type-specific
equality checking should be washed out by the benefits of updating
indexes fewer times.

One notable trade-off is that there are more calls to FormIndexDatum()
as a result.  Caching these might reduce some of that overhead, but not
all.  This lead to the change in the frequency for expressions in the
spec update test to output notice messages, but does not impact
correctness.
---
 src/backend/access/brin/brin.c                |    1 +
 src/backend/access/gin/ginutil.c              |   92 +-
 src/backend/access/hash/hash.c                |   44 +
 src/backend/access/heap/heapam.c              |   10 +-
 src/backend/access/heap/heapam_handler.c      |    6 +-
 src/backend/access/nbtree/nbtree.c            |    1 +
 src/backend/access/table/tableam.c            |    4 +-
 src/backend/bootstrap/bootstrap.c             |    8 +
 src/backend/catalog/index.c                   |   54 +
 src/backend/catalog/indexing.c                |   16 +-
 src/backend/catalog/toasting.c                |    4 +
 src/backend/executor/execIndexing.c           |   45 +-
 src/backend/executor/nodeModifyTable.c        |  496 ++++-
 src/backend/nodes/makefuncs.c                 |    4 +
 src/include/access/amapi.h                    |   28 +
 src/include/access/gin.h                      |    3 +
 src/include/access/heapam.h                   |    6 +-
 src/include/access/nbtree.h                   |    4 +
 src/include/access/tableam.h                  |    8 +-
 src/include/catalog/index.h                   |    1 +
 src/include/executor/executor.h               |   12 +-
 src/include/nodes/execnodes.h                 |   19 +
 .../expected/insert-conflict-specconflict.out |   20 +
 .../regress/expected/heap_hot_updates.out     | 1922 +++++++++++++++++
 src/test/regress/parallel_schedule            |    6 +
 src/test/regress/sql/heap_hot_updates.sql     | 1325 ++++++++++++
 src/tools/pgindent/typedefs.list              |    1 +
 27 files changed, 4015 insertions(+), 125 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index cb3331921cb..36e639552e6 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -290,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = NULL;
 	amroutine->ambeginscan = brinbeginscan;
 	amroutine->amrescan = brinrescan;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 78f7b7a2495..8e31ec21c1c 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = ginbuildphasename;
 	amroutine->amvalidate = ginvalidate;
+	amroutine->amcomparedatums = gincomparedatums;
 	amroutine->amadjustmembers = ginadjustmembers;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -729,3 +724,86 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/* Extract keys from both datums using existing GIN infrastructure */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys, definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		int			cmp = ginCompareEntries(&ginstate, attnum,
+											old_keys[i], old_categories[i],
+											new_keys[i], new_categories[i]);
+
+		if (cmp != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 53061c819fb..91371dfdacd 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -50,6 +50,10 @@ static void hashbuildCallback(Relation index,
 							  void *state);
 
 
+static bool hashcomparedatums(Relation index, int attnum,
+							  Datum old_datum, bool old_isnull,
+							  Datum new_datum, bool new_isnull);
+
 /*
  * Hash handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -98,6 +102,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = hashvalidate;
+	amroutine->amcomparedatums = hashcomparedatums;
 	amroutine->amadjustmembers = hashadjustmembers;
 	amroutine->ambeginscan = hashbeginscan;
 	amroutine->amrescan = hashrescan;
@@ -944,3 +949,42 @@ hashtranslatecmptype(CompareType cmptype, Oid opfamily)
 		return HTEqualStrategyNumber;
 	return InvalidStrategy;
 }
+
+/*
+ * hashcomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * Returns true if the hash values are identical (index doesn't need update).
+ */
+bool
+hashcomparedatums(Relation index, int attnum,
+				  Datum old_datum, bool old_isnull,
+				  Datum new_datum, bool new_isnull)
+{
+	uint32		old_hashkey;
+	uint32		new_hashkey;
+
+	/* If both are NULL, they're equal */
+	if (old_isnull && new_isnull)
+		return true;
+
+	/* If NULL status differs, they're not equal */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * _hash_datum2hashkey() is used because we know this can't be a cross
+	 * type comparison.
+	 */
+	old_hashkey = _hash_datum2hashkey(index, old_datum);
+	new_hashkey = _hash_datum2hashkey(index, new_datum);
+
+	/*
+	 * If hash keys are identical, the index entry would be the same. Return
+	 * true to indicate no index update needed.
+	 *
+	 * Note: Hash collisions are rare but possible. If hash(x) == hash(y) but
+	 * x != y, the hash index still treats them identically, so we correctly
+	 * return true.
+	 */
+	return (old_hashkey == new_hashkey);
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1cdb72b3a7a..5b0ff13b13d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3268,7 +3268,7 @@ heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			Buffer buffer, Page page, BlockNumber block, ItemId lp,
 			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
-			Bitmapset *rid_attrs, Bitmapset *mix_attrs, Buffer *vmbuffer,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4337,8 +4337,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4467,7 +4468,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4500,7 +4501,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4526,6 +4526,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ef08e1d3e10..7527809ec08 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -319,7 +319,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 					Snapshot crosscheck, bool wait,
 					TM_FailureData *tmfd,
 					LockTupleMode *lockmode,
-					Bitmapset *mix_attrs,
+					const Bitmapset *mix_attrs,
 					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
@@ -407,10 +407,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fdff960c130..e435f0d5db4 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -155,6 +155,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = btadjustmembers;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dadcf03ed24..ef7736bfa76 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -336,7 +336,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
-						  Bitmapset *modified_indexed_cols,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -348,7 +348,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode,
-								modified_indexed_cols,
+								mix_attrs,
 								update_indexes);
 
 	switch (result)
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index fc8638c1b61..329c110d0bf 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -961,10 +961,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..e88db7e919b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2414,6 +2416,58 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		indnatts = ii->ii_NumIndexAttrs;
+
+		/* Collect key attributes used by the index, key and including */
+		for (i = 0; i < indnatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		/* Combine key, including, and expression attributes, but not predicate */
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..a361c215490 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 5d819bda54a..c665aa744b3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index fb1bc3a480d..20968a814d6 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
@@ -318,8 +322,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -343,13 +347,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -366,7 +370,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -387,8 +391,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -396,11 +407,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -435,7 +445,9 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
+		indexUnchanged = update &&
+			!bms_overlap(indexInfo->ii_IndexedAttrs,
+						 resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -604,7 +616,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 34f86546fc9..ea50fcaf5dd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -54,10 +54,13 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/attnum.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -75,6 +78,7 @@
 #include "utils/float.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -245,6 +249,10 @@ tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 	typentry = lookup_type_cache(typid,
 								 TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
 
+	/* Use the type's collation if none provided */
+	if (collation == -1)
+		collation = typentry->typcollation;
+
 	/*
 	 * If no equality operator is available, fall back to binary comparison.
 	 * This handles types that don't have proper equality operators defined.
@@ -291,108 +299,415 @@ tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 }
 
 /*
- * Determine which updated attributes actually changed values between old and
- * new tuples and are referenced by indexes on the relation.
+ * ExecCheckIndexedAttrsForChanges
+ *
+ * Determine which indexes need updating by finding the set of modified
+ * indexed attributes.
+ *
+ * For expression indexes and indexes which implement the amcomparedatums()
+ * index AM API we'll need to form index datum and compare each attribute to
+ * see if any actually changed.
+ *
+ * For expression indexes the result of the expression might not change at all,
+ * this is common with JSONB columns, which require expression indexes.  It is
+ * is commonplace to index one or more fields within a document and perform
+ * updates to the document while leaving the indexed fields unchanged.  These
+ * updates don't necessitate index updates.
+ *
+ * Partial indexes won't trigger index updates when the old/new tuples are both
+ * outside of the predicate range.  A transition into or out of the predicate
+ * does require an index update.
+ *
+ * Indexes that support index-only scans (IOS) should return the value that
+ * is the binary equavalent of what is in the table.  For that reason we must
+ * use datumIsEqual() when deciding if an index update is required or not.
+ *
+ * All other indexes require testing old/new datum for equality, we now test
+ * with a type-specific equality operator and fall back to datumIsEqual()
+ * when that isn't possible.
+ *
+ * For a BTREE index (nbtree) their is an additional reason to use binary
+ * comparison for equality.  TID deduplication on page split in nbtree uses
+ * binary comparison.
+ *
+ * The goal is for the executor to know, ahead of calling into the table AM to
+ * process the update and before calling into the index AM for inserting new
+ * index tuples, which attributes in the new TupleTableSlot, if any, truely
+ * necessitate a new index tuple.
  *
- * Returns a Bitmapset of attribute offsets (0-based, adjusted by
- * FirstLowInvalidHeapAttributeNumber) or NULL if no attributes changed.
+ * Returns a Bitmapset of attributes that intersects with indexes which require
+ * a new index tuple.
  */
 Bitmapset *
 ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
-								TupleTableSlot *tts_old,
-								TupleTableSlot *tts_new)
+								EState *estate,
+								TupleTableSlot *old_tts,
+								TupleTableSlot *new_tts)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(relation);
-	Bitmapset  *indexed_attrs;
-	Bitmapset  *modified = NULL;
-	int			attidx;
+	Bitmapset  *mix_attrs = NULL;
 
 	/* If no indexes, we're done */
 	if (relinfo->ri_NumIndices == 0)
 		return NULL;
 
 	/*
-	 * Get the set of index key attributes.  This includes summarizing,
-	 * expression indexes and attributes mentioned in the predicate of a
-	 * partition but not those in INCLUDING.
+	 * NOTE: Expression and predicates that are observed to change will have
+	 * all their attributes added into the m_attrs set knowing that some of
+	 * those might not have changed.  Take for instance an index on (a + b)
+	 * followed by an index on (b) with an update that changes only the value
+	 * of 'a'.  We'll add both 'a' and 'b' to the m_attrs set then later when
+	 * reviewing the second index add 'b' to the u_attrs (unchanged) set.  In
+	 * the end, we'll remove all the unchanged from the m_attrs and get our
+	 * desired result.
 	 */
-	indexed_attrs = RelationGetIndexAttrBitmap(relation,
-											   INDEX_ATTR_BITMAP_INDEXED);
-	Assert(!bms_is_empty(indexed_attrs));
 
-	/*
-	 * NOTE: It is important to scan all indexed attributes in the tuples
-	 * because ExecGetAllUpdatedCols won't include columns that may have been
-	 * modified via heap_modify_tuple_by_col which is the case in
-	 * tsvector_update_trigger.
-	 */
-	attidx = -1;
-	while ((attidx = bms_next_member(indexed_attrs, attidx)) >= 0)
+	/* Find the indexes that reference this attribute */
+	for (int i = 0; i < relinfo->ri_NumIndices; i++)
 	{
-		/* attidx is zero-based, attrnum is the normal attribute number */
-		AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
-		Form_pg_attribute attr;
-		bool		oldnull,
-					newnull;
-		Datum		oldval,
-					newval;
+		Relation	index = relinfo->ri_IndexRelationDescs[i];
+		IndexAmRoutine *amroutine = index->rd_indam;
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		Bitmapset  *m_attrs = NULL; /* (possibly) modified key attributes */
+		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate
+									 * attributes */
+		Bitmapset  *u_attrs = NULL; /* unmodified attributes */
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
+		bool		has_expressions = (indexInfo->ii_Expressions != NIL);
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		supports_ios = (amroutine->amcanreturn != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		TupleTableSlot *save_scantuple;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		Datum		old_values[INDEX_MAX_KEYS];
+		bool		old_isnull[INDEX_MAX_KEYS];
+		Datum		new_values[INDEX_MAX_KEYS];
+		bool		new_isnull[INDEX_MAX_KEYS];
+
+		/* If we've reviewed all the attributes on this index, move on */
+		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
+			continue;
 
-		/*
-		 * If it's a whole-tuple reference, record as modified.  It's not
-		 * really worth supporting this case, since it could only succeed
-		 * after a no-op update, which is hardly a case worth optimizing for.
-		 */
-		if (attrnum == 0)
+		/* Checking partial at this point isn't viable when we're serializable */
+		if (is_partial && IsolationIsSerializable())
 		{
-			modified = bms_add_member(modified, attidx);
-			continue;
+			p_attrs = bms_add_members(p_attrs, pre_attrs);
+		}
+		/* Check partial index predicate */
+		else if (is_partial)
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+				continue;
+
+			/* A transition means we need to update the index */
+			if (old_qualifies != new_qualifies)
+				p_attrs = bms_copy(pre_attrs);
+
+			/*
+			 * When both are within the predicate we must update this index,
+			 * but only if one of the index key attributes changed.
+			 */
 		}
 
 		/*
-		 * Likewise, include in the modified set any system attribute other
-		 * than tableOID; we cannot expect these to be consistent in a HOT
-		 * chain, or even to be set correctly yet in the new tuple.
+		 * Expression indexes, or an index that has a comparison function,
+		 * requires us to form index datums and compare.  We've done all we
+		 * can to avoid this overhead, now it's time to bite the bullet and
+		 * get it done.
+		 *
+		 * XXX: Caching the values/isnull might be a win and avoid one of the
+		 * added calls to FormIndexDatum().
 		 */
-		if (attrnum < 0)
+		if (has_expressions || has_am_compare)
 		{
-			if (attrnum != TableOidAttributeNumber)
-				modified = bms_add_member(modified, attidx);
-			continue;
-		}
+			save_scantuple = econtext->ecxt_scantuple;
 
-		/* Extract values from both slots */
-		oldval = slot_getattr(tts_old, attrnum, &oldnull);
-		newval = slot_getattr(tts_new, attrnum, &newnull);
+			/* Evaluate expressions (if any) to get base datums */
+			econtext->ecxt_scantuple = old_tts;
+			FormIndexDatum(indexInfo, old_tts, estate, old_values, old_isnull);
 
-		/* If one value is NULL and the other is not, they are not equal */
-		if (oldnull != newnull)
-		{
-			modified = bms_add_member(modified, attidx);
-			continue;
+			econtext->ecxt_scantuple = new_tts;
+			FormIndexDatum(indexInfo, new_tts, estate, new_values, new_isnull);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			/* Compare the index key datums for equality */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				AttrNumber	rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				int			rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
+				int			nth_expr = 0;
+				bool		values_equal = false;
+
+				/*
+				 * We can't skip attributes that we've already identified as
+				 * triggering an index update because we may have added an
+				 * attribute from an expression index that didn't change but
+				 * the expression did and that unchanged attribute is
+				 * referenced in a subsequent index where we will discover
+				 * that fact.
+				 */
+
+				/* A change to/from NULL, record this attribute */
+				if (old_isnull[j] != new_isnull[j])
+				{
+					/* Expressions will have rel_attrnum == 0 */
+					if (rel_attrnum == 0)
+						m_attrs = bms_add_members(m_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						m_attrs = bms_add_member(m_attrs, rel_attridx);
+					continue;
+				}
+
+				/* Both NULL, no change */
+				if (old_isnull[j])
+				{
+					if (rel_attrnum != 0)
+						u_attrs = bms_add_member(u_attrs, rel_attridx);
+
+					continue;
+				}
+
+				/*
+				 * Use index AM's comparison function if present when
+				 * comparing the index datum formed when creating an index
+				 * key.
+				 */
+				if (has_am_compare)
+				{
+					/*
+					 * NOTE: For AM comparison, pass the 1-based index
+					 * attribute number. The AM's compare function expects the
+					 * same numbering as used internally by the AM.
+					 */
+					values_equal = amroutine->amcomparedatums(index, j + 1,
+															  old_values[j], old_isnull[j],
+															  new_values[j], new_isnull[j]);
+				}
+				else
+				{
+					/* Non-zero attribute means not an expression */
+					if (rel_attrnum != 0)
+					{
+						if (supports_ios)
+						{
+							CompactAttribute *attr = TupleDescCompactAttr(tupdesc, rel_attrnum - 1);
+
+							values_equal = datumIsEqual(old_values[j],
+														new_values[j],
+														attr->attbyval,
+														attr->attlen);
+						}
+						else
+						{
+							Form_pg_attribute attr = TupleDescAttr(tupdesc, rel_attrnum - 1);
+
+							/*
+							 * Compare using type-specific equality which at
+							 * this point is the relation's type because
+							 * FormIndexDatum() will populate the values/nulls
+							 * but won't transform them into the final values
+							 * destined for the index tuple, that's left to
+							 * index_form_tuple() which we don't call (on
+							 * purpose).
+							 */
+							values_equal = tts_attr_equal(attr->atttypid,
+														  attr->attcollation,
+														  attr->attbyval,
+														  attr->attlen,
+														  old_values[j],
+														  new_values[j]);
+						}
+					}
+					else
+					{
+						/*
+						 * An expression on an indexed attribute without
+						 * custom AM comparison function. In this case, becase
+						 * indexes will store the result of the expression's
+						 * evaluation, we can test for equality using the
+						 * expression's result type.  This allows for JSONB
+						 * and custom type equality tests, which may not be
+						 * the same as binary equality, to be in effect.  The
+						 * result stored in the index and used in index-only
+						 * scans will be valid as it is the expressions
+						 * result, which shouldn't change given the same
+						 * input.
+						 *
+						 * At this point the expression's type is what is
+						 * required when testing for equality, not the index's
+						 * type, because the value created by FormIndexDatum()
+						 * is the expression's result.  Later on in
+						 * index_form_tuple() an index may transform the value
+						 * when forming it's key (as is the case with HASH),
+						 * but at this point the Datum is the expression's
+						 * result type.
+						 */
+						Oid			expr_type_oid;
+						int16		typlen;
+						bool		typbyval;
+						Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+
+						Assert(expr != NULL);
+
+						/* Get type OID from the expression */
+						expr_type_oid = exprType((Node *) expr);
+
+						/* Get type information from the OID */
+						get_typlenbyval(expr_type_oid, &typlen, &typbyval);
+
+						values_equal = tts_attr_equal(expr_type_oid,
+													  -1,	/* use TBD expr type */
+													  typbyval,
+													  typlen,
+													  old_values[j],
+													  new_values[j]);
+					}
+				}
+
+				if (!values_equal)
+				{
+					/* Expressions will have rel_attrnum == 0 */
+					if (rel_attrnum == 0)
+						m_attrs = bms_add_members(m_attrs, indexInfo->ii_ExpressionsAttrs);
+					else
+						m_attrs = bms_add_member(m_attrs, rel_attridx);
+				}
+				else
+				{
+					if (rel_attrnum != 0)
+						u_attrs = bms_add_member(u_attrs, rel_attridx);
+				}
+
+				if (rel_attrnum == 0)
+					nth_expr++;
+			}
 		}
+		else
+		{
+			/*
+			 * Here we know that we're reviewing an index that doesn't have a
+			 * partial predicate, doesn't use expressions, and doesn't have a
+			 * amcomparedatums() implementation.  If this index supports IOS
+			 * we need to use binary comparison, if not then type-specific
+			 * will provide a more accurate result.
+			 */
 
-		/* If both are NULL, consider them equal */
-		if (oldnull)
-			continue;
+			/* Compare the index key datums for equality */
+			for (int j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
+			{
+				AttrNumber	rel_attrnum;
+				int			rel_attridx;
+				bool		values_equal = false;
+				bool		old_null,
+							new_null;
+				Datum		old_val,
+							new_val;
 
-		/* Get attribute metadata */
-		Assert(attrnum > 0 && attrnum <= tupdesc->natts);
-		attr = TupleDescAttr(tupdesc, attrnum - 1);
-
-		/* Compare using type-specific equality operator */
-		if (!tts_attr_equal(attr->atttypid,
-							attr->attcollation,
-							attr->attbyval,
-							attr->attlen,
-							oldval,
-							newval))
-			modified = bms_add_member(modified, attidx);
-	}
+				rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+				rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
+
+				/* Zero would mean expression, something we don't expect here */
+				Assert(rel_attrnum > 0 && rel_attrnum <= tupdesc->natts);
+
+				/* Extract values from both slots for this attribute */
+				old_val = slot_getattr(old_tts, rel_attrnum, &old_null);
+				new_val = slot_getattr(new_tts, rel_attrnum, &new_null);
 
-	bms_free(indexed_attrs);
+				/*
+				 * If one value is NULL and the other is not, they are not
+				 * equal
+				 */
+				if (old_null != new_null)
+				{
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+					continue;
+				}
+
+				/* If both are NULL, consider them equal */
+				if (old_null)
+				{
+					u_attrs = bms_add_member(u_attrs, rel_attridx);
+					continue;
+				}
+
+				if (supports_ios)
+				{
+					CompactAttribute *attr = TupleDescCompactAttr(tupdesc, rel_attrnum - 1);
+
+					values_equal = datumIsEqual(old_val,
+												new_val,
+												attr->attbyval,
+												attr->attlen);
+				}
+				else
+				{
+					Form_pg_attribute attr = TupleDescAttr(tupdesc, rel_attrnum - 1);
+
+					/*
+					 * Compare using type-specific equality which at this
+					 * point is the relation's type because FormIndexDatum()
+					 * will populate the values/nulls but won't transform them
+					 * into the final values destined for the index tuple,
+					 * that's left to index_form_tuple() which we don't call
+					 * (on purpose).
+					 */
+					values_equal = tts_attr_equal(attr->atttypid,
+												  attr->attcollation,
+												  attr->attbyval,
+												  attr->attlen,
+												  old_val,
+												  new_val);
+				}
+
+				if (!values_equal)
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+				else
+					u_attrs = bms_add_member(u_attrs, rel_attridx);
+			}
+		}
+
+		/*
+		 * Here we know all the attributes we thought might be modified and
+		 * all those we know haven't been.  Take the difference and add it to
+		 * the modified indexed attributes set.
+		 */
+		m_attrs = bms_del_members(m_attrs, u_attrs);
+		p_attrs = bms_del_members(p_attrs, u_attrs);
+		mix_attrs = bms_add_members(mix_attrs, m_attrs);
+		mix_attrs = bms_add_members(mix_attrs, p_attrs);
+
+		bms_free(m_attrs);
+		bms_free(u_attrs);
+		bms_free(p_attrs);
+	}
 
-	return modified;
+	return mix_attrs;
 }
 
 /*
@@ -2395,6 +2710,9 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* The set of modified indexed attributes that trigger new index entries */
+	Bitmapset  *mix_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2517,13 +2835,32 @@ lreplace:
 	bms_free(resultRelInfo->ri_ChangedIndexedCols);
 	resultRelInfo->ri_ChangedIndexedCols = NULL;
 
-	resultRelInfo->ri_ChangedIndexedCols =
-		ExecCheckIndexedAttrsForChanges(resultRelInfo, oldSlot, slot);
+	/*
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * Next up we need to find out the set of indexed attributes that have
+	 * changed in value and should trigger a new index tuple.  We could start
+	 * with the set of updated columns via ExecGetUpdatedCols(), but if we do
+	 * we will overlook attributes directly modified by heap_modify_tuple()
+	 * which are not known to ExecGetUpdatedCols().
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot);
 
 	/*
-	 * replace the heap tuple
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2535,9 +2872,12 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
-								resultRelInfo->ri_ChangedIndexedCols,
+								mix_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = mix_attrs;
+
 	return result;
 }
 
@@ -2555,7 +2895,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index d69dc090aa4..e9a53b95caf 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -855,10 +855,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 63dd41c1f21..9bdf73eda59 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 13ea91922ef..2f265f4816c 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 2f9a2b069cd..5783dbebff0 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -369,7 +369,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -404,8 +404,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 16be5c7a9c1..42bd329eaad 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1210,6 +1210,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 8a5931a3118..2b9206ff24a 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,7 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
-								 Bitmapset *updated_cols,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1503,12 +1503,12 @@ 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,
-				   Bitmapset *updated_cols, TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
 										 wait, tmfd, lockmode,
-										 updated_cols, update_indexes);
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2011,7 +2011,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
-									  Bitmapset *modified_indexe_attrs,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index dda95e54903..8d364f8b30f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 993dc0e6ced..a19585ba065 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
@@ -800,9 +805,10 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
-extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *resultRelInfo,
-												  TupleTableSlot *tts_old,
-												  TupleTableSlot *tts_new);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+												  EState *estate,
+												  TupleTableSlot *old_tts,
+												  TupleTableSlot *new_tts);
 extern bool tts_attr_equal(Oid typid, Oid collation, bool typbyval, int16 typlen,
 						   Datum value1, Datum value2);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 898368fb8cb..d8e88817206 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -494,6 +508,11 @@ typedef struct ResultRelInfo
 	Bitmapset  *ri_extraUpdatedCols;
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
+
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
 	Bitmapset  *ri_ChangedIndexedCols;
 
 	/* Projection to generate new tuple in an INSERT/UPDATE */
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
index e34a821c403..54b3981918c 100644
--- a/src/test/isolation/expected/insert-conflict-specconflict.out
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -80,6 +80,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
@@ -172,6 +176,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
@@ -369,6 +377,10 @@ key|data
 step s1_commit: COMMIT;
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 step s2_upsert: <... completed>
 step controller_show: SELECT * FROM upserttest;
 key|data       
@@ -530,6 +542,14 @@ isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
 step s2_commit: COMMIT;
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
 step s1_upsert: <... completed>
 step s1_noop: 
 step controller_show: SELECT * FROM upserttest;
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..f6bd8b18af8
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,1922 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- JSONB Expression Index an some including columns
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB, status TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}', 'ok');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET status = 'not ok' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             5 |           2 |                 40.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->'status'))
+    WHERE (docs->'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           2 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Expression with COLLATION and BTREE (nbtree) index
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    name TEXT COLLATE case_insensitive
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_lower_idx ON t USING BTREE (name COLLATE case_insensitive);
+INSERT INTO t VALUES (1, 'ALICE');
+-- Change case but not value - should NOT be HOT in BTREE
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change to new value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Nested JSONB Expression and JSONB equality '->' (not '->>')
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->'status'))
+    WHERE (data->'priority')::int > 5
+      AND (data->'active')::boolean = true;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT update
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: Still 1 HOT
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+ count 
+-------
+     0
+(1 row)
+
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+INSERT INTO t VALUES (1, 50, 'below range');
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     150
+(1 row)
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     160
+(1 row)
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           4 |                100.00 | t
+(1 row)
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+SELECT id, value, description FROM t;
+ id | value |  description  
+----+-------+---------------
+  1 |    50 | updated again
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash((data->'category'));
+INSERT INTO t VALUES (1, '{"category": "books", "title": "PostgreSQL Guide"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET data = '{"category": "books", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed JSONB field - NOT HOT
+UPDATE t SET data = '{"category": "videos", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET data = '{"category": "courses", "title": "PostgreSQL Basics"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_brin     |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT (BRIN allows it for single row)
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_hash     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (HASH blocks it)
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: 1 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT (BRIN permits single-row updates)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+-- Expected: 2 HOT (HASH blocks it)
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+-- Expected: 3 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Two indexes on a JSONB document, one partial
+-- ================================================================
+CREATE TABLE t (docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO t (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO t (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX t_idx_a ON t ((docs->'a'));
+CREATE INDEX t_idx_b ON t ((docs->'b')) WHERE (docs->'b')::numeric > 9;
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs -> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs -> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 0, "b": 10}
+(1 row)
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- This update changes both 'a' and 'b' to new values this cannot use the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+-- Check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs -> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+       docs        
+-------------------
+ {"a": 2, "b": 12}
+(1 row)
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->'b')::numeric = 12;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             5 |           1 |                 20.00 | t
+(1 row)
+
+-- Check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Index Scan using t_idx_b on t
+   Filter: (((docs -> 'b'::text))::numeric < '100'::numeric)
+(2 rows)
+
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+ docs 
+------
+(0 rows)
+
+DROP TABLE t CASCADE;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+-- ================================================================
+-- Tests to check expression indexes
+-- ================================================================
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(abs(a)) WHERE abs(a) > 10;
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (-1, -1), (-2, -2), (-3, -3), (-4, -4), (-5, -5);
+INSERT INTO t SELECT m, n FROM generate_series(-10000, -10) AS m, abs(m) AS n;
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+-- The indexed value of b hasn't changed, this should be a HOT update.
+-- (-5, -5) -> (-5, 1)
+UPDATE t SET b = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT b  FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using t_idx_b on t
+   Index Cond: ((abs(b) < 10) AND (abs(b) > 0))
+(2 rows)
+
+SELECT b FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+ b  
+----
+ -1
+ -2
+ -3
+ -4
+  5
+(5 rows)
+
+-- Now that we're not checking the predicate of the partial index, this
+-- update of a from -5 to 5 should be HOT because we should ignore the
+-- predicate and check the expression and find it unchanged.
+-- (-5, 1) -> (5, 1)
+UPDATE t SET a = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- This update moves a into the partial index and should not
+-- be HOT.  Let's make sure of that and check the index as well.
+-- (-4, -4) -> (-11, -4)
+UPDATE t SET a = -11 WHERE a = -4;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+   Index Cond: (abs(a) < 15)
+(2 rows)
+
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+  a  | b  
+-----+----
+ -10 | 10
+ -11 | -4
+ -11 | 11
+ -12 | 12
+ -13 | 13
+ -14 | 14
+(6 rows)
+
+-- (-11, -4) -> (11, -4)
+UPDATE t SET a = 11 WHERE b = -4;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+   Index Cond: (abs(a) < 15)
+(2 rows)
+
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+  a  | b  
+-----+----
+ -10 | 10
+  11 | -4
+ -11 | 11
+ -12 | 12
+ -13 | 13
+ -14 | 14
+(6 rows)
+
+-- (11, -4) -> (-4, -4)
+UPDATE t SET a = -4 WHERE b = -4;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             5 |           3 |                 60.00 | t
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+   Index Cond: (abs(a) < 15)
+(2 rows)
+
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+  a  | b  
+-----+----
+ -10 | 10
+ -11 | 11
+ -12 | 12
+ -13 | 13
+ -14 | 14
+(5 rows)
+
+-- This update of a from 5 to -1 is HOT despite that attribute
+-- being indexed because the before and after values for the
+-- partial index predicate are outside the index definition.
+-- (5, 1) -> (-1, 1)
+UPDATE t SET a = -1 WHERE a = 5;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             6 |           4 |                 66.67 | t
+(1 row)
+
+-- This update of a from -2 to -1 will be HOT because the before/after values
+-- of a are both outside the predicate of the partial index.
+-- (-1, 1) -> (-2, 1)
+UPDATE t SET a = -2 WHERE b = -2;
+SELECT * FROM check_hot_updates(5);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             7 |           5 |                 71.43 | t
+(1 row)
+
+-- The indexed value for b isn't changing, this should be HOT.
+-- (-2, -2) -> (-2, 2)
+UPDATE t SET b = 2 WHERE b = -2;
+SELECT * FROM check_hot_updates(6);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             8 |           6 |                 75.00 | t
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT b FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using t_idx_b on t
+   Index Cond: ((abs(b) < 10) AND (abs(b) > 0))
+(2 rows)
+
+SELECT b FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+ b  
+----
+ -1
+  2
+ -3
+ -4
+  5
+(5 rows)
+
+SELECT * FROM t where a > -10 AND a < 10;
+ a  | b  
+----+----
+ -1 | -1
+ -3 | -3
+ -1 |  5
+ -4 | -4
+ -2 |  2
+(5 rows)
+
+-- Before and after values for a are outside the predicate of the index,
+-- and because we're checking this should be HOT.
+-- (-2, 1) -> (5, 1)
+-- (-2, -2) -> (5, -2)
+UPDATE t SET a = 5 WHERE a = -1;
+SELECT * FROM check_hot_updates(8);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |            10 |           8 |                 80.00 | t
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using t_idx_a on t
+   Index Cond: (abs(a) < 15)
+(2 rows)
+
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+  a  | b  
+-----+----
+ -10 | 10
+ -11 | 11
+ -12 | 12
+ -13 | 13
+ -14 | 14
+(5 rows)
+
+DROP TABLE t CASCADE;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+-- ================================================================
+-- JSONB with two indexes each on separate fields, one partial
+-- ================================================================
+CREATE TABLE t(docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'a')) WHERE (docs->'b')::integer = 1;
+INSERT INTO t VALUES ('{"a": 1, "b": 1}');
+EXPLAIN (COSTS OFF) SELECT * FROM t;
+  QUERY PLAN   
+---------------
+ Seq Scan on t
+(1 row)
+
+SELECT * FROM t;
+       docs       
+------------------
+ {"a": 1, "b": 1}
+(1 row)
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::integer = 1;
+            QUERY PLAN            
+----------------------------------
+ Index Scan using t_docs_idx on t
+(1 row)
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+       docs       
+------------------
+ {"a": 1, "b": 1}
+(1 row)
+
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             0 |           0 |                     0 | t
+(1 row)
+
+UPDATE t SET docs='{"a": 1, "b": 0}';
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+ docs 
+------
+(0 rows)
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Tests for multi-column indexes
+-- ================================================================
+CREATE TABLE t(id INT, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t(id, (docs->'a'));
+INSERT INTO t VALUES (1, '{"a": 1, "b": 1}');
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using t_docs_idx on t
+   Index Cond: (id > 0)
+   Filter: (((docs -> 'a'::text))::integer > 0)
+(3 rows)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  1 | {"a": 1, "b": 1}
+(1 row)
+
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             0 |           0 |                     0 | t
+(1 row)
+
+-- Changing the id attribute which is an indexed attribute should
+-- prevent HOT updates.
+UPDATE t SET id = 2;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  2 | {"a": 1, "b": 1}
+(1 row)
+
+-- Changing the docs->'a' field in the indexed attribute 'docs'
+-- should prevent HOT updates.
+UPDATE t SET docs='{"a": -2, "b": 1}';
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+ id |       docs        
+----+-------------------
+  2 | {"a": -2, "b": 1}
+(1 row)
+
+-- Leaving the docs->'a' attribute unchanged means that the expression
+-- is unchanged and because the 'id' attribute isn't in the modified
+-- set the indexed tuple is unchanged, this can go HOT.
+UPDATE t SET docs='{"a": -2, "b": 2}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+ id |       docs        
+----+-------------------
+  2 | {"a": -2, "b": 2}
+(1 row)
+
+-- Here we change the 'id' attribute and the 'docs' attribute setting
+-- the expression docs->'a' to a new value, this cannot be a HOT update.
+UPDATE t SET id = 3, docs='{"a": 3, "b": 3}';
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+ id |       docs       
+----+------------------
+  3 | {"a": 3, "b": 3}
+(1 row)
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Relation with unique constraint, partial index
+-- ================================================================
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(user1@example.com) conflicts with existing key (lower(email::text))=(user1@example.com).
+SELECT * FROM check_hot_updates(0, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ users      |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ users      |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ users      |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+ERROR:  conflicting key value violates exclusion constraint "users_lower_excl"
+DETAIL:  Key (lower(email::text))=(taken@domain.com) conflicts with existing key (lower(email::text))=(taken@domain.com).
+SELECT * FROM check_hot_updates(1, 'users');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ users      |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE users CASCADE;
+-- ================================================================
+-- Constraints spoiling HOT updates, this time with a range.
+-- ================================================================
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+ERROR:  conflicting key value violates exclusion constraint "no_screening_time_overlap"
+DETAIL:  Key (event_time)=(["Sun Jan 01 20:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]) conflicts with existing key (event_time)=(["Sun Jan 01 21:00:00 2023 PST","Sun Jan 01 21:45:00 2023 PST"]).
+SELECT * FROM check_hot_updates(0, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ events     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ events     |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'events');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ events     |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE events CASCADE;
+-- ================================================================
+-- Ensure that only the modified summarizing indexes are updated.
+-- ================================================================
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+SELECT * FROM ex;
+ id |     att1     |      att2       | att3 | att4 
+----+--------------+-----------------+------+------
+  1 | {"data": []} | nothing special |      | 
+(1 row)
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             1 |           1 |                100.00 | t
+(1 row)
+
+SELECT * FROM ex;
+ id |     att1     |      att2      | att3 |   att4   
+----+--------------+----------------+------+----------
+  1 | {"data": []} | special indeed |      | whatever
+(1 row)
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             2 |           1 |                 50.00 | t
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        |        att2         | att3 |   att4   
+----+-------------------+---------------------+------+----------
+  1 | {"data": "howdy"} | special, so special |      | whatever
+(1 row)
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT * FROM check_hot_updates(2, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             3 |           2 |                 66.67 | t
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | a    | b    | c
+(1 row)
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             4 |           3 |                 75.00 | t
+(1 row)
+
+SELECT * FROM ex;
+ id |       att1        | att2 | att3 | att4 
+----+-------------------+------+------+------
+  1 | {"data": "howdy"} | d    | e    | c
+(1 row)
+
+DROP TABLE ex CASCADE;
+-- ================================================================
+-- Don't update unmodified summarizing indexes but do allow HOT
+-- ================================================================
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT * FROM check_hot_updates(1, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT * FROM check_hot_updates(2, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             3 |           3 |                100.00 | t
+(1 row)
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT * FROM check_hot_updates(3, 'ex');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ ex         |             4 |           3 |                 75.00 | t
+(1 row)
+
+DROP TABLE ex CASCADE;
+-- ================================================================
+-- Ensure custom type equality operators are used
+-- ================================================================
+CREATE TYPE my_custom_type AS (val int);
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ my_table   |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ my_table   |             3 |           0 |                  0.00 | t
+(1 row)
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT * FROM check_hot_updates(0, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ my_table   |             4 |           0 |                  0.00 | t
+(1 row)
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'my_table');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ my_table   |             5 |           1 |                 20.00 | t
+(1 row)
+
+-- Query using the index
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+ id | custom_val 
+----+------------
+  3 | (6)
+(1 row)
+
+-- Clean up test case
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..9520d0d4601 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: heap_hot_updates
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..8d5510989df
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,1325 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+
+-- ================================================================
+-- Basic JSONB Expression Index
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update indexed JSONB field - should NOT be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update non-indexed field again - should be HOT
+UPDATE t SET docs = '{"name": "bob", "age": 32}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t;
+
+-- ================================================================
+-- JSONB Expression Index an some including columns
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB, status TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_name_idx ON t((docs->'name'));
+INSERT INTO t VALUES (1, '{"name": "alice", "age": 30}', 'ok');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET docs = '{"name": "alice", "age": 31}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET status = 'not ok' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Partial Index with Predicate Transitions
+-- ================================================================
+CREATE TABLE t(id INT, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_value_idx ON t(value) WHERE value > 10;
+INSERT INTO t VALUES (1, 5);
+
+-- Both outside predicate - should be HOT
+UPDATE t SET value = 8 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET value = 15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Both inside predicate, value changes - should NOT be HOT
+UPDATE t SET value = 20 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Transition out of predicate - should NOT be HOT
+UPDATE t SET value = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Both outside predicate again - should be HOT
+UPDATE t SET value = 3 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(docs JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((docs->'status'))
+    WHERE (docs->'priority')::int > 5;
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3}');
+
+-- Both outside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 4}';
+SELECT * FROM check_hot_updates(1);
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET docs = '{"status": "pending", "priority": 10}';
+SELECT * FROM check_hot_updates(1);
+
+-- Inside predicate, status changes - should NOT be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 10}';
+SELECT * FROM check_hot_updates(1);
+
+-- Inside predicate, status unchanged - should be HOT
+UPDATE t SET docs = '{"status": "active", "priority": 8}';
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Multi-Column Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, a INT, b INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t(id, abs(a), abs(b));
+INSERT INTO t VALUES (1, -5, -10);
+
+-- Change sign but not abs value - should be HOT
+UPDATE t SET a = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change abs value - should NOT be HOT
+UPDATE t SET b = -15 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change id - should NOT be HOT
+UPDATE t SET id = 2 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Expression with COLLATION and BTREE (nbtree) index
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    name TEXT COLLATE case_insensitive
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_lower_idx ON t USING BTREE (name COLLATE case_insensitive);
+
+INSERT INTO t VALUES (1, 'ALICE');
+
+-- Change case but not value - should NOT be HOT in BTREE
+UPDATE t SET name = 'Alice' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Change to new value - should NOT be HOT
+UPDATE t SET name = 'BOB' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Array Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_array_len_idx ON t(array_length(tags, 1));
+INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
+
+-- Same length, different elements - should be HOT
+UPDATE t SET tags = ARRAY['d', 'e', 'f'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Different length - should NOT be HOT
+UPDATE t SET tags = ARRAY['d', 'e'] WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Nested JSONB Expression and JSONB equality '->' (not '->>')
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_nested_idx ON t((data->'user'->'name'));
+INSERT INTO t VALUES ('{"user": {"name": "alice", "age": 30}}');
+
+-- Change nested non-indexed field - should be HOT
+UPDATE t SET data = '{"user": {"name": "alice", "age": 31}}';
+SELECT * FROM check_hot_updates(1);
+
+-- Change nested indexed field - should NOT be HOT
+UPDATE t SET data = '{"user": {"name": "bob", "age": 31}}';
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- Complex Predicate on Multiple JSONB Fields
+-- ================================================================
+CREATE TABLE t(data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx ON t((data->'status'))
+    WHERE (data->'priority')::int > 5
+      AND (data->'active')::boolean = true;
+
+INSERT INTO t VALUES ('{"status": "pending", "priority": 3, "active": true}');
+
+-- Outside predicate (priority too low) - should be HOT
+UPDATE t SET data = '{"status": "done", "priority": 3, "active": true}';
+SELECT * FROM check_hot_updates(1);
+
+-- Transition into predicate - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": true}';
+SELECT * FROM check_hot_updates(1);
+
+-- Inside predicate, change to outside (active = false) - should NOT be HOT
+UPDATE t SET data = '{"status": "done", "priority": 10, "active": false}';
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- TOASTed Values in Expression Index
+-- ================================================================
+CREATE TABLE t(id INT, large_text TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_substr_idx ON t(substr(large_text, 1, 10));
+
+INSERT INTO t VALUES (1, repeat('x', 5000) || 'identifier');
+
+-- Change end of string, prefix unchanged - should be HOT
+UPDATE t SET large_text = repeat('x', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change prefix - should NOT be HOT
+UPDATE t SET large_text = repeat('y', 5000) || 'different' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with TOASTed JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin((data->'tags'));
+
+-- Insert with TOASTed JSONB
+INSERT INTO t (id, data) VALUES
+    (1, jsonb_build_object(
+        'tags', '["postgres", "database"]'::jsonb,
+        'large_field', repeat('x', 10000)
+    ));
+
+-- Update: Change large_field, tags unchanged - should be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "database"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT update
+
+-- Update: Change tags - should NOT be HOT
+UPDATE t
+SET data = jsonb_build_object(
+    'tags', '["postgres", "sql"]'::jsonb,
+    'large_field', repeat('y', 10000)
+)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: Still 1 HOT
+
+-- Verify correctness
+SELECT count(*) FROM t WHERE data->'tags' @> '["database"]'::jsonb;
+-- Expected: 0 rows
+SELECT count(*) FROM t WHERE data->'tags' @> '["sql"]'::jsonb;
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+
+INSERT INTO t VALUES (1, 50, 'below range');
+
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+SELECT id, value, description FROM t;
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- HASH Index on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash((data->'category'));
+INSERT INTO t VALUES (1, '{"category": "books", "title": "PostgreSQL Guide"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET data = '{"category": "books", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update indexed JSONB field - NOT HOT
+UPDATE t SET data = '{"category": "videos", "title": "PostgreSQL Handbook"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - NOT HOT
+UPDATE t SET data = '{"category": "courses", "title": "PostgreSQL Basics"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+-- Expected: 1 HOT (BRIN allows it for single row)
+
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+-- Expected: 0 HOT (HASH blocks it)
+
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (BRIN permits single-row updates)
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT
+
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (HASH blocks it)
+
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+-- Expected: 3 HOT
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Two indexes on a JSONB document, one partial
+-- ================================================================
+CREATE TABLE t (docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO t (docs) VALUES ('{"a": 0, "b": 0}');
+INSERT INTO t (docs) SELECT jsonb_build_object('b', n) FROM generate_series(100, 10000) as n;
+CREATE INDEX t_idx_a ON t ((docs->'a'));
+CREATE INDEX t_idx_b ON t ((docs->'b')) WHERE (docs->'b')::numeric > 9;
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- Leave 'a' unchanged but modify 'b' to a value outside of the index predicate.
+-- This should be a HOT update because neither index is changed.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 1) WHERE (docs->'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+
+-- Check to make sure that the index does not contain a value for 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+
+-- Leave 'a' unchanged but modify 'b' to a value within the index predicate.
+-- This represents a change for field 'b' from unindexed to indexed and so
+-- this should not take the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 0, 'b', 10) WHERE (docs->'a')::numeric = 0;
+SELECT * FROM check_hot_updates(1);
+
+-- Check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+
+-- This update modifies the value of 'a', an indexed field, so it also cannot
+-- be a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 1, 'b', 10) WHERE (docs->'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+
+-- This update changes both 'a' and 'b' to new values this cannot use the HOT path.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 12) WHERE (docs->'b')::numeric = 10;
+SELECT * FROM check_hot_updates(1);
+
+-- Check to make sure that the index contains the new value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+
+-- This update changes 'b' to a value outside its predicate requiring that
+-- we remove it from the index.  That's a transition that can't be done
+-- during a HOT update.
+UPDATE t SET docs = jsonb_build_object('a', 2, 'b', 1) WHERE (docs->'b')::numeric = 12;
+SELECT * FROM check_hot_updates(1);
+
+-- Check to make sure that the index no longer contains the value of 'b'
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+SELECT * FROM t WHERE (docs->'b')::numeric > 9 AND (docs->'b')::numeric < 100;
+
+DROP TABLE t CASCADE;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+-- ================================================================
+-- Tests to check expression indexes
+-- ================================================================
+CREATE TABLE t(a INT, b INT) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_idx_a ON t(abs(a)) WHERE abs(a) > 10;
+CREATE INDEX t_idx_b ON t(abs(b));
+INSERT INTO t VALUES (-1, -1), (-2, -2), (-3, -3), (-4, -4), (-5, -5);
+INSERT INTO t SELECT m, n FROM generate_series(-10000, -10) AS m, abs(m) AS n;
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+-- The indexed value of b hasn't changed, this should be a HOT update.
+-- (-5, -5) -> (-5, 1)
+UPDATE t SET b = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(1);
+EXPLAIN (COSTS OFF) SELECT b  FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+SELECT b FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+
+-- Now that we're not checking the predicate of the partial index, this
+-- update of a from -5 to 5 should be HOT because we should ignore the
+-- predicate and check the expression and find it unchanged.
+-- (-5, 1) -> (5, 1)
+UPDATE t SET a = 5 WHERE a = -5;
+SELECT * FROM check_hot_updates(2);
+
+-- This update moves a into the partial index and should not
+-- be HOT.  Let's make sure of that and check the index as well.
+-- (-4, -4) -> (-11, -4)
+UPDATE t SET a = -11 WHERE a = -4;
+SELECT * FROM check_hot_updates(2);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+
+-- (-11, -4) -> (11, -4)
+UPDATE t SET a = 11 WHERE b = -4;
+SELECT * FROM check_hot_updates(3);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+
+-- (11, -4) -> (-4, -4)
+UPDATE t SET a = -4 WHERE b = -4;
+SELECT * FROM check_hot_updates(3);
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+
+-- This update of a from 5 to -1 is HOT despite that attribute
+-- being indexed because the before and after values for the
+-- partial index predicate are outside the index definition.
+-- (5, 1) -> (-1, 1)
+UPDATE t SET a = -1 WHERE a = 5;
+SELECT * FROM check_hot_updates(4);
+
+-- This update of a from -2 to -1 will be HOT because the before/after values
+-- of a are both outside the predicate of the partial index.
+-- (-1, 1) -> (-2, 1)
+UPDATE t SET a = -2 WHERE b = -2;
+SELECT * FROM check_hot_updates(5);
+
+-- The indexed value for b isn't changing, this should be HOT.
+-- (-2, -2) -> (-2, 2)
+UPDATE t SET b = 2 WHERE b = -2;
+SELECT * FROM check_hot_updates(6);
+EXPLAIN (COSTS OFF) SELECT b FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+SELECT b FROM t WHERE abs(b) < 10 AND abs(b) > 0;
+
+SELECT * FROM t where a > -10 AND a < 10;
+
+-- Before and after values for a are outside the predicate of the index,
+-- and because we're checking this should be HOT.
+-- (-2, 1) -> (5, 1)
+-- (-2, -2) -> (5, -2)
+UPDATE t SET a = 5 WHERE a = -1;
+SELECT * FROM check_hot_updates(8);
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+SELECT * FROM t WHERE abs(a) > 10 AND abs(a) < 15;
+
+DROP TABLE t CASCADE;
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+
+-- ================================================================
+-- JSONB with two indexes each on separate fields, one partial
+-- ================================================================
+CREATE TABLE t(docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'a')) WHERE (docs->'b')::integer = 1;
+INSERT INTO t VALUES ('{"a": 1, "b": 1}');
+
+EXPLAIN (COSTS OFF) SELECT * FROM t;
+SELECT * FROM t;
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE (docs->'b')::integer = 1;
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+
+SELECT * FROM check_hot_updates(0);
+
+UPDATE t SET docs='{"a": 1, "b": 0}';
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE (docs->'b')::integer = 1;
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Tests for multi-column indexes
+-- ================================================================
+CREATE TABLE t(id INT, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t(id, (docs->'a'));
+INSERT INTO t VALUES (1, '{"a": 1, "b": 1}');
+
+SET SESSION enable_seqscan = OFF;
+SET SESSION enable_bitmapscan = OFF;
+
+EXPLAIN (COSTS OFF) SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+SELECT * FROM check_hot_updates(0);
+
+-- Changing the id attribute which is an indexed attribute should
+-- prevent HOT updates.
+UPDATE t SET id = 2;
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+-- Changing the docs->'a' field in the indexed attribute 'docs'
+-- should prevent HOT updates.
+UPDATE t SET docs='{"a": -2, "b": 1}';
+SELECT * FROM check_hot_updates(0);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+
+-- Leaving the docs->'a' attribute unchanged means that the expression
+-- is unchanged and because the 'id' attribute isn't in the modified
+-- set the indexed tuple is unchanged, this can go HOT.
+UPDATE t SET docs='{"a": -2, "b": 2}';
+SELECT * FROM check_hot_updates(1);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer < 0;
+
+-- Here we change the 'id' attribute and the 'docs' attribute setting
+-- the expression docs->'a' to a new value, this cannot be a HOT update.
+UPDATE t SET id = 3, docs='{"a": 3, "b": 3}';
+SELECT * FROM check_hot_updates(1);
+
+SELECT * FROM t WHERE id > 0 AND (docs->'a')::integer > 0;
+
+SET SESSION enable_seqscan = ON;
+SET SESSION enable_bitmapscan = ON;
+
+DROP TABLE t CASCADE;
+
+-- ================================================================
+-- Relation with unique constraint, partial index
+-- ================================================================
+CREATE TABLE users (
+    user_id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) NOT NULL,
+    EXCLUDE USING btree (lower(email) WITH =)
+);
+
+-- Add some data to the table and then update it in ways that should and should
+-- not be HOT updates.
+INSERT INTO users (name, email) VALUES
+('user1', 'user1@example.com'),
+('user2', 'user2@example.com'),
+('taken', 'taken@EXAMPLE.com'),
+('you', 'you@domain.com'),
+('taken', 'taken@domain.com');
+
+-- Should fail because of the unique constraint on the email column.
+UPDATE users SET email = 'user1@example.com' WHERE email = 'user2@example.com';
+SELECT * FROM check_hot_updates(0, 'users');
+
+-- Should succeed because the email column is not being updated and should go HOT.
+UPDATE users SET name = 'foo' WHERE email = 'user1@example.com';
+SELECT * FROM check_hot_updates(1, 'users');
+
+-- Create a partial index on the email column, updates
+CREATE INDEX idx_users_email_no_example ON users (lower(email)) WHERE lower(email) LIKE '%@example.com%';
+
+-- An update that changes the email column but not the indexed portion of it and falls outside the constraint.
+-- Shouldn't be a HOT update because of the exclusion constraint.
+UPDATE users SET email = 'you+2@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+
+-- An update that changes the email column but not the indexed portion of it and falls within the constraint.
+-- Again, should fail constraint and fail to be a HOT update.
+UPDATE users SET email = 'taken@domain.com' WHERE name = 'you';
+SELECT * FROM check_hot_updates(1, 'users');
+
+DROP TABLE users CASCADE;
+
+-- ================================================================
+-- Constraints spoiling HOT updates, this time with a range.
+-- ================================================================
+CREATE TABLE events (
+    id serial primary key,
+    name VARCHAR(255) NOT NULL,
+    event_time tstzrange,
+    constraint no_screening_time_overlap exclude using gist (
+        event_time WITH &&
+    )
+);
+
+-- Add two non-overlapping events.
+INSERT INTO events (id, event_time, name)
+VALUES
+    (1, '["2023-01-01 19:00:00", "2023-01-01 20:45:00"]', 'event1'),
+    (2, '["2023-01-01 21:00:00", "2023-01-01 21:45:00"]', 'event2');
+
+-- Update the first event to overlap with the second, should fail the constraint and not be HOT.
+UPDATE events SET event_time = '["2023-01-01 20:00:00", "2023-01-01 21:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+
+-- Update the first event to not overlap with the second, again not HOT due to the constraint.
+UPDATE events SET event_time = '["2023-01-01 22:00:00", "2023-01-01 22:45:00"]' WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'events');
+
+-- Update the first event to not overlap with the second, this time we're HOT because we don't overlap with the constraint.
+UPDATE events SET name = 'new name here' WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'events');
+
+DROP TABLE events CASCADE;
+
+-- ================================================================
+-- Ensure that only the modified summarizing indexes are updated.
+-- ================================================================
+CREATE TABLE ex (id SERIAL primary key, att1 JSONB, att2 text, att3 text, att4 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+CREATE INDEX ex_expr2_idx ON ex USING btree((att1->'a'));
+CREATE INDEX ex_expr3_idx ON ex USING btree((att1->'b'));
+CREATE INDEX ex_expr4_idx ON ex USING btree((att1->'c'));
+CREATE INDEX ex_sumr2_idx ON ex USING BRIN(att3);
+CREATE INDEX ex_sumr3_idx ON ex USING BRIN(att4);
+CREATE INDEX ex_expr5_idx ON ex USING btree((att1->'d'));
+INSERT INTO ex (att1, att2) VALUES ('{"data": []}'::json, 'nothing special');
+
+SELECT * FROM ex;
+
+-- Update att2 and att4 both are BRIN/summarizing indexes, this should be a HOT update and
+-- only update two of the three summarizing indexes.
+UPDATE ex SET att2 = 'special indeed', att4 = 'whatever';
+SELECT * FROM check_hot_updates(1, 'ex');
+SELECT * FROM ex;
+
+-- Update att1 and att2, only one is BRIN/summarizing, this should NOT be a HOT update.
+UPDATE ex SET att1 = att1 || '{"data": "howdy"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(1, 'ex');
+SELECT * FROM ex;
+
+-- Update att2, att3, and att4 all are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att2 = 'a', att3 = 'b', att4 = 'c';
+SELECT * FROM check_hot_updates(2, 'ex');
+SELECT * FROM ex;
+
+-- Update att1, att2, and att3 all modified values are BRIN/summarizing indexes, this should be a HOT update
+-- and yet still update all three summarizing indexes.
+UPDATE ex SET att1 = '{"data": "howdy"}', att2 = 'd', att3 = 'e';
+SELECT * FROM check_hot_updates(3, 'ex');
+SELECT * FROM ex;
+
+DROP TABLE ex CASCADE;
+
+-- ================================================================
+-- Don't update unmodified summarizing indexes but do allow HOT
+-- ================================================================
+CREATE TABLE ex (att1 JSONB, att2 text) WITH (fillfactor = 60);
+CREATE INDEX ex_expr1_idx ON ex USING btree((att1->'data'));
+CREATE INDEX ex_sumr1_idx ON ex USING BRIN(att2);
+INSERT INTO ex VALUES ('{"data": []}', 'nothing special');
+
+-- Update the unindexed value of att1, this should be a HOT update and and should
+-- update the summarizing index.
+UPDATE ex SET att1 = att1 || '{"status": "stalemate"}';
+SELECT * FROM check_hot_updates(1, 'ex');
+
+-- Update the indexed value of att2, a summarized value, this is a summarized
+-- only update and should use the HOT path while still triggering an update to
+-- the summarizing BRIN index.
+UPDATE ex SET att2 = 'special indeed';
+SELECT * FROM check_hot_updates(2, 'ex');
+
+-- Update to att1 doesn't change the indexed value while the update to att2 does,
+-- this again is a summarized only update and should use the HOT path as well as
+-- trigger an update to the BRIN index.
+UPDATE ex SET att1 = att1 || '{"status": "checkmate"}', att2 = 'special, so special';
+SELECT * FROM check_hot_updates(3, 'ex');
+
+-- This updates both indexes, the expression index on att1 and the summarizing
+-- index on att2.  This should not be a HOT update because there are modified
+-- indexes and only some are summarized, not all.  This should force all
+-- indexes to be updated.
+UPDATE ex SET att1 = att1 || '{"data": [1,2,3]}', att2 = 'do you want to play a game?';
+SELECT * FROM check_hot_updates(3, 'ex');
+
+DROP TABLE ex CASCADE;
+
+-- ================================================================
+-- Ensure custom type equality operators are used
+-- ================================================================
+
+CREATE TYPE my_custom_type AS (val int);
+
+-- Comparison functions (returns boolean)
+CREATE FUNCTION my_custom_lt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val < b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_le(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val <= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_eq(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val = b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ge(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val >= b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_gt(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val > b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+CREATE FUNCTION my_custom_ne(a my_custom_type, b my_custom_type) RETURNS boolean AS $$
+BEGIN
+    RETURN a.val != b.val;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Comparison function (returns -1, 0, 1)
+CREATE FUNCTION my_custom_cmp(a my_custom_type, b my_custom_type) RETURNS int AS $$
+BEGIN
+    IF a.val < b.val THEN
+        RETURN -1;
+    ELSIF a.val > b.val THEN
+        RETURN 1;
+    ELSE
+        RETURN 0;
+    END IF;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the operators
+CREATE OPERATOR < (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_lt,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+CREATE OPERATOR <= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_le,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+CREATE OPERATOR = (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_eq,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+CREATE OPERATOR >= (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ge,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+CREATE OPERATOR > (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_gt,
+    COMMUTATOR = <,
+    NEGATOR = <=
+);
+
+CREATE OPERATOR <> (
+    LEFTARG = my_custom_type,
+    RIGHTARG = my_custom_type,
+    PROCEDURE = my_custom_ne,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+-- Create the operator class (including the support function)
+CREATE OPERATOR CLASS my_custom_ops
+    DEFAULT FOR TYPE my_custom_type USING btree AS
+    OPERATOR 1 <,
+    OPERATOR 2 <=,
+    OPERATOR 3 =,
+    OPERATOR 4 >=,
+    OPERATOR 5 >,
+    FUNCTION 1 my_custom_cmp(my_custom_type, my_custom_type);
+
+-- Create the table
+CREATE TABLE my_table (
+    id int,
+    custom_val my_custom_type
+);
+
+-- Insert some data
+INSERT INTO my_table (id, custom_val) VALUES
+(1, ROW(3)::my_custom_type),
+(2, ROW(1)::my_custom_type),
+(3, ROW(4)::my_custom_type),
+(4, ROW(2)::my_custom_type);
+
+-- Create a function to use when indexing
+CREATE OR REPLACE FUNCTION abs_val(val my_custom_type) RETURNS int AS $$
+BEGIN
+  RETURN abs(val.val);
+END;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
+
+-- Create the index
+CREATE INDEX idx_custom_val_abs ON my_table (abs_val(custom_val));
+
+-- Update 1
+UPDATE my_table SET custom_val = ROW(5)::my_custom_type WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 2
+UPDATE my_table SET custom_val = ROW(0)::my_custom_type WHERE custom_val < ROW(3)::my_custom_type;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 3
+UPDATE my_table SET custom_val = ROW(6)::my_custom_type WHERE id = 3;
+SELECT * FROM check_hot_updates(0, 'my_table');
+
+-- Update 4
+UPDATE my_table SET id = 5 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 'my_table');
+
+-- Query using the index
+SELECT * FROM my_table WHERE abs_val(custom_val) = 6;
+
+-- Clean up test case
+DROP TABLE my_table CASCADE;
+DROP OPERATOR CLASS my_custom_ops USING btree CASCADE;
+DROP OPERATOR < (my_custom_type, my_custom_type);
+DROP OPERATOR <= (my_custom_type, my_custom_type);
+DROP OPERATOR = (my_custom_type, my_custom_type);
+DROP OPERATOR >= (my_custom_type, my_custom_type);
+DROP OPERATOR > (my_custom_type, my_custom_type);
+DROP OPERATOR <> (my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_lt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_le(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_eq(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ge(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_gt(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_ne(my_custom_type, my_custom_type);
+DROP FUNCTION my_custom_cmp(my_custom_type, my_custom_type);
+DROP FUNCTION abs_val(my_custom_type);
+DROP TYPE my_custom_type CASCADE;
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 27a4d131897..1eace574994 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -390,6 +390,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.49.0

#38Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Greg Burd (#37)
Re: Expanding HOT updates for expression and partial indexes

On Sat, 22 Nov 2025, 22:30 Greg Burd, <greg@burd.me> wrote:

Thanks for pointing out the oversight for index-oriented scans (IOS),
you're right that the code in v22 doesn't handle that correctly. I'll
fix that. I still think that indexes that don't support IOS can and
should use the type-specific equality checks. This opens the door to
HOT with custom types that have unusual equality rules (see BSON).

Do you have specific examples why it would be safe to default to
"unusual equality rules" for generally any index's data ingestion
needs? Why e.g. BSON must always be compared with their special
equality test (and not datumIsEqual), and why IOS-less indexes in
general are never going to distinguish between binary distinct but
btree-equal values, and why exact equality is the special case here?

I understand that you want to maximize optimization for specific
workloads that you have in mind, but lacking evidence to the contrary
I am really not convinced that your workloads are sufficiently
generalizable that they can (and should) be the baseline for these new
HOT rules: I have not yet seen good arguments why we could relax
"datum equality" to "type equality" without potentially breaking
existing indexes.

HOT was implemented quite conservatively to make sure that there are
no issues where changed values are not reflected in indexes: each
indexed TID represents a specific and unchanging set of indexed
values, in both the key and non-key attributes of indexes. If a value
changes however so slightly, that may be a cause for indexes to treat
it differently, and thus HOT must not be used.

Aside: The amsummarizing optimization gets around that check by
realizing the TID itself isn't really indexed, so the rules can be
relaxed around that, but it still needs to go through the effort to
update the summarizing indexes if the relevant attributes were ever so
slightly updated.

This patch right now wants to change these rules and behaviour of HOT
in two ways:

1.) Instead of only testing attributes mentioned by indexed
expressions for changes, it wants to test the output of the indexed
expressions.
I would consider this to be generally safe, as long as the expressions
comply with the rules we have for indexed expressions. [Which, if not
held, would break IOS and various other things, too, so relying on
these rules isn't new or special].

2.) Instead of datumIsEqual, it (by default) wants to do equality
checks as provided by the type's default btree opclass' = operator.
I have not seen evidence that this is safe. I have even explained with
an example that IOS will return distinctly wrong results if only
btree's = operator is used to determine if HOT can be applied, and
that doesn't even begin to cover the issues related to indexes that
may handle data differently from Btree.
I also don't want indexes that at some point in the future invent
support for IOS to return subtly incorrect results due to HOT checks
that depended on the output of a previous version's amcanreturn
output.

So, IMV, tts_attr_equal is just a rather expensive version of
datumIsEqual: the fast path cases would've been handled by
datumIsEqual at least as fast (without a switch() statement with 18
specific cases and a default branch); if there is no btree operator
it'll still default to btree compare, and if not then if the slow path
uses correctly implemented compare operators (for HOT, and potentially
all other possible indexes), then these would have an output that is
indistinguishable from datumIsEqual, with the only difference the
address and performance of the called function and a lot of added
catalog lookups.

All together, I think it's best to remove the second component of the
changes to the HOT rules (changing the type of matching done for
indexed values with tts_attr_compare) from this patchset.
If you believe this should be added regardless, I think it's best
discussed separately in its own thread and patchset -- it should be
relatively easy to introduce in both current and future versions of
this code, and (if you're correct and this is safe) it would have some
benefits even when committed on its own.

Kind regards,

Matthias van de Meent

#39Greg Burd
greg@burd.me
In reply to: Matthias van de Meent (#38)
3 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

On Nov 24 2025, at 1:59 pm, Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:

... <awesome thoughtful questions, insights, etc> ...

Kind regards,

Matthias van de Meent

Hey Matthias,

I've updated the patch set to v25 and taken your suggested approach of
minimizing changes in hopes of getting the majority of this patch
series committed (I hope) soon. By that I mean that $subject isn't
really accurate anymore. This patch uses datumIsEqual(), but still
introduces the new index AM API and still moves
HeapDetermineColumnsInfo() into a function in nodeModifyTable.c called
ExecWhichIndexesRequireUpdates(). The "big idea" is that before calling
into the table AM to update a tuple the executor should know what the
impact of that update will be for the indexes on the relation.
ExecWhichIndexesRequireUpdates() finds the set of attributes that were
a) modified and b) are referenced by an index and cause that index to
need a new index tuple. This is left up to heap now, but really should
be generic across all table AMs (IMO), so that's what I've done.

At some point in the future maybe there's a way to switch from the
heap-specific model of all/none/summarized-only to something where we
only update indexes that really require the updates (HOT, WARM, etc.)
and I think this is a step in that direction, but for now the logic
remains the same as does the signal (TU_Updated).

I'll re-introduce $subject as a layer on v24 next week at which point
I'll try to address all the good points you raised in your email. Those
additions (HOT expressions, HOT partial indexes, and type-specific
equality tests) are the most controversial.

I think it may be possible that these first few patches are less
controversial and could make the cut sooner while those other ideas
remain up for debate. I'm open to that, I think the work in the attached
set is good and valuable on it's own.

So, the attached patch set

Benefits of the patch:
* the tests for what changed move outside of the buffer lock
* the redundant index_unchanged_by_update() is removed

Downsides of the patch:
* a bit of new overhead in some cases
* a bit more complicated logic than before

Recall that this patch set combines with another one of mine on the list
[1]: /messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me
that case and you'll see that simple_heap_update() still depends on HeapDetermineColumnsInfo().

* 0001 - Prepare heapam_tuple_update() and simple_heap_update() for divergence

This splits off the top of heap_update() and places that logic in both
heapam_tuple_update() and simple_heap_update(). This patch is also
present in the other thread [1]/messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me and essentially the same. That thread
addresses the changes to the catalog tuple updates. No real effort was
made to make this patch "pretty" or "stand-alone" as it really is a
precursor to the work in 0002 and in [1]/messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me.

* 0002 - Track changed indexed columns in the executor during UPDATEs

This is where the meat is, as described in earlier emails and the commit
message. HeapDetermineColumnsInfo() logic moves up into the executor
into ExecWhichIndexesRequireUpdates(). Some heap-specific logic related
to replica identity remains in heapam_tuple_update().

* 0003 - Replace index_unchanged_by_update() with ri_ChangedIndexedCols

This removes the now redundant index_unchanged_by_update() function and
instead uses the information gathered in
ExecWhichIndexesRequireUpdates() and recorded in ri_ChangedIndexCols for
the same outcome.

best.

-greg

[1]: /messages/by-id/2C5C8B8D-8B36-4547-88EB-BDCF9A7C8D94@greg.burd.me

Attachments:

v25-0001-Prepare-heapam_tuple_update-and-simple_heap_upda.patchapplication/octet-streamDownload
From 88fbab1f79d82b1eb51be2054cab303ae856f3e2 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v25 1/3] Prepare heapam_tuple_update() and
 simple_heap_update() for divergence

This commit lays the foundation for larger changes to come by taking the
first portion of heap_update() through the HeapDeterminColumnsInfo() and
replicating that logic in both heapam_tuple_update() and
simple_heap_upate().  This is done so that these two paths might diverge
in implementation later on.  The simple_heap_update() path deals solely
with updates to catalog tuples which could record their modified
attributes rather than relearn them.  The remaining calls from the
executor into the table AM update API could include the set of updated
attributes.  This is foreshadowing... of course, as that's what the next
commit will start to do.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4d382a04338..30847db1fe3 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -103,10 +101,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2814,6 +2812,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2826,6 +2825,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3029,6 +3030,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3050,7 +3052,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3262,7 +3267,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3270,30 +3278,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3307,7 +3306,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3315,144 +3313,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3464,7 +3331,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3488,17 +3355,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3530,8 +3390,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3571,13 +3431,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3587,9 +3446,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3614,8 +3473,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3656,9 +3515,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3668,20 +3527,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3694,39 +3553,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3739,10 +3592,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3753,9 +3606,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3767,12 +3620,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3787,7 +3640,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3815,7 +3668,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3832,12 +3685,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3870,9 +3723,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3882,18 +3735,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3902,7 +3755,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3916,10 +3769,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3941,7 +3794,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3977,20 +3830,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4028,7 +3881,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4036,7 +3889,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4044,7 +3896,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4055,7 +3907,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4072,10 +3924,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4097,7 +3947,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4106,7 +3956,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4115,17 +3965,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4133,7 +3983,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4158,12 +4008,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4188,7 +4038,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4196,14 +4046,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4236,13 +4086,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4251,7 +4094,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4423,7 +4266,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4506,25 +4349,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9164,12 +9157,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9200,17 +9192,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9223,7 +9211,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9232,8 +9220,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..1cf9a18775d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 632c4332a8c..2f9a2b069cd 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -364,11 +364,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -430,6 +432,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.51.2

v25-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From 6b95e4418b2bfc20245e24b75ea7173b4d83552e Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v25 3/3] Replace index_unchanged_by_update() with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update() with the same information
found earlier in ExecWhichIndexesRequireUpdates() and stashed in
ri_ChangedIndexedCols.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 9d7cb4438d5..c665aa744b3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -304,8 +304,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 05ae7eb9f65..ff9d49d620d 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -143,11 +143,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -451,10 +446,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -1014,152 +1006,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 1f1364f9df9..e9a53b95caf 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e10f4239de9..1259897282e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -216,10 +216,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.51.2

v25-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From fff7f1524821c37db3b512a417031b141c56f631 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v25 2/3] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo() in heap_update().

ExecWhichIndexesRequireUpdates() replaces HeapDeterminesColumnsInfo()
when invoked from the table AM API via heapam_tuple_update(). The
test for equality remains datumIsEqual() as before.

This change necessitated some logic changes in execReplication() as it
performs updates now must provide the set of attributes that are both
changed and referenced by indexes.  Luckilly, this is available within
calls to slot_modify_data() where LogicalRepTupleData is processed and
has a record of updated attributes.  In this case rather than using
ExecWhichIndexesRequireUpdates() we can preseve what slot_modify_data()
identifies as the modified set and then intersect that with the set of
indexes on the relation and get the correct set of modified indexed
attributes required on heap_update().

This commit also extends the role index AMs play determining if they
require an update. A new optional index AM API, amcomparedatums(), is
added to allow index access methods to provide custom logic for
comparing datums. Hash and Gin indexes now implement this function. When
not implemented the executor will compare TupleTableSlot datum for
equality using datumIsEqual() as before.

Because heap_update() now requires the caller to provide the modified
indexed columns simple_heap_update() has become a tad more complex.  It
is only called from CatalogTupleUpdate() which either updates heap
tuples via their Form_XXX or by calling heap_modify_tuple().  In both
cases the caller does know the modified set of attributes, but sadly
those attributes are lost before being provided to simple_heap_update().
Due to that the "simple" path has to (for now) retain the
HeapDetermineColumnsInfo() logic in order for catalog updates to
potentially take the HOT path.
---
 src/backend/access/brin/brin.c                |   1 +
 src/backend/access/gin/ginutil.c              |  90 ++-
 src/backend/access/hash/hash.c                |  44 ++
 src/backend/access/heap/heapam.c              |  20 +-
 src/backend/access/heap/heapam_handler.c      |  76 +-
 src/backend/access/nbtree/nbtree.c            |   1 +
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/bootstrap/bootstrap.c             |   8 +
 src/backend/catalog/index.c                   |  54 ++
 src/backend/catalog/indexing.c                |  16 +-
 src/backend/catalog/toasting.c                |   4 +
 src/backend/executor/execIndexing.c           |  41 +-
 src/backend/executor/execMain.c               |   1 +
 src/backend/executor/execReplication.c        |   7 +
 src/backend/executor/nodeModifyTable.c        | 283 +++++++-
 src/backend/nodes/bitmapset.c                 |   4 +
 src/backend/nodes/makefuncs.c                 |   4 +
 src/backend/replication/logical/worker.c      |  70 +-
 src/backend/utils/cache/relcache.c            |  15 +
 src/include/access/amapi.h                    |  28 +
 src/include/access/gin.h                      |   3 +
 src/include/access/heapam.h                   |   6 +-
 src/include/access/nbtree.h                   |   4 +
 src/include/access/tableam.h                  |   8 +-
 src/include/catalog/index.h                   |   1 +
 src/include/executor/executor.h               |   9 +
 src/include/nodes/execnodes.h                 |  20 +
 src/include/utils/rel.h                       |   1 +
 src/include/utils/relcache.h                  |   1 +
 .../expected/insert-conflict-specconflict.out |  20 +
 .../regress/expected/heap_hot_updates.out     | 650 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   6 +
 src/test/regress/sql/heap_hot_updates.sql     | 513 ++++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 34 files changed, 1941 insertions(+), 74 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index cb3331921cb..36e639552e6 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -290,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = NULL;
 	amroutine->ambeginscan = brinbeginscan;
 	amroutine->amrescan = brinrescan;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 78f7b7a2495..cc5410960a6 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = ginbuildphasename;
 	amroutine->amvalidate = ginvalidate;
+	amroutine->amcomparedatums = gincomparedatums;
 	amroutine->amadjustmembers = ginadjustmembers;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -729,3 +724,84 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/* Extract keys from both datums using existing GIN infrastructure */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys, definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		if (ginCompareEntries(&ginstate, attnum,
+							  old_keys[i], old_categories[i],
+							  new_keys[i], new_categories[i]) != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 53061c819fb..91371dfdacd 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -50,6 +50,10 @@ static void hashbuildCallback(Relation index,
 							  void *state);
 
 
+static bool hashcomparedatums(Relation index, int attnum,
+							  Datum old_datum, bool old_isnull,
+							  Datum new_datum, bool new_isnull);
+
 /*
  * Hash handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -98,6 +102,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = hashvalidate;
+	amroutine->amcomparedatums = hashcomparedatums;
 	amroutine->amadjustmembers = hashadjustmembers;
 	amroutine->ambeginscan = hashbeginscan;
 	amroutine->amrescan = hashrescan;
@@ -944,3 +949,42 @@ hashtranslatecmptype(CompareType cmptype, Oid opfamily)
 		return HTEqualStrategyNumber;
 	return InvalidStrategy;
 }
+
+/*
+ * hashcomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * Returns true if the hash values are identical (index doesn't need update).
+ */
+bool
+hashcomparedatums(Relation index, int attnum,
+				  Datum old_datum, bool old_isnull,
+				  Datum new_datum, bool new_isnull)
+{
+	uint32		old_hashkey;
+	uint32		new_hashkey;
+
+	/* If both are NULL, they're equal */
+	if (old_isnull && new_isnull)
+		return true;
+
+	/* If NULL status differs, they're not equal */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * _hash_datum2hashkey() is used because we know this can't be a cross
+	 * type comparison.
+	 */
+	old_hashkey = _hash_datum2hashkey(index, old_datum);
+	new_hashkey = _hash_datum2hashkey(index, new_datum);
+
+	/*
+	 * If hash keys are identical, the index entry would be the same. Return
+	 * true to indicate no index update needed.
+	 *
+	 * Note: Hash collisions are rare but possible. If hash(x) == hash(y) but
+	 * x != y, the hash index still treats them identically, so we correctly
+	 * return true.
+	 */
+	return (old_hashkey == new_hashkey);
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 30847db1fe3..3e88bdbbda8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3278,12 +3278,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4352,8 +4352,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4482,7 +4483,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4515,7 +4516,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4541,6 +4541,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1cf9a18775d..7527809ec08 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					const Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -405,25 +407,66 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +480,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fdff960c130..e435f0d5db4 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -155,6 +155,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = btadjustmembers;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 1e099febdc8..15f0dd7aa28 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -367,6 +367,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -377,7 +378,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								mix_attrs,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index fc8638c1b61..329c110d0bf 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -961,10 +961,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..e88db7e919b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2414,6 +2416,58 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		indnatts = ii->ii_NumIndexAttrs;
+
+		/* Collect key attributes used by the index, key and including */
+		for (i = 0; i < indnatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		/* Combine key, including, and expression attributes, but not predicate */
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..a361c215490 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 874a8fc89ad..9d7cb4438d5 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index dd323c9b9fd..05ae7eb9f65 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
@@ -324,8 +328,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -349,13 +353,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -372,7 +376,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -393,8 +397,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -402,11 +413,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -613,7 +623,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 27c9eec697b..6b7b6bc8019 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index def32774c90..2709e2db0f2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e44f1223886..a06cf34ca70 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -53,12 +54,18 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/attnum.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,8 +75,11 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
 #include "utils/injection_point.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -176,6 +186,220 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   ResultRelInfo *resultRelInfo,
 										   bool canSetTag);
 
+/*
+ * ExecCheckIndexedAttrsForChanges
+ *
+ * Determine which indexes need updating by finding the set of modified indexed
+ * attributes.
+ *
+ * For which implement the amcomparedatums() index AM API we'll need to form
+ * index datum and compare each attribute to see if anything actually changed.
+ *
+ * The goal is for the executor to know, ahead of calling into the table AM to
+ * process the update and before calling into the index AM for inserting new
+ * index tuples, which attributes in the new TupleTableSlot, if any, truely
+ * necessitate a new index tuple.
+ *
+ * Returns a Bitmapset of attributes that intersects with indexes which require
+ * a new index tuple.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								EState *estate,
+								TupleTableSlot *old_tts,
+								TupleTableSlot *new_tts)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *mix_attrs = NULL;	/* modified indexed attributes */
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/* Find the indexes that reference this attribute */
+	for (int i = 0; i < relinfo->ri_NumIndices; i++)
+	{
+		Relation	index = relinfo->ri_IndexRelationDescs[i];
+		IndexAmRoutine *amroutine = index->rd_indam;
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		Bitmapset  *m_attrs = NULL; /* (possibly) modified indexed attributes */
+		Bitmapset  *u_attrs = NULL; /* unmodified indexed attributes */
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		supports_ios = (amroutine->amcanreturn != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		int			num_datums = supports_ios ?
+			indexInfo->ii_NumIndexAttrs : indexInfo->ii_NumIndexKeyAttrs;
+
+		/* If we've reviewed all the attributes on this index, move on */
+		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
+			continue;
+
+		/* Add partial index attributes */
+		if (is_partial)
+			m_attrs = bms_add_members(m_attrs, indexInfo->ii_PredicateAttrs);
+
+		/* Compare the index datums for equality */
+		for (int j = 0; j < num_datums; j++)
+		{
+			AttrNumber	rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+			int			rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
+			int			nth_expr = 0;
+			int16		typlen;
+			bool		typbyval;
+			Datum		old_value;
+			Datum		new_value;
+			bool		old_null;
+			bool		new_null;
+			bool		values_equal = false;
+
+			/* System attributes */
+			if (rel_attrnum < 0)
+			{
+				/* Extract system values from both slots for this attribute */
+				old_value = slot_getsysattr(old_tts, rel_attrnum, &old_null);
+				new_value = slot_getsysattr(new_tts, rel_attrnum, &new_null);
+
+				/* The only allowed system columns are OIDs, so do this */
+				values_equal = (DatumGetObjectId(old_value) == DatumGetObjectId(new_value));
+				goto equality_determined;
+			}
+
+			/*
+			 * This is an expression attribute, but in an effort to avoid the
+			 * expense of IndexFormDatum we're now faced with testing for
+			 * equality so we'll have to exec the expressions and test for
+			 * binary equality of the results.
+			 */
+			else if (rel_attrnum == 0)
+			{
+				TupleTableSlot *save_scantuple = econtext->ecxt_scantuple;
+				Oid			expr_type_oid;
+				Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+				ExprState  *state;
+
+				if (indexInfo->ii_ExpressionsState == NIL)
+				{
+					/* First time through, set up expression evaluation state */
+					indexInfo->ii_ExpressionsState =
+						ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+				}
+
+				state = (ExprState *) list_nth(indexInfo->ii_ExpressionsState, nth_expr);
+
+				econtext->ecxt_scantuple = old_tts;
+				old_value = ExecEvalExprSwitchContext(state,
+													  GetPerTupleExprContext(estate),
+													  &old_null);
+
+				econtext->ecxt_scantuple = new_tts;
+				new_value = ExecEvalExprSwitchContext(state,
+													  GetPerTupleExprContext(estate),
+													  &new_null);
+
+				econtext->ecxt_scantuple = save_scantuple;
+
+				/*
+				 * NOTE: test for NULL cases here to potentially avoid looking
+				 * up the type information.  It's a tad redundant, but worth
+				 * it.
+				 */
+
+				/* A change to/from NULL, so not equal */
+				if (old_null != new_null)
+				{
+					values_equal = false;
+					goto equality_determined;
+				}
+
+				/* Both NULL, no change record as unmodified */
+				if (old_null)
+				{
+					values_equal = true;
+					goto equality_determined;
+				}
+
+				/* Get type OID from the expression */
+				expr_type_oid = exprType((Node *) expr);
+
+				/* Get type information from the OID */
+				get_typlenbyval(expr_type_oid, &typlen, &typbyval);
+			}
+			/* Not a system or expression attribute */
+			else
+			{
+				CompactAttribute *att = TupleDescCompactAttr(tupdesc, rel_attrnum - 1);
+
+				/* Extract values from both slots for this attribute */
+				old_value = slot_getattr(old_tts, rel_attrnum, &old_null);
+				new_value = slot_getattr(new_tts, rel_attrnum, &new_null);
+
+				typlen = att->attlen;
+				typbyval = att->attbyval;
+			}
+
+			/* A change to/from NULL, so not equal */
+			if (old_null != new_null)
+			{
+				values_equal = false;
+				goto equality_determined;
+			}
+
+			/* Both NULL, no change record as unmodified */
+			if (old_null)
+			{
+				values_equal = true;
+				goto equality_determined;
+			}
+
+			if (has_am_compare)
+			{
+				/*
+				 * NOTE: For AM comparison, pass the 1-based index attribute
+				 * number. The AM's compare function expects the same
+				 * numbering as used internally by the AM.
+				 */
+				values_equal = amroutine->amcomparedatums(index, j + 1,
+														  old_value, old_null,
+														  new_value, new_null);
+			}
+			else
+			{
+				values_equal = datumIsEqual(old_value, new_value, typbyval, typlen);
+			}
+
+	equality_determined:;
+			if (!values_equal)
+				if (rel_attrnum == 0)
+				{
+					Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+
+					pull_varattnos((Node *) expr, relinfo->ri_RangeTableIndex, &m_attrs);
+				}
+				else
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+			else
+				u_attrs = bms_add_member(u_attrs, rel_attridx);
+
+			if (rel_attrnum == 0)
+				nth_expr++;
+		}
+
+		/*
+		 * Here we know all the attributes that might be modified and all
+		 * those we know haven't been across all indexes.  Take the difference
+		 * and add it to the modified indexed attributes set.
+		 */
+		m_attrs = bms_del_members(m_attrs, u_attrs);
+		mix_attrs = bms_add_members(mix_attrs, m_attrs);
+
+		bms_free(m_attrs);
+		bms_free(u_attrs);
+	}
+
+	return mix_attrs;
+}
 
 /*
  * Verify that the tuples to be produced by INSERT match the
@@ -2170,14 +2394,17 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* The set of modified indexed attributes that trigger new index entries */
+	Bitmapset  *mix_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2294,9 +2521,38 @@ lreplace:
 		ExecConstraints(resultRelInfo, slot, estate);
 
 	/*
-	 * replace the heap tuple
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	/*
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * Next up we need to find out the set of indexed attributes that have
+	 * changed in value and should trigger a new index tuple.  We could start
+	 * with the set of updated columns via ExecGetUpdatedCols(), but if we do
+	 * we will overlook attributes directly modified by heap_modify_tuple()
+	 * which are not known to ExecGetUpdatedCols().
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot);
+
+	/*
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2308,8 +2564,12 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								mix_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = mix_attrs;
+
 	return result;
 }
 
@@ -2327,7 +2587,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
@@ -2526,8 +2786,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3224,8 +3485,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3250,6 +3511,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4356,7 +4618,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4532,6 +4794,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index 7b1e9d94103..c522971a37c 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..1f1364f9df9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -857,10 +857,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 93970c6af29..b72bf2055f6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -275,7 +275,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -285,12 +284,14 @@
 #include "storage/procarray.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
+#include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1111,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1199,27 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+
+				if (!datumIsEqual(srcslot->tts_values[i],
+								  slot->tts_values[i],
+								  att->attbyval,
+								  att->attlen))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1227,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2945,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2962,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2987,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3499,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3569,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3595,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 915d0bc9084..32825596be1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2482,6 +2482,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5284,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5307,6 +5309,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5332,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5373,6 +5378,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5505,10 +5511,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5531,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5535,6 +5547,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5551,6 +5564,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 63dd41c1f21..9bdf73eda59 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 13ea91922ef..2f265f4816c 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 2f9a2b069cd..5783dbebff0 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -369,7 +369,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -404,8 +404,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 16be5c7a9c1..42bd329eaad 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1210,6 +1210,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 2fa790b6bf5..d94dfc9b41d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1512,12 +1513,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2020,6 +2021,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index dda95e54903..8d364f8b30f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..2b851d9964c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
@@ -800,5 +805,9 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+												  EState *estate,
+												  TupleTableSlot *old_tts,
+												  TupleTableSlot *new_tts);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64ff6996431..e10f4239de9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -499,6 +513,12 @@ typedef struct ResultRelInfo
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
 
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
+	Bitmapset  *ri_ChangedIndexedCols;
+
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
 	/* Slot to hold that tuple */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 80286076a11..b23a7306e69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..d3fbb8b093a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
index e34a821c403..54b3981918c 100644
--- a/src/test/isolation/expected/insert-conflict-specconflict.out
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -80,6 +80,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
@@ -172,6 +176,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
@@ -369,6 +377,10 @@ key|data
 step s1_commit: COMMIT;
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 step s2_upsert: <... completed>
 step controller_show: SELECT * FROM upserttest;
 key|data       
@@ -530,6 +542,14 @@ isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
 step s2_commit: COMMIT;
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
 step s1_upsert: <... completed>
 step s1_noop: 
 step controller_show: SELECT * FROM upserttest;
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..14276e3cbca
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,650 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+INSERT INTO t VALUES (1, 50, 'below range');
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     150
+(1 row)
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     160
+(1 row)
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           4 |                100.00 | t
+(1 row)
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+SELECT id, value, description FROM t;
+ id | value |  description  
+----+-------+---------------
+  1 |    50 | updated again
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_brin     |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT (BRIN allows it for single row)
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_hash     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (HASH blocks it)
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: 1 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT (BRIN permits single-row updates)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+-- Expected: 2 HOT (HASH blocks it)
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+-- Expected: 3 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..f3db9270fe6 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: heap_hot_updates
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..e047bcddf5c
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,513 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+
+INSERT INTO t VALUES (1, 50, 'below range');
+
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+SELECT id, value, description FROM t;
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+-- Expected: 1 HOT (BRIN allows it for single row)
+
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+-- Expected: 0 HOT (HASH blocks it)
+
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+
+
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (BRIN permits single-row updates)
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT
+
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (HASH blocks it)
+
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+-- Expected: 3 HOT
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t CASCADE;
+
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cf3f6a7dafd..4cc7a9d4c7d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -390,6 +390,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.51.2

#40Greg Burd
greg@burd.me
In reply to: Greg Burd (#39)
11 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

I've updated the patch set a tad and I've got some benchmark results
(and questions).

PATCHES
===========================================================

* 0001 - Prepare heapam_tuple_update() and simple_heap_update() for divergence

Unchanged.

* 0002 - Track changed indexed columns in the executor during UPDATEs

Bug/oversight minor fix related to partial index attributes.

Also, I mistakenly said that v25 removed the $subject (ability to allow
expression indexes to be HOT). That's not true, they can go HOT with
this patch provided that the result of the expression evaluated using
the before/after attribute values are equal using datumIsEqual(). When
that is the case, as can happen with updates to fields within JSONB
columns when indexes are on other fields, the update can be HOT should
the heap find room on the page to store the new tuple.

* 0003 - Replace index_unchanged_by_update() with ri_ChangedIndexedCols

Unchanged.

* 0004 - Identify if partial indexes are impacted by an update

This is the new piece, it existed in the v24 patch set and now it is
back. This checks the before/after partial index expression and when
both are outside the predicate then it is possible that heap can use the
HOT path whereas in the past this couldn't happen. In the past any
update to an attribute in an index, even if it was outside the
predicate, was (is) HOT blocking.

SUMMARY
===========================================================

I've just started to scratch the surface of performance testing for
this, attached is a very simple comparison of master/patch for a basic
update load that should always go HOT in either case. It shows about 1%
variance between the two (-O0), tests run on my laptop so that's
essentially no difference despite more overhead of the new function and
that it seems to be called more frequently due to (guessing here) more
opportunity for TM_Updated to be the return from heapam_tuple_update.
Your thoughts welcome here, or best/worst case ideas for tests to run.

Next up I plan to layer the controversial type-specific piece into this
patch set if nothing else just as a record of what's left over. Then
I'll try to better isolate good/bad performance implications of this
patch set.

Ideally, this patch set and the one (under development) for catalog
tuples could combine to completely restructure the heap update process
and open the door to more HOT updates and faster catalog updates. But,
I still have to demonstrate that. For JSONB heavy applications this
should be a net win, for the rest it should be a minor or zero
regression. For other custom implementations of indexes over
specialized types (as is the case for the new open sourced DocumentDB
work) this opens the door for HOT updates when possible. All of that is
the the hope, it's time to measure hope against reality. :)

This patch set does start to move the executor away from a heap-specific
view of the world where updates are all/none/summarizing. This
potentially eases the integration of WARM or PHOT-like solutions where
we only update those indexes that are materially impacted by an update.
It should be clear by now, that's my ultimate goal.

best.

-greg

Attachments:

v26-0001-Prepare-heapam_tuple_update-and-simple_heap_upda.patchapplication/octet-streamDownload
From f4756df0c566a10dcf978f2b76726e8bb3b30b0c Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v26 1/4] Prepare heapam_tuple_update() and
 simple_heap_update() for divergence

This commit lays the foundation for larger changes to come by taking the
first portion of heap_update() through the HeapDeterminColumnsInfo() and
replicating that logic in both heapam_tuple_update() and
simple_heap_upate().  This is done so that these two paths might diverge
in implementation later on.  The simple_heap_update() path deals solely
with updates to catalog tuples which could record their modified
attributes rather than relearn them.  The remaining calls from the
executor into the table AM update API could include the set of updated
attributes.  This is foreshadowing... of course, as that's what the next
commit will start to do.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 9636bb53ddd..d5a1b844b5a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -103,10 +101,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2814,6 +2812,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2826,6 +2825,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3029,6 +3030,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3050,7 +3052,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3262,7 +3267,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3270,30 +3278,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3307,7 +3306,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3315,144 +3313,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3464,7 +3331,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3488,17 +3355,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3530,8 +3390,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3571,13 +3431,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3587,9 +3446,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3614,8 +3473,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3656,9 +3515,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3668,20 +3527,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3694,39 +3553,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3739,10 +3592,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3753,9 +3606,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3767,12 +3620,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3787,7 +3640,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3815,7 +3668,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3832,12 +3685,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3870,9 +3723,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3882,18 +3735,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3902,7 +3755,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3916,10 +3769,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3941,7 +3794,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3977,20 +3830,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4028,7 +3881,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4036,7 +3889,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4044,7 +3896,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4055,7 +3907,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4072,10 +3924,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4097,7 +3947,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4106,7 +3956,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4115,17 +3965,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4133,7 +3983,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4158,12 +4008,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4188,7 +4038,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4196,14 +4046,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4236,13 +4086,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4251,7 +4094,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4423,7 +4266,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4506,25 +4349,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9164,12 +9157,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9200,17 +9192,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9223,7 +9211,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9232,8 +9220,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index dd4fe6bf62f..8708af01f8c 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f7e4ae3843c..9398216d5d9 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -364,11 +364,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -430,6 +432,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.51.2

v26-0004-Identify-if-partial-indexes-are-impacted-by-an-u.patchapplication/octet-streamDownload
From 516f799c70e3d6bdf123afc9ab7650bb0edc4de6 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 5 Dec 2025 13:42:13 -0500
Subject: [PATCH v26 4/4] Identify if partial indexes are impacted by an
 update.

The executor now determines which, if any, attributes that are indexed
are both modified and force new index tuples to be inserted ahead of
calling into the table AM update function.  Prior to this commit the
test for partial indexes happened after table update, this changes that
to before so that in cases where the before and after tuples both lie
outside the predicate the attributes for the predicate are not included
in the "modified indexed attributes" bitmapset.
---
 src/backend/executor/nodeModifyTable.c | 53 ++++++++++++++++++++++++--
 1 file changed, 49 insertions(+), 4 deletions(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 52a74479502..c55d84f86f8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -226,9 +226,11 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 		Bitmapset  *m_attrs = NULL; /* (possibly) modified indexed attrs */
 		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attrs */
 		Bitmapset  *u_attrs = NULL; /* unmodified indexed attrs */
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
 		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
 		bool		supports_ios = (amroutine->amcanreturn != NULL);
 		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		TupleTableSlot *save_scantuple;
 		ExprContext *econtext = GetPerTupleExprContext(estate);
 		int			num_datums = supports_ios ?
 			indexInfo->ii_NumIndexAttrs : indexInfo->ii_NumIndexKeyAttrs;
@@ -237,9 +239,51 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
 			continue;
 
-		/* Add partial index attributes */
-		if (is_partial)
-			p_attrs = bms_add_members(p_attrs, indexInfo->ii_PredicateAttrs);
+		/* Checking partial at this point isn't viable when we're serializable */
+		if (is_partial && IsolationIsSerializable())
+		{
+			p_attrs = bms_add_members(p_attrs, pre_attrs);
+		}
+		/* Check partial index predicate */
+		else if (is_partial)
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+				continue;
+
+			/* A transition means we need to update the index */
+			if (old_qualifies != new_qualifies)
+				p_attrs = bms_copy(pre_attrs);
+
+			/*
+			 * When both are within the predicate we must update this index,
+			 * but only if one of the index key attributes changed.
+			 */
+		}
 
 		/* Compare the index datums for equality */
 		for (int j = 0; j < num_datums; j++)
@@ -275,11 +319,12 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 			 */
 			else if (rel_attrnum == 0)
 			{
-				TupleTableSlot *save_scantuple = econtext->ecxt_scantuple;
 				Oid			expr_type_oid;
 				Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
 				ExprState  *state;
 
+				save_scantuple = econtext->ecxt_scantuple;
+
 				if (indexInfo->ii_ExpressionsState == NIL)
 				{
 					/* First time through, set up expression evaluation state */
-- 
2.51.2

v26-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From 99659e8c9398790ea3d1bd34d46013498bff2f52 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v26 3/4] Replace index_unchanged_by_update() with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update() with the same information
found earlier in ExecWhichIndexesRequireUpdates() and stashed in
ri_ChangedIndexedCols.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 9d7cb4438d5..c665aa744b3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -304,8 +304,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 1b17d8d135f..a634ebd49aa 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -143,11 +143,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -451,10 +446,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -1014,152 +1006,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 1f1364f9df9..e9a53b95caf 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index dfc93c2cc98..901162bf09d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -216,10 +216,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.51.2

v26-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From 4e50176da1b03e57b37738d9beb03653ed03a1ad Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v26 2/4] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo() in heap_update().

ExecWhichIndexesRequireUpdates() replaces HeapDeterminesColumnsInfo()
when invoked from the table AM API via heapam_tuple_update(). The
test for equality remains datumIsEqual() as before.

This change necessitated some logic changes in execReplication() as it
performs updates now must provide the set of attributes that are both
changed and referenced by indexes.  Luckilly, this is available within
calls to slot_modify_data() where LogicalRepTupleData is processed and
has a record of updated attributes.  In this case rather than using
ExecWhichIndexesRequireUpdates() we can preseve what slot_modify_data()
identifies as the modified set and then intersect that with the set of
indexes on the relation and get the correct set of modified indexed
attributes required on heap_update().

This commit also extends the role index AMs play determining if they
require an update. A new optional index AM API, amcomparedatums(), is
added to allow index access methods to provide custom logic for
comparing datums. Hash and Gin indexes now implement this function. When
not implemented the executor will compare TupleTableSlot datum for
equality using datumIsEqual() as before.

Because heap_update() now requires the caller to provide the modified
indexed columns simple_heap_update() has become a tad more complex.  It
is only called from CatalogTupleUpdate() which either updates heap
tuples via their Form_XXX or by calling heap_modify_tuple().  In both
cases the caller does know the modified set of attributes, but sadly
those attributes are lost before being provided to simple_heap_update().
Due to that the "simple" path has to (for now) retain the
HeapDetermineColumnsInfo() logic in order for catalog updates to
potentially take the HOT path.
---
 src/backend/access/brin/brin.c                |   1 +
 src/backend/access/gin/ginutil.c              |  90 ++-
 src/backend/access/hash/hash.c                |  44 ++
 src/backend/access/heap/heapam.c              |  20 +-
 src/backend/access/heap/heapam_handler.c      |  76 +-
 src/backend/access/nbtree/nbtree.c            |   1 +
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/bootstrap/bootstrap.c             |   8 +
 src/backend/catalog/index.c                   |  57 ++
 src/backend/catalog/indexing.c                |  16 +-
 src/backend/catalog/toasting.c                |   4 +
 src/backend/executor/execIndexing.c           |  41 +-
 src/backend/executor/execMain.c               |   1 +
 src/backend/executor/execReplication.c        |   7 +
 src/backend/executor/nodeModifyTable.c        | 287 +++++++-
 src/backend/nodes/bitmapset.c                 |   4 +
 src/backend/nodes/makefuncs.c                 |   4 +
 src/backend/replication/logical/worker.c      |  70 +-
 src/backend/utils/cache/relcache.c            |  15 +
 src/include/access/amapi.h                    |  28 +
 src/include/access/gin.h                      |   3 +
 src/include/access/heapam.h                   |   6 +-
 src/include/access/nbtree.h                   |   4 +
 src/include/access/tableam.h                  |   8 +-
 src/include/catalog/index.h                   |   1 +
 src/include/executor/executor.h               |   9 +
 src/include/nodes/execnodes.h                 |  20 +
 src/include/utils/rel.h                       |   1 +
 src/include/utils/relcache.h                  |   1 +
 .../expected/insert-conflict-specconflict.out |  20 +
 .../regress/expected/heap_hot_updates.out     | 650 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   6 +
 src/test/regress/sql/heap_hot_updates.sql     | 513 ++++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 34 files changed, 1948 insertions(+), 74 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 26cb75058d1..e0388271614 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -290,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = NULL;
 	amroutine->ambeginscan = brinbeginscan;
 	amroutine->amrescan = brinrescan;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 605f80aad39..fd1d365c828 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = ginbuildphasename;
 	amroutine->amvalidate = ginvalidate;
+	amroutine->amcomparedatums = gincomparedatums;
 	amroutine->amadjustmembers = ginadjustmembers;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -729,3 +724,84 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/* Extract keys from both datums using existing GIN infrastructure */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys, definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		if (ginCompareEntries(&ginstate, attnum,
+							  old_keys[i], old_categories[i],
+							  new_keys[i], new_categories[i]) != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e388252afdc..ada646c6671 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -50,6 +50,10 @@ static void hashbuildCallback(Relation index,
 							  void *state);
 
 
+static bool hashcomparedatums(Relation index, int attnum,
+							  Datum old_datum, bool old_isnull,
+							  Datum new_datum, bool new_isnull);
+
 /*
  * Hash handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -98,6 +102,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = NULL;
 	amroutine->ambuildphasename = NULL;
 	amroutine->amvalidate = hashvalidate;
+	amroutine->amcomparedatums = hashcomparedatums;
 	amroutine->amadjustmembers = hashadjustmembers;
 	amroutine->ambeginscan = hashbeginscan;
 	amroutine->amrescan = hashrescan;
@@ -943,3 +948,42 @@ hashtranslatecmptype(CompareType cmptype, Oid opfamily)
 		return HTEqualStrategyNumber;
 	return InvalidStrategy;
 }
+
+/*
+ * hashcomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * Returns true if the hash values are identical (index doesn't need update).
+ */
+bool
+hashcomparedatums(Relation index, int attnum,
+				  Datum old_datum, bool old_isnull,
+				  Datum new_datum, bool new_isnull)
+{
+	uint32		old_hashkey;
+	uint32		new_hashkey;
+
+	/* If both are NULL, they're equal */
+	if (old_isnull && new_isnull)
+		return true;
+
+	/* If NULL status differs, they're not equal */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * _hash_datum2hashkey() is used because we know this can't be a cross
+	 * type comparison.
+	 */
+	old_hashkey = _hash_datum2hashkey(index, old_datum);
+	new_hashkey = _hash_datum2hashkey(index, new_datum);
+
+	/*
+	 * If hash keys are identical, the index entry would be the same. Return
+	 * true to indicate no index update needed.
+	 *
+	 * Note: Hash collisions are rare but possible. If hash(x) == hash(y) but
+	 * x != y, the hash index still treats them identically, so we correctly
+	 * return true.
+	 */
+	return (old_hashkey == new_hashkey);
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d5a1b844b5a..887aeee4bcd 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3278,12 +3278,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4352,8 +4352,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4482,7 +4483,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4515,7 +4516,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4541,6 +4541,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 8708af01f8c..6b12f49f2b5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					const Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -405,25 +407,66 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +480,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 6197b725fb1..e411be020b4 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -156,6 +156,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amproperty = btproperty;
 	amroutine->ambuildphasename = btbuildphasename;
 	amroutine->amvalidate = btvalidate;
+	amroutine->amcomparedatums = NULL;
 	amroutine->amadjustmembers = btadjustmembers;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 73ebc01a08f..4f8fed45c05 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -367,6 +367,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -377,7 +378,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								mix_attrs,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 4986b1ea7ed..43442a61f39 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -961,10 +961,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8dea58ad96b..ed57dde1fc8 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2414,6 +2416,61 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		indnatts = ii->ii_NumIndexAttrs;
+
+		/* Collect key attributes used by the index, key and including */
+		for (i = 0; i < indnatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		/*
+		 * Combine key, including, and expression, but not partial index
+		 * predicate attributes.
+		 */
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..a361c215490 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 874a8fc89ad..9d7cb4438d5 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 0b3a31f1703..1b17d8d135f 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
@@ -324,8 +328,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -349,13 +353,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -372,7 +376,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -393,8 +397,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -402,11 +413,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -613,7 +623,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 797d8b1ca1c..3d140556a1a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 860f79f9cc1..d5860fb2c3f 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 874b71e6608..52a74479502 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -53,12 +54,18 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/attnum.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,8 +75,11 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
 #include "utils/injection_point.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -176,6 +186,224 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   ResultRelInfo *resultRelInfo,
 										   bool canSetTag);
 
+/*
+ * ExecCheckIndexedAttrsForChanges
+ *
+ * Determine which indexes need updating by finding the set of modified indexed
+ * attributes.
+ *
+ * For which implement the amcomparedatums() index AM API we'll need to form
+ * index datum and compare each attribute to see if anything actually changed.
+ *
+ * The goal is for the executor to know, ahead of calling into the table AM to
+ * process the update and before calling into the index AM for inserting new
+ * index tuples, which attributes in the new TupleTableSlot, if any, truely
+ * necessitate a new index tuple.
+ *
+ * Returns a Bitmapset of attributes that intersects with indexes which require
+ * a new index tuple.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								EState *estate,
+								TupleTableSlot *old_tts,
+								TupleTableSlot *new_tts)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *mix_attrs = NULL;	/* modified indexed attributes */
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/* Find the indexes that reference this attribute */
+	for (int i = 0; i < relinfo->ri_NumIndices; i++)
+	{
+		Relation	index = relinfo->ri_IndexRelationDescs[i];
+		IndexAmRoutine *amroutine = index->rd_indam;
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		Bitmapset  *m_attrs = NULL; /* (possibly) modified indexed attrs */
+		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attrs */
+		Bitmapset  *u_attrs = NULL; /* unmodified indexed attrs */
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		supports_ios = (amroutine->amcanreturn != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		int			num_datums = supports_ios ?
+			indexInfo->ii_NumIndexAttrs : indexInfo->ii_NumIndexKeyAttrs;
+
+		/* If we've reviewed all the attributes on this index, move on */
+		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
+			continue;
+
+		/* Add partial index attributes */
+		if (is_partial)
+			p_attrs = bms_add_members(p_attrs, indexInfo->ii_PredicateAttrs);
+
+		/* Compare the index datums for equality */
+		for (int j = 0; j < num_datums; j++)
+		{
+			AttrNumber	rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+			int			rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
+			int			nth_expr = 0;
+			int16		typlen;
+			bool		typbyval;
+			Datum		old_value;
+			Datum		new_value;
+			bool		old_null;
+			bool		new_null;
+			bool		values_equal = false;
+
+			/* System attributes */
+			if (rel_attrnum < 0)
+			{
+				/* Extract system values from both slots for this attribute */
+				old_value = slot_getsysattr(old_tts, rel_attrnum, &old_null);
+				new_value = slot_getsysattr(new_tts, rel_attrnum, &new_null);
+
+				/* The only allowed system columns are OIDs, so do this */
+				values_equal = (DatumGetObjectId(old_value) == DatumGetObjectId(new_value));
+				goto equality_determined;
+			}
+
+			/*
+			 * This is an expression attribute, but in an effort to avoid the
+			 * expense of IndexFormDatum we're now faced with testing for
+			 * equality so we'll have to exec the expressions and test for
+			 * binary equality of the results.
+			 */
+			else if (rel_attrnum == 0)
+			{
+				TupleTableSlot *save_scantuple = econtext->ecxt_scantuple;
+				Oid			expr_type_oid;
+				Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+				ExprState  *state;
+
+				if (indexInfo->ii_ExpressionsState == NIL)
+				{
+					/* First time through, set up expression evaluation state */
+					indexInfo->ii_ExpressionsState =
+						ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+				}
+
+				state = (ExprState *) list_nth(indexInfo->ii_ExpressionsState, nth_expr);
+
+				econtext->ecxt_scantuple = old_tts;
+				old_value = ExecEvalExprSwitchContext(state,
+													  GetPerTupleExprContext(estate),
+													  &old_null);
+
+				econtext->ecxt_scantuple = new_tts;
+				new_value = ExecEvalExprSwitchContext(state,
+													  GetPerTupleExprContext(estate),
+													  &new_null);
+
+				econtext->ecxt_scantuple = save_scantuple;
+
+				/*
+				 * NOTE: test for NULL cases here to potentially avoid looking
+				 * up the type information.  It's a tad redundant, but worth
+				 * it.
+				 */
+
+				/* A change to/from NULL, so not equal */
+				if (old_null != new_null)
+				{
+					values_equal = false;
+					goto equality_determined;
+				}
+
+				/* Both NULL, no change record as unmodified */
+				if (old_null)
+				{
+					values_equal = true;
+					goto equality_determined;
+				}
+
+				/* Get type OID from the expression */
+				expr_type_oid = exprType((Node *) expr);
+
+				/* Get type information from the OID */
+				get_typlenbyval(expr_type_oid, &typlen, &typbyval);
+			}
+			/* Not a system or expression attribute */
+			else
+			{
+				CompactAttribute *att = TupleDescCompactAttr(tupdesc, rel_attrnum - 1);
+
+				/* Extract values from both slots for this attribute */
+				old_value = slot_getattr(old_tts, rel_attrnum, &old_null);
+				new_value = slot_getattr(new_tts, rel_attrnum, &new_null);
+
+				typlen = att->attlen;
+				typbyval = att->attbyval;
+			}
+
+			/* A change to/from NULL, so not equal */
+			if (old_null != new_null)
+			{
+				values_equal = false;
+				goto equality_determined;
+			}
+
+			/* Both NULL, no change record as unmodified */
+			if (old_null)
+			{
+				values_equal = true;
+				goto equality_determined;
+			}
+
+			if (has_am_compare)
+			{
+				/*
+				 * NOTE: For AM comparison, pass the 1-based index attribute
+				 * number. The AM's compare function expects the same
+				 * numbering as used internally by the AM.
+				 */
+				values_equal = amroutine->amcomparedatums(index, j + 1,
+														  old_value, old_null,
+														  new_value, new_null);
+			}
+			else
+			{
+				values_equal = datumIsEqual(old_value, new_value, typbyval, typlen);
+			}
+
+	equality_determined:;
+			if (!values_equal)
+				if (rel_attrnum == 0)
+				{
+					Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+
+					pull_varattnos((Node *) expr, relinfo->ri_RangeTableIndex, &m_attrs);
+				}
+				else
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+			else
+				u_attrs = bms_add_member(u_attrs, rel_attridx);
+
+			if (rel_attrnum == 0)
+				nth_expr++;
+		}
+
+		/*
+		 * Here we know all the attributes that might be modified and all
+		 * those we know haven't been across all indexes.  Take the difference
+		 * and add it to the modified indexed attributes set.
+		 */
+		m_attrs = bms_del_members(m_attrs, u_attrs);
+		p_attrs = bms_del_members(p_attrs, u_attrs);
+		mix_attrs = bms_add_members(mix_attrs, m_attrs);
+		mix_attrs = bms_add_members(mix_attrs, p_attrs);
+
+		bms_free(m_attrs);
+		bms_free(u_attrs);
+		bms_free(p_attrs);
+	}
+
+	return mix_attrs;
+}
 
 /*
  * Verify that the tuples to be produced by INSERT match the
@@ -2168,14 +2396,17 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* The set of modified indexed attributes that trigger new index entries */
+	Bitmapset  *mix_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2292,9 +2523,38 @@ lreplace:
 		ExecConstraints(resultRelInfo, slot, estate);
 
 	/*
-	 * replace the heap tuple
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	/*
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * Next up we need to find out the set of indexed attributes that have
+	 * changed in value and should trigger a new index tuple.  We could start
+	 * with the set of updated columns via ExecGetUpdatedCols(), but if we do
+	 * we will overlook attributes directly modified by heap_modify_tuple()
+	 * which are not known to ExecGetUpdatedCols().
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot);
+
+	/*
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2306,8 +2566,12 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								mix_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = mix_attrs;
+
 	return result;
 }
 
@@ -2325,7 +2589,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
@@ -2524,8 +2788,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3222,8 +3487,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3248,6 +3513,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4354,7 +4620,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4530,6 +4796,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index 7b1e9d94103..c522971a37c 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41a..1f1364f9df9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -857,10 +857,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index fc64476a9ef..3fca4cd292b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -275,7 +275,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -285,12 +284,14 @@
 #include "storage/procarray.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
+#include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1111,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1199,27 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+
+				if (!datumIsEqual(srcslot->tts_values[i],
+								  slot->tts_values[i],
+								  att->attbyval,
+								  att->attlen))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1227,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2945,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2962,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2987,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3499,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3569,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3595,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2d0cb7bcfd4..415ad1019b2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2482,6 +2482,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5283,6 +5284,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5307,6 +5309,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5329,6 +5332,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5373,6 +5378,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5505,10 +5511,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5521,6 +5531,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5535,6 +5547,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5551,6 +5564,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 63dd41c1f21..9bdf73eda59 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 13ea91922ef..2f265f4816c 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9398216d5d9..072749ef417 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -369,7 +369,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -404,8 +404,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 7a3efd209bc..2a28a791df0 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1179,6 +1179,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 2fa790b6bf5..d94dfc9b41d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1512,12 +1513,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2020,6 +2021,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index dda95e54903..8d364f8b30f 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 7cd6a49309f..d0461a0baec 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
@@ -800,5 +805,9 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+												  EState *estate,
+												  TupleTableSlot *old_tts,
+												  TupleTableSlot *new_tts);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3968429f991..dfc93c2cc98 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -499,6 +513,12 @@ typedef struct ResultRelInfo
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
 
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
+	Bitmapset  *ri_ChangedIndexedCols;
+
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
 	/* Slot to hold that tuple */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 80286076a11..b23a7306e69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 3561c6bef0b..d3fbb8b093a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
index e34a821c403..54b3981918c 100644
--- a/src/test/isolation/expected/insert-conflict-specconflict.out
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -80,6 +80,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
@@ -172,6 +176,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
@@ -369,6 +377,10 @@ key|data
 step s1_commit: COMMIT;
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 step s2_upsert: <... completed>
 step controller_show: SELECT * FROM upserttest;
 key|data       
@@ -530,6 +542,14 @@ isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
 step s2_commit: COMMIT;
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
 step s1_upsert: <... completed>
 step s1_noop: 
 step controller_show: SELECT * FROM upserttest;
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..14276e3cbca
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,650 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+INSERT INTO t VALUES (1, 50, 'below range');
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     150
+(1 row)
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     160
+(1 row)
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           4 |                100.00 | t
+(1 row)
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+SELECT id, value, description FROM t;
+ id | value |  description  
+----+-------+---------------
+  1 |    50 | updated again
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_brin     |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT (BRIN allows it for single row)
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_hash     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (HASH blocks it)
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: 1 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT (BRIN permits single-row updates)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+-- Expected: 2 HOT (HASH blocks it)
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+-- Expected: 3 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 905f9bca959..3f0eab3131b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_merge partition_split partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: heap_hot_updates
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..e047bcddf5c
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,513 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+
+INSERT INTO t VALUES (1, 50, 'below range');
+
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+SELECT id, value, description FROM t;
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+-- Expected: 1 HOT (BRIN allows it for single row)
+
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+-- Expected: 0 HOT (HASH blocks it)
+
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+
+
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (BRIN permits single-row updates)
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT
+
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (HASH blocks it)
+
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+-- Expected: 3 HOT
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t CASCADE;
+
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3451538565e..2c457af2257 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -398,6 +398,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.51.2

hot_test.sqlapplication/octet-streamDownload
setup.sqlapplication/octet-streamDownload
cf-5556-flame-a.svgapplication/octet-streamDownload
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" width="1200" height="838" onload="init(evt)" viewBox="0 0 1200 838" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples. -->
<!-- NOTES:  -->
<defs>
	<linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
		<stop stop-color="#eeeeee" offset="5%" />
		<stop stop-color="#eeeeb0" offset="95%" />
	</linearGradient>
</defs>
<style type="text/css">
	text { font-family:Verdana; font-size:12px; fill:rgb(0,0,0); }
	#search, #ignorecase { opacity:0.1; cursor:pointer; }
	#search:hover, #search.show, #ignorecase:hover, #ignorecase.show { opacity:1; }
	#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }
	#title { text-anchor:middle; font-size:17px}
	#unzoom { cursor:pointer; }
	#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
	.hide { display:none; }
	.parent { opacity:0.5; }
</style>
<script type="text/ecmascript">
<![CDATA[
	"use strict";
	var details, searchbtn, unzoombtn, matchedtxt, svg, searching, currentSearchTerm, ignorecase, ignorecaseBtn;
	function init(evt) {
		details = document.getElementById("details").firstChild;
		searchbtn = document.getElementById("search");
		ignorecaseBtn = document.getElementById("ignorecase");
		unzoombtn = document.getElementById("unzoom");
		matchedtxt = document.getElementById("matched");
		svg = document.getElementsByTagName("svg")[0];
		searching = 0;
		currentSearchTerm = null;

		// use GET parameters to restore a flamegraphs state.
		var params = get_params();
		if (params.x && params.y)
			zoom(find_group(document.querySelector('[x="' + params.x + '"][y="' + params.y + '"]')));
                if (params.s) search(params.s);
	}

	// event listeners
	window.addEventListener("click", function(e) {
		var target = find_group(e.target);
		if (target) {
			if (target.nodeName == "a") {
				if (e.ctrlKey === false) return;
				e.preventDefault();
			}
			if (target.classList.contains("parent")) unzoom(true);
			zoom(target);
			if (!document.querySelector('.parent')) {
				// we have basically done a clearzoom so clear the url
				var params = get_params();
				if (params.x) delete params.x;
				if (params.y) delete params.y;
				history.replaceState(null, null, parse_params(params));
				unzoombtn.classList.add("hide");
				return;
			}

			// set parameters for zoom state
			var el = target.querySelector("rect");
			if (el && el.attributes && el.attributes.y && el.attributes._orig_x) {
				var params = get_params()
				params.x = el.attributes._orig_x.value;
				params.y = el.attributes.y.value;
				history.replaceState(null, null, parse_params(params));
			}
		}
		else if (e.target.id == "unzoom") clearzoom();
		else if (e.target.id == "search") search_prompt();
		else if (e.target.id == "ignorecase") toggle_ignorecase();
	}, false)

	// mouse-over for info
	// show
	window.addEventListener("mouseover", function(e) {
		var target = find_group(e.target);
		if (target) details.nodeValue = "Function: " + g_to_text(target);
	}, false)

	// clear
	window.addEventListener("mouseout", function(e) {
		var target = find_group(e.target);
		if (target) details.nodeValue = ' ';
	}, false)

	// ctrl-F for search
	// ctrl-I to toggle case-sensitive search
	window.addEventListener("keydown",function (e) {
		if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {
			e.preventDefault();
			search_prompt();
		}
		else if (e.ctrlKey && e.keyCode === 73) {
			e.preventDefault();
			toggle_ignorecase();
		}
	}, false)

	// functions
	function get_params() {
		var params = {};
		var paramsarr = window.location.search.substr(1).split('&');
		for (var i = 0; i < paramsarr.length; ++i) {
			var tmp = paramsarr[i].split("=");
			if (!tmp[0] || !tmp[1]) continue;
			params[tmp[0]]  = decodeURIComponent(tmp[1]);
		}
		return params;
	}
	function parse_params(params) {
		var uri = "?";
		for (var key in params) {
			uri += key + '=' + encodeURIComponent(params[key]) + '&';
		}
		if (uri.slice(-1) == "&")
			uri = uri.substring(0, uri.length - 1);
		if (uri == '?')
			uri = window.location.href.split('?')[0];
		return uri;
	}
	function find_child(node, selector) {
		var children = node.querySelectorAll(selector);
		if (children.length) return children[0];
	}
	function find_group(node) {
		var parent = node.parentElement;
		if (!parent) return;
		if (parent.id == "frames") return node;
		return find_group(parent);
	}
	function orig_save(e, attr, val) {
		if (e.attributes["_orig_" + attr] != undefined) return;
		if (e.attributes[attr] == undefined) return;
		if (val == undefined) val = e.attributes[attr].value;
		e.setAttribute("_orig_" + attr, val);
	}
	function orig_load(e, attr) {
		if (e.attributes["_orig_"+attr] == undefined) return;
		e.attributes[attr].value = e.attributes["_orig_" + attr].value;
		e.removeAttribute("_orig_"+attr);
	}
	function g_to_text(e) {
		var text = find_child(e, "title").firstChild.nodeValue;
		return (text)
	}
	function g_to_func(e) {
		var func = g_to_text(e);
		// if there's any manipulation we want to do to the function
		// name before it's searched, do it here before returning.
		return (func);
	}
	function update_text(e) {
		var r = find_child(e, "rect");
		var t = find_child(e, "text");
		var w = parseFloat(r.attributes.width.value) -3;
		var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
		t.attributes.x.value = parseFloat(r.attributes.x.value) + 3;

		// Smaller than this size won't fit anything
		if (w < 2 * 12 * 0.59) {
			t.textContent = "";
			return;
		}

		t.textContent = txt;
		var sl = t.getSubStringLength(0, txt.length);
		// check if only whitespace or if we can fit the entire string into width w
		if (/^ *$/.test(txt) || sl < w)
			return;

		// this isn't perfect, but gives a good starting point
		// and avoids calling getSubStringLength too often
		var start = Math.floor((w/sl) * txt.length);
		for (var x = start; x > 0; x = x-2) {
			if (t.getSubStringLength(0, x + 2) <= w) {
				t.textContent = txt.substring(0, x) + "..";
				return;
			}
		}
		t.textContent = "";
	}

	// zoom
	function zoom_reset(e) {
		if (e.attributes != undefined) {
			orig_load(e, "x");
			orig_load(e, "width");
		}
		if (e.childNodes == undefined) return;
		for (var i = 0, c = e.childNodes; i < c.length; i++) {
			zoom_reset(c[i]);
		}
	}
	function zoom_child(e, x, ratio) {
		if (e.attributes != undefined) {
			if (e.attributes.x != undefined) {
				orig_save(e, "x");
				e.attributes.x.value = (parseFloat(e.attributes.x.value) - x - 10) * ratio + 10;
				if (e.tagName == "text")
					e.attributes.x.value = find_child(e.parentNode, "rect[x]").attributes.x.value + 3;
			}
			if (e.attributes.width != undefined) {
				orig_save(e, "width");
				e.attributes.width.value = parseFloat(e.attributes.width.value) * ratio;
			}
		}

		if (e.childNodes == undefined) return;
		for (var i = 0, c = e.childNodes; i < c.length; i++) {
			zoom_child(c[i], x - 10, ratio);
		}
	}
	function zoom_parent(e) {
		if (e.attributes) {
			if (e.attributes.x != undefined) {
				orig_save(e, "x");
				e.attributes.x.value = 10;
			}
			if (e.attributes.width != undefined) {
				orig_save(e, "width");
				e.attributes.width.value = parseInt(svg.width.baseVal.value) - (10 * 2);
			}
		}
		if (e.childNodes == undefined) return;
		for (var i = 0, c = e.childNodes; i < c.length; i++) {
			zoom_parent(c[i]);
		}
	}
	function zoom(node) {
		var attr = find_child(node, "rect").attributes;
		var width = parseFloat(attr.width.value);
		var xmin = parseFloat(attr.x.value);
		var xmax = parseFloat(xmin + width);
		var ymin = parseFloat(attr.y.value);
		var ratio = (svg.width.baseVal.value - 2 * 10) / width;

		// XXX: Workaround for JavaScript float issues (fix me)
		var fudge = 0.0001;

		unzoombtn.classList.remove("hide");

		var el = document.getElementById("frames").children;
		for (var i = 0; i < el.length; i++) {
			var e = el[i];
			var a = find_child(e, "rect").attributes;
			var ex = parseFloat(a.x.value);
			var ew = parseFloat(a.width.value);
			var upstack;
			// Is it an ancestor
			if (0 == 0) {
				upstack = parseFloat(a.y.value) > ymin;
			} else {
				upstack = parseFloat(a.y.value) < ymin;
			}
			if (upstack) {
				// Direct ancestor
				if (ex <= xmin && (ex+ew+fudge) >= xmax) {
					e.classList.add("parent");
					zoom_parent(e);
					update_text(e);
				}
				// not in current path
				else
					e.classList.add("hide");
			}
			// Children maybe
			else {
				// no common path
				if (ex < xmin || ex + fudge >= xmax) {
					e.classList.add("hide");
				}
				else {
					zoom_child(e, xmin, ratio);
					update_text(e);
				}
			}
		}
		search();
	}
	function unzoom(dont_update_text) {
		unzoombtn.classList.add("hide");
		var el = document.getElementById("frames").children;
		for(var i = 0; i < el.length; i++) {
			el[i].classList.remove("parent");
			el[i].classList.remove("hide");
			zoom_reset(el[i]);
			if(!dont_update_text) update_text(el[i]);
		}
		search();
	}
	function clearzoom() {
		unzoom();

		// remove zoom state
		var params = get_params();
		if (params.x) delete params.x;
		if (params.y) delete params.y;
		history.replaceState(null, null, parse_params(params));
	}

	// search
	function toggle_ignorecase() {
		ignorecase = !ignorecase;
		if (ignorecase) {
			ignorecaseBtn.classList.add("show");
		} else {
			ignorecaseBtn.classList.remove("show");
		}
		reset_search();
		search();
	}
	function reset_search() {
		var el = document.querySelectorAll("#frames rect");
		for (var i = 0; i < el.length; i++) {
			orig_load(el[i], "fill")
		}
		var params = get_params();
		delete params.s;
		history.replaceState(null, null, parse_params(params));
	}
	function search_prompt() {
		if (!searching) {
			var term = prompt("Enter a search term (regexp " +
			    "allowed, eg: ^ext4_)"
			    + (ignorecase ? ", ignoring case" : "")
			    + "\nPress Ctrl-i to toggle case sensitivity", "");
			if (term != null) search(term);
		} else {
			reset_search();
			searching = 0;
			currentSearchTerm = null;
			searchbtn.classList.remove("show");
			searchbtn.firstChild.nodeValue = "Search"
			matchedtxt.classList.add("hide");
			matchedtxt.firstChild.nodeValue = ""
		}
	}
	function search(term) {
		if (term) currentSearchTerm = term;

		var re = new RegExp(currentSearchTerm, ignorecase ? 'i' : '');
		var el = document.getElementById("frames").children;
		var matches = new Object();
		var maxwidth = 0;
		for (var i = 0; i < el.length; i++) {
			var e = el[i];
			var func = g_to_func(e);
			var rect = find_child(e, "rect");
			if (func == null || rect == null)
				continue;

			// Save max width. Only works as we have a root frame
			var w = parseFloat(rect.attributes.width.value);
			if (w > maxwidth)
				maxwidth = w;

			if (func.match(re)) {
				// highlight
				var x = parseFloat(rect.attributes.x.value);
				orig_save(rect, "fill");
				rect.attributes.fill.value = "rgb(230,0,230)";

				// remember matches
				if (matches[x] == undefined) {
					matches[x] = w;
				} else {
					if (w > matches[x]) {
						// overwrite with parent
						matches[x] = w;
					}
				}
				searching = 1;
			}
		}
		if (!searching)
			return;
		var params = get_params();
		params.s = currentSearchTerm;
		history.replaceState(null, null, parse_params(params));

		searchbtn.classList.add("show");
		searchbtn.firstChild.nodeValue = "Reset Search";

		// calculate percent matched, excluding vertical overlap
		var count = 0;
		var lastx = -1;
		var lastw = 0;
		var keys = Array();
		for (k in matches) {
			if (matches.hasOwnProperty(k))
				keys.push(k);
		}
		// sort the matched frames by their x location
		// ascending, then width descending
		keys.sort(function(a, b){
			return a - b;
		});
		// Step through frames saving only the biggest bottom-up frames
		// thanks to the sort order. This relies on the tree property
		// where children are always smaller than their parents.
		var fudge = 0.0001;	// JavaScript floating point
		for (var k in keys) {
			var x = parseFloat(keys[k]);
			var w = matches[keys[k]];
			if (x >= lastx + lastw - fudge) {
				count += w;
				lastx = x;
				lastw = w;
			}
		}
		// display matched percent
		matchedtxt.classList.remove("hide");
		var pct = 100 * count / maxwidth;
		if (pct != 100) pct = pct.toFixed(1)
		matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
	}
]]>
</script>
<rect x="0.0" y="0" width="1200.0" height="838.0" fill="url(#background)"  />
<text id="title" x="600.00" y="24" >Flame Graph</text>
<text id="details" x="10.00" y="821" > </text>
<text id="unzoom" x="10.00" y="24" class="hide">Reset Zoom</text>
<text id="search" x="1090.00" y="24" >Search</text>
<text id="ignorecase" x="1174.00" y="24" >ic</text>
<text id="matched" x="1090.00" y="821" > </text>
<g id="frames">
<g >
<title>SearchCatCacheInternal (3,806,068,638 samples, 0.04%)</title><rect x="104.2" y="421" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="107.21" y="431.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,213,115,126 samples, 0.01%)</title><rect x="370.9" y="277" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="373.89" y="287.5" ></text>
</g>
<g >
<title>grouping_planner (838,410,516 samples, 0.01%)</title><rect x="128.2" y="517" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="131.20" y="527.5" ></text>
</g>
<g >
<title>heap_compute_data_size (6,884,339,206 samples, 0.07%)</title><rect x="396.9" y="357" width="0.8" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="399.89" y="367.5" ></text>
</g>
<g >
<title>_bt_saveitem (885,664,507 samples, 0.01%)</title><rect x="131.4" y="757" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="134.37" y="767.5" ></text>
</g>
<g >
<title>PinBuffer (5,085,671,181 samples, 0.05%)</title><rect x="101.7" y="181" width="0.7" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="104.74" y="191.5" ></text>
</g>
<g >
<title>SearchCatCache1 (8,003,711,250 samples, 0.08%)</title><rect x="430.9" y="325" width="1.0" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="433.93" y="335.5" ></text>
</g>
<g >
<title>wake_up_q (994,779,827 samples, 0.01%)</title><rect x="478.2" y="293" width="0.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="481.19" y="303.5" ></text>
</g>
<g >
<title>palloc (1,496,134,934 samples, 0.02%)</title><rect x="975.6" y="389" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="978.60" y="399.5" ></text>
</g>
<g >
<title>__libc_start_call_main (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="757" width="2.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="126.99" y="767.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (3,997,299,874 samples, 0.04%)</title><rect x="349.8" y="357" width="0.5" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="352.81" y="367.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="309" width="2.4" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="103.12" y="319.5" ></text>
</g>
<g >
<title>PortalRunMulti (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="629" width="2.6" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="87.32" y="639.5" ></text>
</g>
<g >
<title>malloc (7,474,412,386 samples, 0.08%)</title><rect x="1049.6" y="437" width="1.0" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="1052.64" y="447.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,616,376,637 samples, 0.02%)</title><rect x="37.7" y="757" width="0.2" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="40.70" y="767.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (903,272,914 samples, 0.01%)</title><rect x="948.6" y="357" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="951.57" y="367.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,267,014,538 samples, 0.02%)</title><rect x="882.1" y="405" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="885.11" y="415.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (1,011,222,487 samples, 0.01%)</title><rect x="1142.0" y="757" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1144.97" y="767.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="629" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.62" y="639.5" ></text>
</g>
<g >
<title>__pick_next_task (1,481,138,869 samples, 0.02%)</title><rect x="209.1" y="405" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="212.15" y="415.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (15,913,541,371 samples, 0.17%)</title><rect x="1037.5" y="373" width="2.0" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1040.54" y="383.5" ></text>
</g>
<g >
<title>AtStart_Memory (1,187,073,689 samples, 0.01%)</title><rect x="1074.4" y="533" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1077.36" y="543.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,124,510,013 samples, 0.04%)</title><rect x="1038.2" y="325" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1041.16" y="335.5" ></text>
</g>
<g >
<title>finalize_primnode (9,724,868,691 samples, 0.10%)</title><rect x="881.8" y="437" width="1.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="884.78" y="447.5" ></text>
</g>
<g >
<title>hash_bytes (2,809,450,265 samples, 0.03%)</title><rect x="453.4" y="325" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="456.37" y="335.5" ></text>
</g>
<g >
<title>LWLockWakeup (3,159,547,102 samples, 0.03%)</title><rect x="375.8" y="309" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="378.80" y="319.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (22,836,665,944 samples, 0.24%)</title><rect x="826.4" y="325" width="2.8" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="829.38" y="335.5" ></text>
</g>
<g >
<title>GetUserIdAndSecContext (846,973,469 samples, 0.01%)</title><rect x="1076.3" y="533" width="0.1" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1079.31" y="543.5" ></text>
</g>
<g >
<title>list_make1_impl (1,890,882,862 samples, 0.02%)</title><rect x="992.7" y="341" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="995.72" y="351.5" ></text>
</g>
<g >
<title>palloc (1,311,115,446 samples, 0.01%)</title><rect x="980.7" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="983.68" y="383.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,453,393,646 samples, 0.07%)</title><rect x="1038.7" y="341" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1041.68" y="351.5" ></text>
</g>
<g >
<title>MarkBufferDirtyHint (4,557,669,790 samples, 0.05%)</title><rect x="308.0" y="197" width="0.5" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="310.97" y="207.5" ></text>
</g>
<g >
<title>lappend (2,560,826,136 samples, 0.03%)</title><rect x="982.5" y="437" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="985.54" y="447.5" ></text>
</g>
<g >
<title>ExecInitFunc (9,581,182,210 samples, 0.10%)</title><rect x="439.0" y="389" width="1.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="441.99" y="399.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,109,429,033 samples, 0.02%)</title><rect x="868.5" y="437" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="871.48" y="447.5" ></text>
</g>
<g >
<title>transformStmt (432,539,738,388 samples, 4.55%)</title><rect x="804.8" y="517" width="53.7" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="807.76" y="527.5" >trans..</text>
</g>
<g >
<title>GetPrivateRefCount (934,929,096 samples, 0.01%)</title><rect x="393.1" y="309" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="396.13" y="319.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,714,880,420 samples, 0.04%)</title><rect x="917.8" y="405" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="920.79" y="415.5" ></text>
</g>
<g >
<title>core_yy_load_buffer_state (1,169,987,930 samples, 0.01%)</title><rect x="872.0" y="501" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="874.97" y="511.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,149,125,232 samples, 0.01%)</title><rect x="309.3" y="101" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="312.34" y="111.5" ></text>
</g>
<g >
<title>hash_bytes (2,472,859,454 samples, 0.03%)</title><rect x="451.3" y="357" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="454.31" y="367.5" ></text>
</g>
<g >
<title>GetSysCacheOid (38,030,391,419 samples, 0.40%)</title><rect x="825.4" y="373" width="4.8" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="828.44" y="383.5" ></text>
</g>
<g >
<title>palloc0 (2,115,608,682 samples, 0.02%)</title><rect x="857.3" y="293" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="860.30" y="303.5" ></text>
</g>
<g >
<title>IsBinaryTidClause (1,364,403,342 samples, 0.01%)</title><rect x="1025.6" y="357" width="0.2" height="15.0" fill="rgb(214,41,9)" rx="2" ry="2" />
<text  x="1028.65" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (3,770,139,198 samples, 0.04%)</title><rect x="258.1" y="421" width="0.5" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="261.12" y="431.5" ></text>
</g>
<g >
<title>exprCollation (1,960,548,354 samples, 0.02%)</title><rect x="445.3" y="373" width="0.3" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="448.32" y="383.5" ></text>
</g>
<g >
<title>palloc (1,093,405,604 samples, 0.01%)</title><rect x="440.9" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="443.87" y="383.5" ></text>
</g>
<g >
<title>makeVar (4,022,865,513 samples, 0.04%)</title><rect x="842.0" y="325" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="845.03" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,034,948,399 samples, 0.01%)</title><rect x="213.2" y="533" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="216.21" y="543.5" ></text>
</g>
<g >
<title>rseq_ip_fixup (4,858,781,314 samples, 0.05%)</title><rect x="176.3" y="405" width="0.6" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="179.32" y="415.5" ></text>
</g>
<g >
<title>fmgr_info_copy (863,023,406 samples, 0.01%)</title><rect x="316.1" y="245" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="319.12" y="255.5" ></text>
</g>
<g >
<title>SearchCatCache1 (5,237,345,942 samples, 0.06%)</title><rect x="848.6" y="373" width="0.7" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="851.60" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (1,581,189,397 samples, 0.02%)</title><rect x="229.3" y="533" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="232.31" y="543.5" ></text>
</g>
<g >
<title>hash_initial_lookup (879,975,667 samples, 0.01%)</title><rect x="924.1" y="373" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="927.13" y="383.5" ></text>
</g>
<g >
<title>find_duplicate_ors (848,567,698 samples, 0.01%)</title><rect x="1054.2" y="453" width="0.1" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="1057.18" y="463.5" ></text>
</g>
<g >
<title>ReindexIsProcessingIndex (812,291,620 samples, 0.01%)</title><rect x="939.9" y="389" width="0.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="942.89" y="399.5" ></text>
</g>
<g >
<title>SearchCatCacheList (7,395,981,178 samples, 0.08%)</title><rect x="961.8" y="357" width="0.9" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="964.82" y="367.5" ></text>
</g>
<g >
<title>bms_join (1,087,045,349 samples, 0.01%)</title><rect x="1069.3" y="469" width="0.2" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="1072.32" y="479.5" ></text>
</g>
<g >
<title>finalize_primnode (13,796,062,484 samples, 0.15%)</title><rect x="881.3" y="469" width="1.7" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="884.29" y="479.5" ></text>
</g>
<g >
<title>ExecShutdownNode (7,314,935,841 samples, 0.08%)</title><rect x="410.2" y="485" width="0.9" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="413.20" y="495.5" ></text>
</g>
<g >
<title>bms_is_valid_set (881,719,888 samples, 0.01%)</title><rect x="117.8" y="741" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="120.77" y="751.5" ></text>
</g>
<g >
<title>populate_compact_attribute (2,693,418,099 samples, 0.03%)</title><rect x="444.8" y="357" width="0.4" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="447.84" y="367.5" ></text>
</g>
<g >
<title>update_process_times (4,144,052,450 samples, 0.04%)</title><rect x="758.0" y="437" width="0.5" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="761.03" y="447.5" ></text>
</g>
<g >
<title>index_getnext_tid (232,486,007,302 samples, 2.45%)</title><rect x="313.5" y="309" width="28.8" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="316.47" y="319.5" >in..</text>
</g>
<g >
<title>do_fsync (2,132,486,286 samples, 0.02%)</title><rect x="507.8" y="373" width="0.3" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="510.80" y="383.5" ></text>
</g>
<g >
<title>makeColumnRef (15,346,079,527 samples, 0.16%)</title><rect x="1114.6" y="741" width="1.9" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="1117.62" y="751.5" ></text>
</g>
<g >
<title>newNode (5,828,656,722 samples, 0.06%)</title><rect x="952.2" y="405" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="955.21" y="415.5" ></text>
</g>
<g >
<title>pg_fdatasync (8,543,193,636 samples, 0.09%)</title><rect x="507.3" y="453" width="1.0" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="510.26" y="463.5" ></text>
</g>
<g >
<title>log@@GLIBC_2.29 (2,859,322,383 samples, 0.03%)</title><rect x="1014.6" y="309" width="0.4" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="1017.61" y="319.5" ></text>
</g>
<g >
<title>PageValidateSpecialPointer (818,529,498 samples, 0.01%)</title><rect x="335.7" y="197" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="338.67" y="207.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (1,612,904,934 samples, 0.02%)</title><rect x="250.6" y="405" width="0.2" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="253.61" y="415.5" ></text>
</g>
<g >
<title>transform_MERGE_to_join (963,443,170 samples, 0.01%)</title><rect x="1071.0" y="501" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1073.95" y="511.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,163,465,944 samples, 0.01%)</title><rect x="475.6" y="453" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="478.57" y="463.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (972,290,973 samples, 0.01%)</title><rect x="443.0" y="309" width="0.2" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="446.04" y="319.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,136,980,425 samples, 0.02%)</title><rect x="345.3" y="373" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="348.30" y="383.5" ></text>
</g>
<g >
<title>hash_initial_lookup (961,352,424 samples, 0.01%)</title><rect x="452.4" y="309" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="455.41" y="319.5" ></text>
</g>
<g >
<title>CheckCmdReplicaIdentity (6,594,901,168 samples, 0.07%)</title><rect x="419.5" y="437" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="422.49" y="447.5" ></text>
</g>
<g >
<title>pg_plan_query (2,204,184,953 samples, 0.02%)</title><rect x="128.2" y="581" width="0.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="131.20" y="591.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,031,911,705 samples, 0.01%)</title><rect x="355.3" y="261" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="358.29" y="271.5" ></text>
</g>
<g >
<title>hdefault (11,383,818,276 samples, 0.12%)</title><rect x="1061.3" y="453" width="1.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1064.32" y="463.5" ></text>
</g>
<g >
<title>dlist_is_empty (2,991,532,673 samples, 0.03%)</title><rect x="527.4" y="453" width="0.3" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="530.35" y="463.5" ></text>
</g>
<g >
<title>preprocess_rowmarks (12,362,549,651 samples, 0.13%)</title><rect x="1068.3" y="501" width="1.5" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1071.26" y="511.5" ></text>
</g>
<g >
<title>PostgresMain (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="677" width="2.8" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="87.32" y="687.5" ></text>
</g>
<g >
<title>put_prev_task_fair (1,808,571,780 samples, 0.02%)</title><rect x="163.6" y="325" width="0.2" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="166.56" y="335.5" ></text>
</g>
<g >
<title>do_syscall_64 (4,097,558,273 samples, 0.04%)</title><rect x="507.7" y="405" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="510.68" y="415.5" ></text>
</g>
<g >
<title>pg_any_to_server (10,762,709,598 samples, 0.11%)</title><rect x="1080.5" y="565" width="1.3" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1083.48" y="575.5" ></text>
</g>
<g >
<title>unix_poll (3,952,576,047 samples, 0.04%)</title><rect x="160.2" y="341" width="0.4" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="163.16" y="351.5" ></text>
</g>
<g >
<title>__send (146,582,105,367 samples, 1.54%)</title><rect x="191.6" y="501" width="18.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="194.59" y="511.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberBuffer (947,681,380 samples, 0.01%)</title><rect x="298.1" y="213" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="301.05" y="223.5" ></text>
</g>
<g >
<title>lappend (2,367,391,003 samples, 0.02%)</title><rect x="835.2" y="453" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="838.22" y="463.5" ></text>
</g>
<g >
<title>heapam_index_fetch_end (2,526,125,479 samples, 0.03%)</title><rect x="248.3" y="389" width="0.3" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="251.30" y="399.5" ></text>
</g>
<g >
<title>AllocSetFree (1,104,113,535 samples, 0.01%)</title><rect x="235.2" y="517" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="238.16" y="527.5" ></text>
</g>
<g >
<title>newNode (3,020,900,073 samples, 0.03%)</title><rect x="423.1" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="426.05" y="383.5" ></text>
</g>
<g >
<title>_raw_spin_lock (11,097,733,905 samples, 0.12%)</title><rect x="492.8" y="261" width="1.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="495.77" y="271.5" ></text>
</g>
<g >
<title>deconstruct_jointree (177,303,814,897 samples, 1.87%)</title><rect x="956.2" y="469" width="22.0" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="959.22" y="479.5" >d..</text>
</g>
<g >
<title>LWLockReleaseInternal (1,981,975,984 samples, 0.02%)</title><rect x="522.9" y="437" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="525.89" y="447.5" ></text>
</g>
<g >
<title>AllocSetFree (837,451,335 samples, 0.01%)</title><rect x="348.9" y="357" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="351.90" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,689,342,397 samples, 0.02%)</title><rect x="388.6" y="245" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="391.60" y="255.5" ></text>
</g>
<g >
<title>makeTargetEntry (3,110,619,701 samples, 0.03%)</title><rect x="934.1" y="389" width="0.3" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="937.05" y="399.5" ></text>
</g>
<g >
<title>SearchCatCache3 (7,648,843,499 samples, 0.08%)</title><rect x="1032.2" y="197" width="1.0" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1035.21" y="207.5" ></text>
</g>
<g >
<title>palloc0 (4,083,177,387 samples, 0.04%)</title><rect x="803.8" y="533" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="806.84" y="543.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,231,634,944 samples, 0.01%)</title><rect x="943.0" y="357" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="946.03" y="367.5" ></text>
</g>
<g >
<title>makeFromExpr (2,680,761,095 samples, 0.03%)</title><rect x="812.1" y="485" width="0.3" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="815.11" y="495.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,215,949,273 samples, 0.01%)</title><rect x="369.8" y="245" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="372.76" y="255.5" ></text>
</g>
<g >
<title>get_timeout_active (870,268,723 samples, 0.01%)</title><rect x="1142.1" y="757" width="0.1" height="15.0" fill="rgb(254,225,54)" rx="2" ry="2" />
<text  x="1145.10" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (16,639,167,567 samples, 0.18%)</title><rect x="1054.8" y="453" width="2.0" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1057.75" y="463.5" ></text>
</g>
<g >
<title>MemoryContextCreate (1,351,717,195 samples, 0.01%)</title><rect x="422.6" y="357" width="0.1" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="425.56" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32 (896,578,856 samples, 0.01%)</title><rect x="326.2" y="197" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="329.18" y="207.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64 (2,632,575,738 samples, 0.03%)</title><rect x="510.3" y="485" width="0.3" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="513.29" y="495.5" ></text>
</g>
<g >
<title>can_coerce_type (914,906,183 samples, 0.01%)</title><rect x="843.9" y="421" width="0.2" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="846.95" y="431.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,504,642,807 samples, 0.02%)</title><rect x="57.2" y="757" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="60.25" y="767.5" ></text>
</g>
<g >
<title>BufferGetTag (4,866,257,802 samples, 0.05%)</title><rect x="392.6" y="325" width="0.7" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="395.65" y="335.5" ></text>
</g>
<g >
<title>set_ps_display (6,733,836,682 samples, 0.07%)</title><rect x="1081.9" y="597" width="0.8" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="1084.85" y="607.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,691,691,922 samples, 0.02%)</title><rect x="350.1" y="293" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="353.07" y="303.5" ></text>
</g>
<g >
<title>_bt_search (10,588,175,261 samples, 0.11%)</title><rect x="126.9" y="277" width="1.3" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="129.89" y="287.5" ></text>
</g>
<g >
<title>setNamespaceLateralState (1,216,214,070 samples, 0.01%)</title><rect x="833.6" y="469" width="0.1" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="836.56" y="479.5" ></text>
</g>
<g >
<title>DecrTupleDescRefCount (2,309,510,087 samples, 0.02%)</title><rect x="249.3" y="469" width="0.3" height="15.0" fill="rgb(243,174,41)" rx="2" ry="2" />
<text  x="252.27" y="479.5" ></text>
</g>
<g >
<title>palloc (4,082,475,020 samples, 0.04%)</title><rect x="918.7" y="453" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="921.71" y="463.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,451,970,234 samples, 0.04%)</title><rect x="1045.5" y="389" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1048.45" y="399.5" ></text>
</g>
<g >
<title>PageGetMaxOffsetNumber (1,058,184,890 samples, 0.01%)</title><rect x="310.8" y="245" width="0.2" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="313.83" y="255.5" ></text>
</g>
<g >
<title>exprTypmod (1,408,298,420 samples, 0.01%)</title><rect x="844.2" y="405" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="847.22" y="415.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (891,884,930 samples, 0.01%)</title><rect x="415.8" y="373" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="418.76" y="383.5" ></text>
</g>
<g >
<title>newNode (2,504,113,266 samples, 0.03%)</title><rect x="1054.9" y="437" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1057.92" y="447.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="693" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.62" y="703.5" ></text>
</g>
<g >
<title>palloc0 (4,600,120,982 samples, 0.05%)</title><rect x="428.5" y="325" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="431.50" y="335.5" ></text>
</g>
<g >
<title>futex_wait (1,128,197,959 samples, 0.01%)</title><rect x="353.6" y="229" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="356.56" y="239.5" ></text>
</g>
<g >
<title>pg_utf8_verifystr (1,245,032,492 samples, 0.01%)</title><rect x="1171.2" y="757" width="0.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1174.17" y="767.5" ></text>
</g>
<g >
<title>ProcReleaseLocks (109,617,347,170 samples, 1.15%)</title><rect x="516.0" y="485" width="13.6" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="518.99" y="495.5" ></text>
</g>
<g >
<title>create_scan_plan (44,299,091,673 samples, 0.47%)</title><rect x="886.9" y="421" width="5.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="889.93" y="431.5" ></text>
</g>
<g >
<title>hash_seq_term (1,078,752,059 samples, 0.01%)</title><rect x="529.4" y="437" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="532.41" y="447.5" ></text>
</g>
<g >
<title>PageValidateSpecialPointer (1,021,928,411 samples, 0.01%)</title><rect x="82.9" y="757" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="85.87" y="767.5" ></text>
</g>
<g >
<title>LockHeldByMe (11,889,065,235 samples, 0.13%)</title><rect x="867.0" y="453" width="1.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="869.96" y="463.5" ></text>
</g>
<g >
<title>postmaster_child_launch (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="661" width="2.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="129.31" y="671.5" ></text>
</g>
<g >
<title>transformExpr (56,948,732,517 samples, 0.60%)</title><rect x="836.0" y="437" width="7.1" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="839.01" y="447.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,106,923,373 samples, 0.01%)</title><rect x="123.5" y="741" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="126.53" y="751.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (898,117,188 samples, 0.01%)</title><rect x="86.8" y="165" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="89.80" y="175.5" ></text>
</g>
<g >
<title>do_syscall_64 (9,034,105,319 samples, 0.10%)</title><rect x="937.0" y="197" width="1.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="940.00" y="207.5" ></text>
</g>
<g >
<title>try_to_wake_up (935,152,819 samples, 0.01%)</title><rect x="478.2" y="277" width="0.1" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="481.20" y="287.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,986,012,016 samples, 0.05%)</title><rect x="388.5" y="293" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="391.47" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,810,068,831 samples, 0.02%)</title><rect x="1116.3" y="693" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1119.31" y="703.5" ></text>
</g>
<g >
<title>apply_scanjoin_target_to_paths (14,425,447,193 samples, 0.15%)</title><rect x="909.4" y="485" width="1.8" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="912.37" y="495.5" ></text>
</g>
<g >
<title>get_typlen (4,906,908,612 samples, 0.05%)</title><rect x="1046.7" y="437" width="0.6" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="1049.73" y="447.5" ></text>
</g>
<g >
<title>BackendStartup (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="677" width="2.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="129.31" y="687.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (328,103,132,612 samples, 3.45%)</title><rect x="717.0" y="533" width="40.7" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="719.98" y="543.5" >Mem..</text>
</g>
<g >
<title>_bt_readpage (4,599,176,588 samples, 0.05%)</title><rect x="124.2" y="277" width="0.6" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="127.19" y="287.5" ></text>
</g>
<g >
<title>ExecEndNode (70,040,122,799 samples, 0.74%)</title><rect x="240.0" y="485" width="8.7" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="243.01" y="495.5" ></text>
</g>
<g >
<title>list_copy (2,758,921,519 samples, 0.03%)</title><rect x="401.2" y="373" width="0.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="404.23" y="383.5" ></text>
</g>
<g >
<title>ProcessQuery (1,819,800,332,381 samples, 19.16%)</title><rect x="235.3" y="549" width="226.1" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="238.32" y="559.5" >ProcessQuery</text>
</g>
<g >
<title>ResourceOwnerEnlarge (1,732,462,127 samples, 0.02%)</title><rect x="91.9" y="757" width="0.2" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="94.85" y="767.5" ></text>
</g>
<g >
<title>palloc (1,957,667,816 samples, 0.02%)</title><rect x="456.6" y="389" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="459.59" y="399.5" ></text>
</g>
<g >
<title>new_list (1,758,342,600 samples, 0.02%)</title><rect x="933.8" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="936.81" y="383.5" ></text>
</g>
<g >
<title>bms_equal (2,245,929,414 samples, 0.02%)</title><rect x="956.8" y="453" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="959.77" y="463.5" ></text>
</g>
<g >
<title>socket_flush (157,841,222,611 samples, 1.66%)</title><rect x="190.4" y="581" width="19.6" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="193.39" y="591.5" ></text>
</g>
<g >
<title>_bt_lockbuf (1,853,327,073 samples, 0.02%)</title><rect x="127.8" y="229" width="0.2" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="130.76" y="239.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (818,868,155 samples, 0.01%)</title><rect x="357.5" y="309" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="360.48" y="319.5" ></text>
</g>
<g >
<title>do_futex (26,944,102,367 samples, 0.28%)</title><rect x="491.7" y="357" width="3.3" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="494.68" y="367.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,556,301,355 samples, 0.02%)</title><rect x="475.5" y="485" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="478.53" y="495.5" ></text>
</g>
<g >
<title>ReadBuffer_common (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="245" width="0.5" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="89.39" y="255.5" ></text>
</g>
<g >
<title>ReleaseCatCache (862,026,613 samples, 0.01%)</title><rect x="972.2" y="357" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="975.23" y="367.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,238,292,445 samples, 0.01%)</title><rect x="428.9" y="293" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="431.87" y="303.5" ></text>
</g>
<g >
<title>ExecScanExtended (471,501,489,492 samples, 4.96%)</title><rect x="285.0" y="373" width="58.5" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="287.97" y="383.5" >ExecSc..</text>
</g>
<g >
<title>check_stack_depth (848,610,235 samples, 0.01%)</title><rect x="846.4" y="437" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="849.42" y="447.5" ></text>
</g>
<g >
<title>wipe_mem (5,013,617,111 samples, 0.05%)</title><rect x="264.5" y="405" width="0.6" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="267.50" y="415.5" ></text>
</g>
<g >
<title>pg_plan_query (1,837,816,106 samples, 0.02%)</title><rect x="86.9" y="629" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="89.91" y="639.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,613,237,873 samples, 0.02%)</title><rect x="1016.7" y="309" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1019.70" y="319.5" ></text>
</g>
<g >
<title>lappend (2,100,274,638 samples, 0.02%)</title><rect x="914.7" y="469" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="917.74" y="479.5" ></text>
</g>
<g >
<title>RelationGetNumberOfBlocksInFork (11,765,590,243 samples, 0.12%)</title><rect x="931.6" y="405" width="1.5" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="934.62" y="415.5" ></text>
</g>
<g >
<title>MakePerTupleExprContext (9,788,278,232 samples, 0.10%)</title><rect x="346.6" y="389" width="1.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="349.59" y="399.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (2,014,500,464 samples, 0.02%)</title><rect x="312.1" y="229" width="0.3" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="315.10" y="239.5" ></text>
</g>
<g >
<title>transform_MERGE_to_join (808,131,431 samples, 0.01%)</title><rect x="1187.9" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1190.94" y="767.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,454,248,903 samples, 0.02%)</title><rect x="353.5" y="309" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="356.54" y="319.5" ></text>
</g>
<g >
<title>newNode (3,472,630,718 samples, 0.04%)</title><rect x="1023.5" y="389" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1026.53" y="399.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (877,280,351 samples, 0.01%)</title><rect x="393.4" y="293" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="396.43" y="303.5" ></text>
</g>
<g >
<title>update_entity_lag (3,579,844,560 samples, 0.04%)</title><rect x="172.3" y="277" width="0.5" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="175.31" y="287.5" ></text>
</g>
<g >
<title>__task_rq_lock (1,078,792,808 samples, 0.01%)</title><rect x="201.9" y="325" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="204.85" y="335.5" ></text>
</g>
<g >
<title>ExecModifyTable (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="485" width="1.9" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="126.99" y="495.5" ></text>
</g>
<g >
<title>AllocSetFree (46,469,164,795 samples, 0.49%)</title><rect x="242.4" y="373" width="5.7" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="245.37" y="383.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,316,451,535 samples, 0.01%)</title><rect x="58.1" y="757" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="61.13" y="767.5" ></text>
</g>
<g >
<title>ItemPointerEquals (1,482,352,474 samples, 0.02%)</title><rect x="55.7" y="757" width="0.1" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="58.66" y="767.5" ></text>
</g>
<g >
<title>finalize_primnode (1,100,098,909 samples, 0.01%)</title><rect x="882.3" y="389" width="0.1" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="885.26" y="399.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (908,998,094 samples, 0.01%)</title><rect x="128.4" y="405" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="131.36" y="415.5" ></text>
</g>
<g >
<title>buildNSItemFromTupleDesc (6,649,508,113 samples, 0.07%)</title><rect x="815.7" y="453" width="0.8" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="818.69" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (820,349,264 samples, 0.01%)</title><rect x="874.4" y="517" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="877.41" y="527.5" ></text>
</g>
<g >
<title>LWLockRelease (2,512,547,452 samples, 0.03%)</title><rect x="522.8" y="453" width="0.3" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="525.83" y="463.5" ></text>
</g>
<g >
<title>__schedule (854,719,934 samples, 0.01%)</title><rect x="353.6" y="165" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="356.57" y="175.5" ></text>
</g>
<g >
<title>expr_setup_walker (1,352,099,641 samples, 0.01%)</title><rect x="438.7" y="309" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="441.73" y="319.5" ></text>
</g>
<g >
<title>verify_compact_attribute (2,387,865,595 samples, 0.03%)</title><rect x="84.7" y="229" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="87.69" y="239.5" ></text>
</g>
<g >
<title>pg_database_encoding_max_length (910,535,600 samples, 0.01%)</title><rect x="1113.1" y="677" width="0.1" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="1116.11" y="687.5" ></text>
</g>
<g >
<title>ExecProcNode (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="549" width="2.6" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="87.32" y="559.5" ></text>
</g>
<g >
<title>AtEOXact_Parallel (993,871,802 samples, 0.01%)</title><rect x="32.0" y="757" width="0.1" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="34.99" y="767.5" ></text>
</g>
<g >
<title>generate_bitmap_or_paths (8,054,599,852 samples, 0.08%)</title><rect x="990.3" y="389" width="1.0" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="993.32" y="399.5" ></text>
</g>
<g >
<title>palloc0 (4,678,419,234 samples, 0.05%)</title><rect x="819.5" y="437" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="822.46" y="447.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (898,622,352 samples, 0.01%)</title><rect x="801.9" y="533" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="804.95" y="543.5" ></text>
</g>
<g >
<title>sysvec_thermal (1,908,130,319 samples, 0.02%)</title><rect x="758.7" y="517" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="761.67" y="527.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,473,319,950 samples, 0.07%)</title><rect x="973.1" y="357" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="976.13" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,389,507,898 samples, 0.03%)</title><rect x="481.7" y="453" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="484.72" y="463.5" ></text>
</g>
<g >
<title>update_se (7,012,441,178 samples, 0.07%)</title><rect x="171.4" y="261" width="0.9" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="174.44" y="271.5" ></text>
</g>
<g >
<title>hash_initial_lookup (859,228,511 samples, 0.01%)</title><rect x="403.1" y="309" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="406.10" y="319.5" ></text>
</g>
<g >
<title>hash_bytes (2,466,366,371 samples, 0.03%)</title><rect x="823.2" y="325" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="826.17" y="335.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,369,785,851 samples, 0.05%)</title><rect x="924.6" y="437" width="0.5" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="927.57" y="447.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVisibility (937,219,638 samples, 0.01%)</title><rect x="52.9" y="757" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="55.89" y="767.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,624,560,495 samples, 0.02%)</title><rect x="1023.1" y="309" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1026.12" y="319.5" ></text>
</g>
<g >
<title>hash_bytes (4,588,996,817 samples, 0.05%)</title><rect x="831.0" y="325" width="0.6" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="834.04" y="335.5" ></text>
</g>
<g >
<title>analyze_requires_snapshot (1,621,052,871 samples, 0.02%)</title><rect x="1083.7" y="757" width="0.2" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="1086.73" y="767.5" ></text>
</g>
<g >
<title>finish_xact_command (2,714,133,727,078 samples, 28.57%)</title><rect x="465.1" y="581" width="337.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="468.13" y="591.5" >finish_xact_command</text>
</g>
<g >
<title>AtEOXact_PgStat_Relations (3,034,855,043 samples, 0.03%)</title><rect x="470.8" y="501" width="0.4" height="15.0" fill="rgb(228,108,26)" rx="2" ry="2" />
<text  x="473.79" y="511.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (855,737,378 samples, 0.01%)</title><rect x="377.7" y="309" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="380.66" y="319.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,197,754,817 samples, 0.01%)</title><rect x="959.4" y="277" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="962.45" y="287.5" ></text>
</g>
<g >
<title>GetCommandTagNameAndLen (827,601,576 samples, 0.01%)</title><rect x="49.0" y="757" width="0.1" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="52.04" y="767.5" ></text>
</g>
<g >
<title>ExecConditionalAssignProjectionInfo (80,750,426,473 samples, 0.85%)</title><rect x="423.5" y="405" width="10.0" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="426.46" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (19,337,673,108 samples, 0.20%)</title><rect x="371.4" y="261" width="2.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="374.40" y="271.5" ></text>
</g>
<g >
<title>palloc (1,275,804,392 samples, 0.01%)</title><rect x="971.2" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="974.21" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,096,358,159 samples, 0.01%)</title><rect x="967.0" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="969.99" y="351.5" ></text>
</g>
<g >
<title>tas (2,120,694,718 samples, 0.02%)</title><rect x="519.5" y="421" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="522.47" y="431.5" ></text>
</g>
<g >
<title>futex_do_wait (890,099,722 samples, 0.01%)</title><rect x="353.6" y="197" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="356.57" y="207.5" ></text>
</g>
<g >
<title>list_free (1,661,787,340 samples, 0.02%)</title><rect x="254.3" y="421" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="257.33" y="431.5" ></text>
</g>
<g >
<title>palloc0 (3,721,733,483 samples, 0.04%)</title><rect x="436.0" y="405" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="439.01" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,352,869,853 samples, 0.01%)</title><rect x="463.9" y="517" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="466.88" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (3,310,907,079 samples, 0.03%)</title><rect x="1112.7" y="661" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1115.68" y="671.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,694,528,881 samples, 0.05%)</title><rect x="1041.5" y="309" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1044.52" y="319.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (2,118,377,737 samples, 0.02%)</title><rect x="1058.2" y="437" width="0.3" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="1061.25" y="447.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,014,517,469 samples, 0.04%)</title><rect x="291.3" y="197" width="0.5" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="294.26" y="207.5" ></text>
</g>
<g >
<title>GetCurrentTransactionStopTimestamp (3,640,156,337 samples, 0.04%)</title><rect x="476.6" y="501" width="0.5" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="479.64" y="511.5" ></text>
</g>
<g >
<title>lappend (5,204,850,013 samples, 0.05%)</title><rect x="1154.5" y="757" width="0.6" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1157.45" y="767.5" ></text>
</g>
<g >
<title>makeIntConst (4,705,105,787 samples, 0.05%)</title><rect x="1116.5" y="741" width="0.6" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1119.53" y="751.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (1,665,522,712 samples, 0.02%)</title><rect x="312.1" y="213" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="315.14" y="223.5" ></text>
</g>
<g >
<title>TransactionIdGetStatus (9,020,835,300 samples, 0.09%)</title><rect x="308.7" y="181" width="1.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="311.73" y="191.5" ></text>
</g>
<g >
<title>LWLockWakeup (6,984,148,097 samples, 0.07%)</title><rect x="367.5" y="277" width="0.9" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="370.49" y="287.5" ></text>
</g>
<g >
<title>shmem_file_llseek (1,491,072,759 samples, 0.02%)</title><rect x="937.9" y="165" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="940.90" y="175.5" ></text>
</g>
<g >
<title>expr_setup_walker (11,652,263,952 samples, 0.12%)</title><rect x="425.2" y="341" width="1.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="428.21" y="351.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (836,334,169 samples, 0.01%)</title><rect x="912.9" y="405" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="915.92" y="415.5" ></text>
</g>
<g >
<title>ReadBuffer (1,544,277,513 samples, 0.02%)</title><rect x="337.1" y="213" width="0.2" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="340.11" y="223.5" ></text>
</g>
<g >
<title>_bt_getbuf (1,853,327,073 samples, 0.02%)</title><rect x="127.8" y="245" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="130.76" y="255.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (13,046,915,088 samples, 0.14%)</title><rect x="144.5" y="533" width="1.6" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="147.51" y="543.5" ></text>
</g>
<g >
<title>get_tablespace (3,039,406,182 samples, 0.03%)</title><rect x="1015.6" y="309" width="0.4" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1018.64" y="319.5" ></text>
</g>
<g >
<title>bms_is_member (2,230,396,233 samples, 0.02%)</title><rect x="277.3" y="405" width="0.3" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="280.30" y="415.5" ></text>
</g>
<g >
<title>add_vars_to_targetlist (27,462,915,426 samples, 0.29%)</title><rect x="950.2" y="453" width="3.4" height="15.0" fill="rgb(221,76,18)" rx="2" ry="2" />
<text  x="953.18" y="463.5" ></text>
</g>
<g >
<title>LWLockRelease (9,169,008,169 samples, 0.10%)</title><rect x="367.4" y="309" width="1.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="370.37" y="319.5" ></text>
</g>
<g >
<title>BTreeTupleIsPosting (1,744,896,269 samples, 0.02%)</title><rect x="335.2" y="197" width="0.2" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="338.21" y="207.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (9,181,703,205 samples, 0.10%)</title><rect x="467.5" y="485" width="1.2" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="470.53" y="495.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (970,016,705 samples, 0.01%)</title><rect x="947.0" y="357" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="949.98" y="367.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,987,775,517 samples, 0.04%)</title><rect x="963.7" y="357" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="966.68" y="367.5" ></text>
</g>
<g >
<title>ep_item_poll.isra.0 (12,088,871,488 samples, 0.13%)</title><rect x="159.1" y="373" width="1.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="162.15" y="383.5" ></text>
</g>
<g >
<title>pfree (1,456,927,540 samples, 0.02%)</title><rect x="229.5" y="565" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="232.53" y="575.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (10,837,258,066 samples, 0.11%)</title><rect x="321.8" y="213" width="1.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="324.83" y="223.5" ></text>
</g>
<g >
<title>_bt_first (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="341" width="2.6" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="87.32" y="351.5" ></text>
</g>
<g >
<title>palloc0 (4,602,477,414 samples, 0.05%)</title><rect x="439.6" y="373" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="442.61" y="383.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="357" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="128.92" y="367.5" ></text>
</g>
<g >
<title>palloc0 (2,905,925,888 samples, 0.03%)</title><rect x="441.1" y="389" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="444.08" y="399.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="597" width="6.9" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="98.68" y="607.5" ></text>
</g>
<g >
<title>check_functions_in_node (7,082,785,709 samples, 0.07%)</title><rect x="916.8" y="421" width="0.9" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="919.79" y="431.5" ></text>
</g>
<g >
<title>EvalPlanQualInit (2,570,993,188 samples, 0.03%)</title><rect x="420.4" y="453" width="0.3" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="423.36" y="463.5" ></text>
</g>
<g >
<title>AllocSetFree (3,668,031,359 samples, 0.04%)</title><rect x="525.7" y="421" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="528.66" y="431.5" ></text>
</g>
<g >
<title>pg_plan_queries (2,204,184,953 samples, 0.02%)</title><rect x="128.2" y="597" width="0.3" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="131.20" y="607.5" ></text>
</g>
<g >
<title>palloc (3,358,475,623 samples, 0.04%)</title><rect x="1165.3" y="757" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1168.32" y="767.5" ></text>
</g>
<g >
<title>new_list (1,752,395,959 samples, 0.02%)</title><rect x="893.4" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="896.40" y="447.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="453" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="129.16" y="463.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (29,117,505,150 samples, 0.31%)</title><rect x="900.5" y="453" width="3.7" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="903.55" y="463.5" ></text>
</g>
<g >
<title>set_cheapest (4,023,345,730 samples, 0.04%)</title><rect x="910.7" y="469" width="0.5" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="913.66" y="479.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,073,088,514 samples, 0.01%)</title><rect x="943.7" y="325" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="946.66" y="335.5" ></text>
</g>
<g >
<title>bms_add_member (1,396,263,284 samples, 0.01%)</title><rect x="1120.6" y="757" width="0.2" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1123.63" y="767.5" ></text>
</g>
<g >
<title>new_list (1,812,070,690 samples, 0.02%)</title><rect x="440.8" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="443.79" y="399.5" ></text>
</g>
<g >
<title>ttwu_do_activate (11,326,503,590 samples, 0.12%)</title><rect x="206.4" y="325" width="1.4" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="209.44" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (11,206,223,450 samples, 0.12%)</title><rect x="294.2" y="261" width="1.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="297.22" y="271.5" ></text>
</g>
<g >
<title>_bt_first (226,761,321,575 samples, 2.39%)</title><rect x="314.1" y="277" width="28.2" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="317.15" y="287.5" >_..</text>
</g>
<g >
<title>file_update_time (3,171,826,220 samples, 0.03%)</title><rect x="501.8" y="373" width="0.4" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="504.80" y="383.5" ></text>
</g>
<g >
<title>contain_volatile_functions_checker (6,341,062,891 samples, 0.07%)</title><rect x="959.3" y="325" width="0.8" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="962.31" y="335.5" ></text>
</g>
<g >
<title>PortalCleanup (1,007,102,109 samples, 0.01%)</title><rect x="83.7" y="757" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="86.75" y="767.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,096,661,537 samples, 0.01%)</title><rect x="507.5" y="421" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="510.53" y="431.5" ></text>
</g>
<g >
<title>hash_search (4,630,125,783 samples, 0.05%)</title><rect x="453.1" y="357" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="456.15" y="367.5" ></text>
</g>
<g >
<title>bms_make_singleton (3,477,055,901 samples, 0.04%)</title><rect x="878.2" y="469" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="881.19" y="479.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (2,463,738,124 samples, 0.03%)</title><rect x="84.7" y="245" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="87.68" y="255.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,397,509,747 samples, 0.05%)</title><rect x="402.8" y="357" width="0.5" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="405.80" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache4 (10,751,842,512 samples, 0.11%)</title><rect x="1008.4" y="277" width="1.3" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="1011.36" y="287.5" ></text>
</g>
<g >
<title>get_hash_entry (4,960,742,812 samples, 0.05%)</title><rect x="373.0" y="245" width="0.6" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="375.97" y="255.5" ></text>
</g>
<g >
<title>ep_poll (142,768,906,068 samples, 1.50%)</title><rect x="157.1" y="405" width="17.8" height="15.0" fill="rgb(238,151,36)" rx="2" ry="2" />
<text  x="160.14" y="415.5" ></text>
</g>
<g >
<title>ExecScan (24,636,562,570 samples, 0.26%)</title><rect x="281.7" y="405" width="3.0" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="284.67" y="415.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,085,768,088 samples, 0.01%)</title><rect x="369.9" y="213" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="372.95" y="223.5" ></text>
</g>
<g >
<title>lcons (2,337,232,746 samples, 0.02%)</title><rect x="910.9" y="453" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="913.87" y="463.5" ></text>
</g>
<g >
<title>try_to_block_task.constprop.0.isra.0 (48,126,316,393 samples, 0.51%)</title><rect x="168.5" y="341" width="6.0" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="171.54" y="351.5" ></text>
</g>
<g >
<title>ScanKeywords_hash_func (9,918,355,124 samples, 0.10%)</title><rect x="1110.3" y="693" width="1.3" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1113.34" y="703.5" ></text>
</g>
<g >
<title>preprocess_targetlist (1,086,112,813 samples, 0.01%)</title><rect x="1174.1" y="757" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1177.06" y="767.5" ></text>
</g>
<g >
<title>BackendStartup (83,672,568,701 samples, 0.88%)</title><rect x="95.7" y="741" width="10.4" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="98.68" y="751.5" ></text>
</g>
<g >
<title>tts_buffer_heap_materialize (1,494,985,184 samples, 0.02%)</title><rect x="1188.5" y="757" width="0.2" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="1191.47" y="767.5" ></text>
</g>
<g >
<title>planstate_tree_walker_impl (6,075,316,499 samples, 0.06%)</title><rect x="410.3" y="453" width="0.8" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="413.34" y="463.5" ></text>
</g>
<g >
<title>heap_page_prune_opt (10,847,312,247 samples, 0.11%)</title><rect x="1148.8" y="757" width="1.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1151.81" y="767.5" ></text>
</g>
<g >
<title>CommitTransaction (520,292,370,484 samples, 5.48%)</title><rect x="466.2" y="533" width="64.6" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="469.17" y="543.5" >CommitT..</text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,779,446,574 samples, 0.02%)</title><rect x="496.5" y="421" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="499.48" y="431.5" ></text>
</g>
<g >
<title>palloc (1,056,889,321 samples, 0.01%)</title><rect x="815.2" y="405" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="818.16" y="415.5" ></text>
</g>
<g >
<title>finish_task_switch.isra.0 (8,973,666,904 samples, 0.09%)</title><rect x="164.2" y="341" width="1.1" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="167.17" y="351.5" ></text>
</g>
<g >
<title>RelationGetIndexPredicate (1,162,115,285 samples, 0.01%)</title><rect x="931.5" y="405" width="0.1" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="934.47" y="415.5" ></text>
</g>
<g >
<title>smgrnblocks (22,276,484,069 samples, 0.23%)</title><rect x="935.7" y="293" width="2.8" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="938.75" y="303.5" ></text>
</g>
<g >
<title>__irq_exit_rcu (1,258,707,312 samples, 0.01%)</title><rect x="757.8" y="501" width="0.1" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="760.75" y="511.5" ></text>
</g>
<g >
<title>message_level_is_interesting (1,028,535,048 samples, 0.01%)</title><rect x="1076.6" y="517" width="0.1" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="1079.60" y="527.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (7,341,089,923 samples, 0.08%)</title><rect x="300.5" y="117" width="0.9" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="303.52" y="127.5" ></text>
</g>
<g >
<title>tas (1,751,831,693 samples, 0.02%)</title><rect x="388.2" y="293" width="0.3" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="391.24" y="303.5" ></text>
</g>
<g >
<title>_bt_lockbuf (3,185,105,605 samples, 0.03%)</title><rect x="341.2" y="229" width="0.3" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="344.15" y="239.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (825,282,682 samples, 0.01%)</title><rect x="101.3" y="149" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="104.30" y="159.5" ></text>
</g>
<g >
<title>ResourceOwnerReleaseAll (1,584,420,156 samples, 0.02%)</title><rect x="228.1" y="533" width="0.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="231.11" y="543.5" ></text>
</g>
<g >
<title>int4hashfast (965,280,220 samples, 0.01%)</title><rect x="443.9" y="293" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="446.85" y="303.5" ></text>
</g>
<g >
<title>_bt_check_compare (4,627,104,075 samples, 0.05%)</title><rect x="126.3" y="229" width="0.6" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="129.31" y="239.5" ></text>
</g>
<g >
<title>LWLockRelease (1,465,648,541 samples, 0.02%)</title><rect x="527.1" y="437" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="530.14" y="447.5" ></text>
</g>
<g >
<title>inode_to_bdi (1,023,479,854 samples, 0.01%)</title><rect x="502.6" y="341" width="0.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="505.65" y="351.5" ></text>
</g>
<g >
<title>pfree (47,490,926,688 samples, 0.50%)</title><rect x="242.3" y="389" width="5.9" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="245.32" y="399.5" ></text>
</g>
<g >
<title>new_list (1,560,804,191 samples, 0.02%)</title><rect x="896.8" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="899.76" y="447.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (45,621,589,468 samples, 0.48%)</title><rect x="19.2" y="741" width="5.7" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="22.18" y="751.5" ></text>
</g>
<g >
<title>AllocSetAllocLarge (9,901,218,233 samples, 0.10%)</title><rect x="294.3" y="245" width="1.3" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="297.34" y="255.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (1,799,229,374 samples, 0.02%)</title><rect x="299.7" y="165" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="302.66" y="175.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (884,781,725 samples, 0.01%)</title><rect x="95.7" y="213" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="98.68" y="223.5" ></text>
</g>
<g >
<title>vfs_write (52,413,972,048 samples, 0.55%)</title><rect x="500.3" y="405" width="6.5" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="503.29" y="415.5" ></text>
</g>
<g >
<title>ComputeXidHorizons (2,530,131,093 samples, 0.03%)</title><rect x="305.8" y="181" width="0.3" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="308.76" y="191.5" ></text>
</g>
<g >
<title>ItemPointerIsValid (3,133,019,062 samples, 0.03%)</title><rect x="56.6" y="757" width="0.4" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="59.61" y="767.5" ></text>
</g>
<g >
<title>HeapTupleHeaderXminCommitted (3,755,011,625 samples, 0.04%)</title><rect x="307.0" y="213" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="310.02" y="223.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberCatCacheRef (997,002,053 samples, 0.01%)</title><rect x="829.1" y="309" width="0.1" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="832.06" y="319.5" ></text>
</g>
<g >
<title>SerializationNeededForRead (1,104,371,322 samples, 0.01%)</title><rect x="95.5" y="757" width="0.2" height="15.0" fill="rgb(224,87,20)" rx="2" ry="2" />
<text  x="98.53" y="767.5" ></text>
</g>
<g >
<title>futex_wait (55,331,353,157 samples, 0.58%)</title><rect x="483.2" y="357" width="6.9" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="486.20" y="367.5" ></text>
</g>
<g >
<title>_bt_freestack (2,484,712,612 samples, 0.03%)</title><rect x="323.3" y="261" width="0.3" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="326.32" y="271.5" ></text>
</g>
<g >
<title>_mm_set_epi8 (815,375,639 samples, 0.01%)</title><rect x="1081.5" y="469" width="0.1" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="1084.50" y="479.5" ></text>
</g>
<g >
<title>palloc0 (1,153,498,951 samples, 0.01%)</title><rect x="1163.9" y="741" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1166.93" y="751.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (2,158,915,946 samples, 0.02%)</title><rect x="275.1" y="389" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="278.08" y="399.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,052,820,281 samples, 0.01%)</title><rect x="1068.1" y="405" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1071.08" y="415.5" ></text>
</g>
<g >
<title>lappend (3,294,863,039 samples, 0.03%)</title><rect x="861.3" y="501" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="864.26" y="511.5" ></text>
</g>
<g >
<title>bsearch (6,227,095,902 samples, 0.07%)</title><rect x="270.0" y="325" width="0.8" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="272.98" y="335.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,267,687,232 samples, 0.03%)</title><rect x="960.3" y="325" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="963.25" y="335.5" ></text>
</g>
<g >
<title>palloc0 (1,636,922,764 samples, 0.02%)</title><rect x="996.7" y="245" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="999.75" y="255.5" ></text>
</g>
<g >
<title>BufferGetPage (4,779,378,458 samples, 0.05%)</title><rect x="35.3" y="757" width="0.6" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="38.28" y="767.5" ></text>
</g>
<g >
<title>MemoryContextSetParent (2,192,331,437 samples, 0.02%)</title><rect x="253.3" y="437" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="256.29" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (810,889,545 samples, 0.01%)</title><rect x="527.2" y="389" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="530.22" y="399.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,534,147,982 samples, 0.02%)</title><rect x="1065.6" y="437" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1068.65" y="447.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,762,260,756 samples, 0.03%)</title><rect x="857.2" y="309" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="860.22" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,509,696,471 samples, 0.02%)</title><rect x="864.0" y="453" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="867.02" y="463.5" ></text>
</g>
<g >
<title>pfree (2,131,212,591 samples, 0.02%)</title><rect x="995.2" y="325" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="998.25" y="335.5" ></text>
</g>
<g >
<title>native_write_msr (1,115,051,174 samples, 0.01%)</title><rect x="484.8" y="213" width="0.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="487.81" y="223.5" ></text>
</g>
<g >
<title>create_scan_plan (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="453" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="128.92" y="463.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesMVCC (30,880,562,662 samples, 0.33%)</title><rect x="306.4" y="229" width="3.8" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="309.39" y="239.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (891,789,765 samples, 0.01%)</title><rect x="120.1" y="741" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="123.10" y="751.5" ></text>
</g>
<g >
<title>palloc (1,100,477,832 samples, 0.01%)</title><rect x="1053.4" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1056.44" y="415.5" ></text>
</g>
<g >
<title>GetScanKeyword (1,670,892,337 samples, 0.02%)</title><rect x="1110.1" y="693" width="0.2" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="1113.13" y="703.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (816,605,350 samples, 0.01%)</title><rect x="1029.8" y="229" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1032.82" y="239.5" ></text>
</g>
<g >
<title>_bt_check_compare (2,463,738,124 samples, 0.03%)</title><rect x="84.7" y="277" width="0.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="87.68" y="287.5" ></text>
</g>
<g >
<title>tag_hash (2,544,563,585 samples, 0.03%)</title><rect x="448.6" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="451.56" y="351.5" ></text>
</g>
<g >
<title>AllocSetCheck (2,043,955,319,382 samples, 21.52%)</title><rect x="542.4" y="549" width="253.8" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="545.35" y="559.5" >AllocSetCheck</text>
</g>
<g >
<title>_bt_num_array_keys (1,307,635,084 samples, 0.01%)</title><rect x="130.6" y="757" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="133.57" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,852,740,407 samples, 0.04%)</title><rect x="228.7" y="549" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="231.67" y="559.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="501" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="128.92" y="511.5" ></text>
</g>
<g >
<title>create_projection_path (5,682,820,305 samples, 0.06%)</title><rect x="909.9" y="469" width="0.7" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="912.86" y="479.5" ></text>
</g>
<g >
<title>lcons (1,182,615,046 samples, 0.01%)</title><rect x="1155.2" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1158.19" y="767.5" ></text>
</g>
<g >
<title>lnext (1,606,005,733 samples, 0.02%)</title><rect x="802.3" y="581" width="0.2" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="805.28" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (986,545,172 samples, 0.01%)</title><rect x="457.4" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="460.37" y="415.5" ></text>
</g>
<g >
<title>core_yyalloc (2,877,486,095 samples, 0.03%)</title><rect x="873.0" y="517" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="875.97" y="527.5" ></text>
</g>
<g >
<title>epoll_wait (1,700,398,746 samples, 0.02%)</title><rect x="177.8" y="485" width="0.2" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="180.78" y="495.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,341,249,667 samples, 0.01%)</title><rect x="388.9" y="213" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="391.90" y="223.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,430,218,785 samples, 0.02%)</title><rect x="240.9" y="389" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="243.85" y="399.5" ></text>
</g>
<g >
<title>create_seqscan_path (16,138,174,122 samples, 0.17%)</title><rect x="1022.0" y="405" width="2.0" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1024.96" y="415.5" ></text>
</g>
<g >
<title>palloc0 (1,488,177,300 samples, 0.02%)</title><rect x="1058.6" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1061.60" y="447.5" ></text>
</g>
<g >
<title>bsearch (1,539,955,512 samples, 0.02%)</title><rect x="1123.0" y="757" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1126.04" y="767.5" ></text>
</g>
<g >
<title>hash_search (14,817,043,985 samples, 0.16%)</title><rect x="523.8" y="437" width="1.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="526.75" y="447.5" ></text>
</g>
<g >
<title>get_relation_foreign_keys (1,057,002,162 samples, 0.01%)</title><rect x="939.3" y="405" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="942.25" y="415.5" ></text>
</g>
<g >
<title>__irq_exit_rcu (1,189,339,536 samples, 0.01%)</title><rect x="757.2" y="485" width="0.1" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="760.19" y="495.5" ></text>
</g>
<g >
<title>update_se (3,394,489,575 samples, 0.04%)</title><rect x="487.4" y="197" width="0.5" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="490.44" y="207.5" ></text>
</g>
<g >
<title>finalize_primnode (4,810,712,275 samples, 0.05%)</title><rect x="882.4" y="405" width="0.6" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="885.40" y="415.5" ></text>
</g>
<g >
<title>hash_search (3,564,983,453 samples, 0.04%)</title><rect x="1067.2" y="421" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1070.25" y="431.5" ></text>
</g>
<g >
<title>pg_verify_mbstr (10,552,927,707 samples, 0.11%)</title><rect x="1080.5" y="549" width="1.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1083.50" y="559.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (15,255,037,055 samples, 0.16%)</title><rect x="958.8" y="373" width="1.9" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="961.77" y="383.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,372,385,763 samples, 0.01%)</title><rect x="310.3" y="245" width="0.1" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="313.28" y="255.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="533" width="2.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="87.32" y="543.5" ></text>
</g>
<g >
<title>do_futex (939,032,409 samples, 0.01%)</title><rect x="369.9" y="165" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="372.95" y="175.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (1,196,869,803 samples, 0.01%)</title><rect x="1063.1" y="421" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1066.06" y="431.5" ></text>
</g>
<g >
<title>postgres (9,499,813,485,584 samples, 100.00%)</title><rect x="10.0" y="773" width="1180.0" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="13.00" y="783.5" >postgres</text>
</g>
<g >
<title>schedule (43,028,304,687 samples, 0.45%)</title><rect x="483.6" y="309" width="5.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="486.58" y="319.5" ></text>
</g>
<g >
<title>table_open (28,603,800,722 samples, 0.30%)</title><rect x="866.8" y="517" width="3.5" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="869.77" y="527.5" ></text>
</g>
<g >
<title>create_tidscan_paths (15,216,772,435 samples, 0.16%)</title><rect x="1024.0" y="405" width="1.9" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="1026.96" y="415.5" ></text>
</g>
<g >
<title>LockHeldByMe (4,947,441,742 samples, 0.05%)</title><rect x="451.0" y="405" width="0.6" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="454.00" y="415.5" ></text>
</g>
<g >
<title>TransactionIdCommitTree (26,628,422,521 samples, 0.28%)</title><rect x="477.3" y="501" width="3.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="480.33" y="511.5" ></text>
</g>
<g >
<title>RelationClose (1,468,145,260 samples, 0.02%)</title><rect x="238.5" y="437" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="241.47" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,379,194,883 samples, 0.01%)</title><rect x="892.1" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="895.05" y="351.5" ></text>
</g>
<g >
<title>BufTableHashCode (4,030,688,084 samples, 0.04%)</title><rect x="95.9" y="181" width="0.5" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="98.93" y="191.5" ></text>
</g>
<g >
<title>_bt_first (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="293" width="1.9" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="129.31" y="303.5" ></text>
</g>
<g >
<title>palloc0 (2,641,348,420 samples, 0.03%)</title><rect x="1020.5" y="293" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1023.50" y="303.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberCatCacheRef (836,499,119 samples, 0.01%)</title><rect x="92.9" y="757" width="0.1" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="95.89" y="767.5" ></text>
</g>
<g >
<title>newNode (4,642,960,801 samples, 0.05%)</title><rect x="966.6" y="373" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="969.55" y="383.5" ></text>
</g>
<g >
<title>bms_add_member (2,562,181,748 samples, 0.03%)</title><rect x="834.6" y="469" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="837.62" y="479.5" ></text>
</g>
<g >
<title>__x64_sys_futex (2,527,180,487 samples, 0.03%)</title><rect x="389.3" y="165" width="0.4" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="392.34" y="175.5" ></text>
</g>
<g >
<title>add_function_cost (6,467,096,309 samples, 0.07%)</title><rect x="1037.9" y="341" width="0.8" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1040.87" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,505,600,175 samples, 0.02%)</title><rect x="1020.6" y="277" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1023.64" y="287.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,088,549,554 samples, 0.03%)</title><rect x="996.1" y="293" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="999.14" y="303.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (29,650,317,438 samples, 0.31%)</title><rect x="299.4" y="261" width="3.6" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="302.35" y="271.5" ></text>
</g>
<g >
<title>update_load_avg (2,771,553,637 samples, 0.03%)</title><rect x="207.2" y="277" width="0.3" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="210.16" y="287.5" ></text>
</g>
<g >
<title>hash_bytes (10,912,273,998 samples, 0.11%)</title><rect x="851.7" y="341" width="1.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="854.74" y="351.5" ></text>
</g>
<g >
<title>_copyVar (3,584,804,977 samples, 0.04%)</title><rect x="888.9" y="325" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="891.89" y="335.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,593,747,446 samples, 0.02%)</title><rect x="872.8" y="517" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="875.77" y="527.5" ></text>
</g>
<g >
<title>AllocSetFree (992,168,993 samples, 0.01%)</title><rect x="954.0" y="405" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="956.98" y="415.5" ></text>
</g>
<g >
<title>new_list (1,937,481,588 samples, 0.02%)</title><rect x="969.0" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="972.03" y="367.5" ></text>
</g>
<g >
<title>fix_expr_common (1,307,840,816 samples, 0.01%)</title><rect x="903.1" y="325" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="906.09" y="335.5" ></text>
</g>
<g >
<title>GetUserId (1,871,608,393 samples, 0.02%)</title><rect x="50.8" y="757" width="0.3" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="53.85" y="767.5" ></text>
</g>
<g >
<title>apply_tlist_labeling (3,342,497,039 samples, 0.04%)</title><rect x="884.5" y="469" width="0.4" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="887.52" y="479.5" ></text>
</g>
<g >
<title>_bt_getbuf (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="309" width="4.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="98.68" y="319.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="229" width="2.4" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="103.12" y="239.5" ></text>
</g>
<g >
<title>__libc_start_main@@GLIBC_2.34 (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="757" width="2.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="129.31" y="767.5" ></text>
</g>
<g >
<title>palloc (2,349,784,583 samples, 0.02%)</title><rect x="813.6" y="421" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="816.58" y="431.5" ></text>
</g>
<g >
<title>PinBuffer (4,779,812,799 samples, 0.05%)</title><rect x="302.0" y="133" width="0.6" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="305.03" y="143.5" ></text>
</g>
<g >
<title>set_next_task_idle (1,017,893,674 samples, 0.01%)</title><rect x="484.6" y="261" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="487.57" y="271.5" ></text>
</g>
<g >
<title>palloc (1,610,107,450 samples, 0.02%)</title><rect x="441.9" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="444.90" y="367.5" ></text>
</g>
<g >
<title>check_stack_depth (7,941,260,994 samples, 0.08%)</title><rect x="1126.9" y="757" width="1.0" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="1129.88" y="767.5" ></text>
</g>
<g >
<title>ReleaseBuffer (3,046,294,381 samples, 0.03%)</title><rect x="382.5" y="357" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="385.50" y="367.5" ></text>
</g>
<g >
<title>makeConst (3,329,451,742 samples, 0.04%)</title><rect x="854.9" y="389" width="0.4" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="857.91" y="399.5" ></text>
</g>
<g >
<title>list_copy (2,481,575,746 samples, 0.03%)</title><rect x="976.6" y="421" width="0.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="979.60" y="431.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,262,946,556 samples, 0.03%)</title><rect x="124.8" y="229" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="127.77" y="239.5" ></text>
</g>
<g >
<title>pg_class_aclmask_ext (9,171,395,237 samples, 0.10%)</title><rect x="414.9" y="437" width="1.1" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="417.88" y="447.5" ></text>
</g>
<g >
<title>sock_poll (7,731,399,366 samples, 0.08%)</title><rect x="159.7" y="357" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="162.69" y="367.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (7,267,575,997 samples, 0.08%)</title><rect x="757.8" y="517" width="0.9" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="760.75" y="527.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (958,549,365 samples, 0.01%)</title><rect x="972.6" y="325" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="975.56" y="335.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (896,730,422 samples, 0.01%)</title><rect x="1034.1" y="101" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1037.14" y="111.5" ></text>
</g>
<g >
<title>heap_update (276,221,107,880 samples, 2.91%)</title><rect x="361.2" y="373" width="34.3" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="364.18" y="383.5" >he..</text>
</g>
<g >
<title>palloc (1,807,544,647 samples, 0.02%)</title><rect x="911.4" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="914.36" y="447.5" ></text>
</g>
<g >
<title>attnameAttNum (3,002,750,993 samples, 0.03%)</title><rect x="834.2" y="469" width="0.4" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="837.25" y="479.5" ></text>
</g>
<g >
<title>BufferDescriptorGetContentLock (857,339,616 samples, 0.01%)</title><rect x="34.6" y="757" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="37.63" y="767.5" ></text>
</g>
<g >
<title>uint32_hash (1,387,464,438 samples, 0.01%)</title><rect x="989.7" y="309" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="992.72" y="319.5" ></text>
</g>
<g >
<title>is_publishable_relation (1,091,140,393 samples, 0.01%)</title><rect x="420.1" y="405" width="0.2" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="423.12" y="415.5" ></text>
</g>
<g >
<title>set_ps_display_with_len (5,412,978,321 samples, 0.06%)</title><rect x="1082.0" y="581" width="0.7" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="1085.01" y="591.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,768,463,810 samples, 0.02%)</title><rect x="941.5" y="341" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="944.51" y="351.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,008,737,281 samples, 0.01%)</title><rect x="46.8" y="757" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="49.80" y="767.5" ></text>
</g>
<g >
<title>WalSndWakeup (848,004,966 samples, 0.01%)</title><rect x="110.4" y="757" width="0.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="113.36" y="767.5" ></text>
</g>
<g >
<title>skb_copy_datagram_iter (9,532,345,692 samples, 0.10%)</title><rect x="185.6" y="357" width="1.2" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="188.64" y="367.5" ></text>
</g>
<g >
<title>prune_freeze_plan (7,042,189,314 samples, 0.07%)</title><rect x="1149.3" y="725" width="0.8" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="1152.27" y="735.5" ></text>
</g>
<g >
<title>ExecFetchSlotHeapTuple (1,802,449,504 samples, 0.02%)</title><rect x="351.9" y="373" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="354.89" y="383.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,064,128,777 samples, 0.01%)</title><rect x="803.6" y="485" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="806.61" y="495.5" ></text>
</g>
<g >
<title>expr_setup_walker (8,534,705,861 samples, 0.09%)</title><rect x="425.6" y="309" width="1.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="428.59" y="319.5" ></text>
</g>
<g >
<title>GetCurrentTransactionId (85,851,580,835 samples, 0.90%)</title><rect x="364.1" y="357" width="10.7" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="367.11" y="367.5" ></text>
</g>
<g >
<title>ExecProject (21,920,849,111 samples, 0.23%)</title><rect x="269.2" y="421" width="2.7" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="272.22" y="431.5" ></text>
</g>
<g >
<title>hash_bytes (3,325,572,317 samples, 0.04%)</title><rect x="407.8" y="229" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="410.81" y="239.5" ></text>
</g>
<g >
<title>palloc (1,336,224,524 samples, 0.01%)</title><rect x="860.0" y="485" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="863.03" y="495.5" ></text>
</g>
<g >
<title>list_make2_impl (1,998,126,006 samples, 0.02%)</title><rect x="849.3" y="405" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="852.25" y="415.5" ></text>
</g>
<g >
<title>gup_fast_fallback (3,950,383,435 samples, 0.04%)</title><rect x="489.6" y="293" width="0.5" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="492.57" y="303.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberTupleDesc (1,397,163,980 samples, 0.01%)</title><rect x="279.0" y="357" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="282.00" y="367.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (8,891,784,737 samples, 0.09%)</title><rect x="832.2" y="405" width="1.1" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="835.16" y="415.5" ></text>
</g>
<g >
<title>eval_const_expressions (971,078,648 samples, 0.01%)</title><rect x="128.4" y="485" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="131.36" y="495.5" ></text>
</g>
<g >
<title>ChoosePortalStrategy (2,967,793,870 samples, 0.03%)</title><rect x="462.9" y="565" width="0.4" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="465.93" y="575.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,691,727,609 samples, 0.02%)</title><rect x="363.2" y="357" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="366.17" y="367.5" ></text>
</g>
<g >
<title>BackendMain (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="693" width="2.8" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="87.32" y="703.5" ></text>
</g>
<g >
<title>PageIsAllVisible (1,564,660,729 samples, 0.02%)</title><rect x="82.6" y="757" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="85.63" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,726,391,117 samples, 0.04%)</title><rect x="1045.4" y="421" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1048.43" y="431.5" ></text>
</g>
<g >
<title>newNode (1,321,632,754 samples, 0.01%)</title><rect x="813.9" y="453" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="816.91" y="463.5" ></text>
</g>
<g >
<title>index_getattr (2,613,393,792 samples, 0.03%)</title><rect x="124.4" y="229" width="0.3" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="127.43" y="239.5" ></text>
</g>
<g >
<title>make_pathkeys_for_sortclauses (1,259,642,825 samples, 0.01%)</title><rect x="1044.6" y="453" width="0.2" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1047.62" y="463.5" ></text>
</g>
<g >
<title>sock_recvmsg (53,402,976,122 samples, 0.56%)</title><rect x="180.2" y="421" width="6.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="183.19" y="431.5" ></text>
</g>
<g >
<title>list_nth_cell (3,537,961,211 samples, 0.04%)</title><rect x="1158.3" y="757" width="0.4" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1161.28" y="767.5" ></text>
</g>
<g >
<title>heapam_slot_callbacks (959,335,471 samples, 0.01%)</title><rect x="1150.8" y="757" width="0.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="1153.79" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,476,497,215 samples, 0.02%)</title><rect x="857.4" y="277" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="860.38" y="287.5" ></text>
</g>
<g >
<title>palloc (1,727,265,062 samples, 0.02%)</title><rect x="898.1" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="901.09" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,558,582,476 samples, 0.03%)</title><rect x="895.2" y="485" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="898.17" y="495.5" ></text>
</g>
<g >
<title>StartReadBuffer (11,333,614,982 samples, 0.12%)</title><rect x="85.0" y="229" width="1.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="87.98" y="239.5" ></text>
</g>
<g >
<title>bms_is_valid_set (968,631,399 samples, 0.01%)</title><rect x="383.1" y="341" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="386.15" y="351.5" ></text>
</g>
<g >
<title>uint32_hash (1,566,502,379 samples, 0.02%)</title><rect x="1012.5" y="245" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1015.53" y="255.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,019,949,518 samples, 0.05%)</title><rect x="438.3" y="373" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="441.29" y="383.5" ></text>
</g>
<g >
<title>BackendMain (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="677" width="0.4" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="1161.90" y="687.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,262,105,196 samples, 0.01%)</title><rect x="431.6" y="293" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="434.63" y="303.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetTupleDesc (1,607,240,744 samples, 0.02%)</title><rect x="249.4" y="453" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="252.36" y="463.5" ></text>
</g>
<g >
<title>ReadBuffer (29,220,058,617 samples, 0.31%)</title><rect x="299.4" y="245" width="3.6" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="302.41" y="255.5" ></text>
</g>
<g >
<title>BufTableLookup (5,157,198,271 samples, 0.05%)</title><rect x="96.4" y="181" width="0.7" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="99.43" y="191.5" ></text>
</g>
<g >
<title>list_free_private (2,181,384,869 samples, 0.02%)</title><rect x="403.4" y="373" width="0.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="406.43" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (3,168,379,243 samples, 0.03%)</title><rect x="357.3" y="325" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="360.28" y="335.5" ></text>
</g>
<g >
<title>heap_update (825,460,253 samples, 0.01%)</title><rect x="1150.2" y="757" width="0.1" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="1153.21" y="767.5" ></text>
</g>
<g >
<title>cpus_share_cache (1,229,933,276 samples, 0.01%)</title><rect x="203.4" y="277" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="206.43" y="287.5" ></text>
</g>
<g >
<title>heap_page_prune_execute (2,054,534,112 samples, 0.02%)</title><rect x="1149.0" y="725" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1152.01" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,140,080,678 samples, 0.01%)</title><rect x="975.3" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="978.32" y="399.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (4,247,239,795 samples, 0.04%)</title><rect x="225.6" y="517" width="0.5" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="228.58" y="527.5" ></text>
</g>
<g >
<title>bms_add_member (3,669,928,262 samples, 0.04%)</title><rect x="857.1" y="325" width="0.5" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="860.10" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,070,381,988 samples, 0.01%)</title><rect x="921.2" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="924.25" y="399.5" ></text>
</g>
<g >
<title>pg_rewrite_query (99,262,457,719 samples, 1.04%)</title><rect x="858.5" y="565" width="12.3" height="15.0" fill="rgb(216,54,13)" rx="2" ry="2" />
<text  x="861.49" y="575.5" ></text>
</g>
<g >
<title>TransactionIdSetPageStatusInternal (12,043,732,127 samples, 0.13%)</title><rect x="479.1" y="453" width="1.5" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="482.13" y="463.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,294,382,926 samples, 0.02%)</title><rect x="1069.5" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1072.50" y="463.5" ></text>
</g>
<g >
<title>index_getnext_slot (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="405" width="6.8" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="98.68" y="415.5" ></text>
</g>
<g >
<title>CreateDestReceiver (5,316,237,883 samples, 0.06%)</title><rect x="212.7" y="581" width="0.6" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="215.68" y="591.5" ></text>
</g>
<g >
<title>palloc (2,967,741,231 samples, 0.03%)</title><rect x="1166.5" y="757" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1169.46" y="767.5" ></text>
</g>
<g >
<title>_bt_num_array_keys (2,841,645,866 samples, 0.03%)</title><rect x="84.3" y="293" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="87.32" y="303.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (4,687,329,473 samples, 0.05%)</title><rect x="126.3" y="277" width="0.6" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="129.31" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,227,315,458 samples, 0.01%)</title><rect x="814.4" y="405" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="817.36" y="415.5" ></text>
</g>
<g >
<title>make_one_rel (838,410,516 samples, 0.01%)</title><rect x="128.2" y="485" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="131.20" y="495.5" ></text>
</g>
<g >
<title>BufferIsLockedByMeInMode (1,685,110,652 samples, 0.02%)</title><rect x="393.8" y="325" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="396.76" y="335.5" ></text>
</g>
<g >
<title>get_relids_in_jointree (5,684,621,913 samples, 0.06%)</title><rect x="1069.1" y="485" width="0.7" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="1072.08" y="495.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,510,813,221 samples, 0.02%)</title><rect x="188.2" y="533" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="191.22" y="543.5" ></text>
</g>
<g >
<title>make_eq_member (7,427,329,380 samples, 0.08%)</title><rect x="969.3" y="373" width="0.9" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="972.28" y="383.5" ></text>
</g>
<g >
<title>core_yyalloc (1,497,654,808 samples, 0.02%)</title><rect x="872.5" y="517" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="875.49" y="527.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="709" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.62" y="719.5" ></text>
</g>
<g >
<title>hash_bytes (4,076,553,979 samples, 0.04%)</title><rect x="828.4" y="277" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="831.37" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,000,534,722 samples, 0.01%)</title><rect x="995.0" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="998.02" y="303.5" ></text>
</g>
<g >
<title>XLogRecordAssemble (3,770,086,481 samples, 0.04%)</title><rect x="513.9" y="469" width="0.5" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="516.92" y="479.5" ></text>
</g>
<g >
<title>pull_varattnos_walker (10,373,101,402 samples, 0.11%)</title><rect x="995.7" y="325" width="1.3" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="998.67" y="335.5" ></text>
</g>
<g >
<title>LWLockRelease (4,431,155,658 samples, 0.05%)</title><rect x="389.2" y="277" width="0.5" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="392.17" y="287.5" ></text>
</g>
<g >
<title>smgrDoPendingSyncs (1,452,716,477 samples, 0.02%)</title><rect x="1183.3" y="757" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="1186.35" y="767.5" ></text>
</g>
<g >
<title>file_remove_privs_flags (1,058,673,925 samples, 0.01%)</title><rect x="501.7" y="373" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="504.67" y="383.5" ></text>
</g>
<g >
<title>exec_simple_query (6,981,396,054,894 samples, 73.49%)</title><rect x="210.6" y="597" width="867.2" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="213.59" y="607.5" >exec_simple_query</text>
</g>
<g >
<title>bms_make_singleton (2,293,122,592 samples, 0.02%)</title><rect x="979.3" y="437" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="982.33" y="447.5" ></text>
</g>
<g >
<title>heap_prune_chain (5,284,379,906 samples, 0.06%)</title><rect x="1146.8" y="725" width="0.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1149.77" y="735.5" ></text>
</g>
<g >
<title>ResourceOwnerDelete (3,281,187,547 samples, 0.03%)</title><rect x="515.2" y="517" width="0.4" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="518.16" y="527.5" ></text>
</g>
<g >
<title>next_pow2_int (1,296,307,913 samples, 0.01%)</title><rect x="1063.3" y="437" width="0.2" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="1066.33" y="447.5" ></text>
</g>
<g >
<title>LWLockQueueSelf (2,621,845,496 samples, 0.03%)</title><rect x="482.1" y="469" width="0.3" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="485.06" y="479.5" ></text>
</g>
<g >
<title>security_socket_recvmsg (5,158,559,313 samples, 0.05%)</title><rect x="180.3" y="405" width="0.6" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="183.28" y="415.5" ></text>
</g>
<g >
<title>SearchSysCacheExists (8,163,922,712 samples, 0.09%)</title><rect x="1020.9" y="293" width="1.0" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="1023.90" y="303.5" ></text>
</g>
<g >
<title>palloc (1,262,111,043 samples, 0.01%)</title><rect x="897.9" y="437" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="900.89" y="447.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,201,615,804 samples, 0.01%)</title><rect x="385.2" y="309" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="388.24" y="319.5" ></text>
</g>
<g >
<title>XLogCheckBufferNeedsBackup (832,974,021 samples, 0.01%)</title><rect x="110.7" y="757" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="113.68" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,967,741,231 samples, 0.03%)</title><rect x="1166.5" y="741" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1169.46" y="751.5" ></text>
</g>
<g >
<title>namestrcmp (1,705,632,157 samples, 0.02%)</title><rect x="834.4" y="453" width="0.2" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="837.41" y="463.5" ></text>
</g>
<g >
<title>perf_event_context_sched_out (8,983,422,492 samples, 0.09%)</title><rect x="165.4" y="309" width="1.1" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="168.40" y="319.5" ></text>
</g>
<g >
<title>assign_collations_walker (26,366,934,824 samples, 0.28%)</title><rect x="808.8" y="405" width="3.3" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="811.81" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,229,194,794 samples, 0.01%)</title><rect x="953.4" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="956.40" y="399.5" ></text>
</g>
<g >
<title>_bt_compare (1,304,462,264 samples, 0.01%)</title><rect x="125.6" y="261" width="0.2" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="128.60" y="271.5" ></text>
</g>
<g >
<title>newNode (3,942,669,968 samples, 0.04%)</title><rect x="812.4" y="485" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="815.44" y="495.5" ></text>
</g>
<g >
<title>HeapTupleHeaderGetXmin (4,325,437,220 samples, 0.05%)</title><rect x="304.8" y="245" width="0.5" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="307.78" y="255.5" ></text>
</g>
<g >
<title>fireBSTriggers (1,775,599,002 samples, 0.02%)</title><rect x="404.5" y="437" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="407.50" y="447.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,122,214,421 samples, 0.01%)</title><rect x="307.8" y="181" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="310.79" y="191.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (3,324,985,075 samples, 0.04%)</title><rect x="508.3" y="437" width="0.5" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="511.34" y="447.5" ></text>
</g>
<g >
<title>extract_restriction_or_clauses (1,503,123,750 samples, 0.02%)</title><rect x="1137.6" y="757" width="0.2" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="1140.63" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (7,043,705,875 samples, 0.07%)</title><rect x="946.0" y="389" width="0.9" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="949.00" y="399.5" ></text>
</g>
<g >
<title>get_tablespace (3,674,270,332 samples, 0.04%)</title><rect x="1012.3" y="277" width="0.4" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1015.27" y="287.5" ></text>
</g>
<g >
<title>AllocSetCheck (36,303,513,052 samples, 0.38%)</title><rect x="255.3" y="437" width="4.5" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="258.29" y="447.5" ></text>
</g>
<g >
<title>selinux_socket_recvmsg (4,191,886,905 samples, 0.04%)</title><rect x="180.4" y="389" width="0.5" height="15.0" fill="rgb(227,104,25)" rx="2" ry="2" />
<text  x="183.40" y="399.5" ></text>
</g>
<g >
<title>index_open (15,749,765,441 samples, 0.17%)</title><rect x="447.8" y="421" width="1.9" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="450.77" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,317,244,526 samples, 0.01%)</title><rect x="459.6" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="462.58" y="463.5" ></text>
</g>
<g >
<title>IsTransactionOrTransactionBlock (1,212,794,791 samples, 0.01%)</title><rect x="55.3" y="757" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="58.33" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,775,193,917 samples, 0.03%)</title><rect x="1045.9" y="421" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1048.92" y="431.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,647,775,217 samples, 0.04%)</title><rect x="1055.5" y="389" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1058.48" y="399.5" ></text>
</g>
<g >
<title>list_make1_impl (1,059,273,152 samples, 0.01%)</title><rect x="1157.6" y="757" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1160.55" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (8,424,414,636 samples, 0.09%)</title><rect x="430.9" y="341" width="1.0" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="433.87" y="351.5" ></text>
</g>
<g >
<title>lcons (2,240,952,160 samples, 0.02%)</title><rect x="422.8" y="373" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="425.77" y="383.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,469,870,638 samples, 0.02%)</title><rect x="847.1" y="373" width="0.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="850.09" y="383.5" ></text>
</g>
<g >
<title>addRTEPermissionInfo (6,796,404,007 samples, 0.07%)</title><rect x="814.8" y="453" width="0.9" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="817.84" y="463.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,503,333,425 samples, 0.05%)</title><rect x="481.5" y="469" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="484.46" y="479.5" ></text>
</g>
<g >
<title>index_getnext_tid (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="357" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1161.90" y="367.5" ></text>
</g>
<g >
<title>selinux_socket_getpeersec_dgram (2,497,980,271 samples, 0.03%)</title><rect x="195.1" y="389" width="0.3" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="198.14" y="399.5" ></text>
</g>
<g >
<title>skb_copy_datagram_from_iter (8,484,190,857 samples, 0.09%)</title><rect x="195.4" y="405" width="1.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="198.45" y="415.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,416,697,638 samples, 0.01%)</title><rect x="478.1" y="373" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="481.14" y="383.5" ></text>
</g>
<g >
<title>extract_nonindex_conditions (3,598,051,094 samples, 0.04%)</title><rect x="1015.2" y="325" width="0.4" height="15.0" fill="rgb(216,54,13)" rx="2" ry="2" />
<text  x="1018.16" y="335.5" ></text>
</g>
<g >
<title>lappend (3,287,765,258 samples, 0.03%)</title><rect x="105.4" y="437" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="108.41" y="447.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,126,947,685 samples, 0.01%)</title><rect x="444.0" y="309" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="446.97" y="319.5" ></text>
</g>
<g >
<title>RelationClose (1,232,126,621 samples, 0.01%)</title><rect x="1066.1" y="453" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="1069.08" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,359,342,132 samples, 0.04%)</title><rect x="426.2" y="245" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="429.22" y="255.5" ></text>
</g>
<g >
<title>_bt_getroot (3,477,463,344 samples, 0.04%)</title><rect x="125.2" y="277" width="0.4" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="128.17" y="287.5" ></text>
</g>
<g >
<title>newNode (2,451,691,344 samples, 0.03%)</title><rect x="835.7" y="421" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="838.71" y="431.5" ></text>
</g>
<g >
<title>pgstat_get_transactional_drops (3,466,717,265 samples, 0.04%)</title><rect x="514.5" y="501" width="0.5" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="517.54" y="511.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (5,790,144,553 samples, 0.06%)</title><rect x="1052.6" y="437" width="0.7" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1055.61" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,261,922,566 samples, 0.02%)</title><rect x="974.9" y="373" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="977.88" y="383.5" ></text>
</g>
<g >
<title>palloc0 (2,627,765,467 samples, 0.03%)</title><rect x="103.3" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="106.30" y="463.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,074,843,915 samples, 0.01%)</title><rect x="354.2" y="277" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="357.24" y="287.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (169,943,536,453 samples, 1.79%)</title><rect x="679.4" y="533" width="21.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="682.45" y="543.5" ></text>
</g>
<g >
<title>PreCommit_on_commit_actions (1,154,551,256 samples, 0.01%)</title><rect x="87.3" y="757" width="0.2" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="90.34" y="767.5" ></text>
</g>
<g >
<title>update_process_times (810,158,057 samples, 0.01%)</title><rect x="700.4" y="421" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="703.41" y="431.5" ></text>
</g>
<g >
<title>sched_balance_update_blocked_averages (1,097,912,714 samples, 0.01%)</title><rect x="757.8" y="453" width="0.1" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="760.77" y="463.5" ></text>
</g>
<g >
<title>PortalDrop (39,182,832,091 samples, 0.41%)</title><rect x="224.8" y="581" width="4.9" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="227.84" y="591.5" ></text>
</g>
<g >
<title>ModifyWaitEvent (1,566,064,201 samples, 0.02%)</title><rect x="153.5" y="517" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="156.53" y="527.5" ></text>
</g>
<g >
<title>VARSIZE_SHORT (805,251,287 samples, 0.01%)</title><rect x="110.0" y="757" width="0.1" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="113.03" y="767.5" ></text>
</g>
<g >
<title>get_timeout_active (1,093,749,772 samples, 0.01%)</title><rect x="802.1" y="549" width="0.2" height="15.0" fill="rgb(254,225,54)" rx="2" ry="2" />
<text  x="805.12" y="559.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (865,415,340 samples, 0.01%)</title><rect x="862.3" y="469" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="865.25" y="479.5" ></text>
</g>
<g >
<title>new_list (1,251,496,353 samples, 0.01%)</title><rect x="971.4" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="974.44" y="383.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,041,661,865 samples, 0.01%)</title><rect x="478.7" y="437" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="481.66" y="447.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (36,569,013,574 samples, 0.38%)</title><rect x="796.3" y="549" width="4.6" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="799.33" y="559.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,161,846,794 samples, 0.01%)</title><rect x="299.0" y="197" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="302.02" y="207.5" ></text>
</g>
<g >
<title>AfterTriggerFireDeferred (1,409,490,905 samples, 0.01%)</title><rect x="467.3" y="517" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="470.30" y="527.5" ></text>
</g>
<g >
<title>hash_bytes (2,474,999,291 samples, 0.03%)</title><rect x="1067.4" y="389" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1070.38" y="399.5" ></text>
</g>
<g >
<title>AllocSetFree (1,100,266,504 samples, 0.01%)</title><rect x="944.4" y="357" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="947.39" y="367.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,297,946,916 samples, 0.05%)</title><rect x="863.8" y="485" width="0.6" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="866.82" y="495.5" ></text>
</g>
<g >
<title>postmaster_child_launch (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="677" width="2.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="126.99" y="687.5" ></text>
</g>
<g >
<title>castNodeImpl (7,397,557,796 samples, 0.08%)</title><rect x="1124.8" y="757" width="0.9" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="1127.79" y="767.5" ></text>
</g>
<g >
<title>ShowTransactionState (1,028,394,196 samples, 0.01%)</title><rect x="530.1" y="517" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="533.09" y="527.5" ></text>
</g>
<g >
<title>pg_client_to_server (13,423,486,741 samples, 0.14%)</title><rect x="1080.2" y="581" width="1.6" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1083.16" y="591.5" ></text>
</g>
<g >
<title>try_to_wake_up (19,469,879,792 samples, 0.20%)</title><rect x="492.6" y="309" width="2.4" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="495.61" y="319.5" ></text>
</g>
<g >
<title>lappend_oid (2,075,896,047 samples, 0.02%)</title><rect x="897.8" y="469" width="0.3" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="900.80" y="479.5" ></text>
</g>
<g >
<title>do_futex (56,019,577,849 samples, 0.59%)</title><rect x="483.1" y="373" width="7.0" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="486.11" y="383.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,917,438,911 samples, 0.02%)</title><rect x="461.6" y="517" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="464.57" y="527.5" ></text>
</g>
<g >
<title>BufferGetPage (1,142,290,462 samples, 0.01%)</title><rect x="311.8" y="245" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="314.80" y="255.5" ></text>
</g>
<g >
<title>StartReadBuffer (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="245" width="2.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="103.12" y="255.5" ></text>
</g>
<g >
<title>extract_update_targetlist_colnos (4,419,778,207 samples, 0.05%)</title><rect x="922.2" y="469" width="0.6" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="925.23" y="479.5" ></text>
</g>
<g >
<title>palloc (1,329,638,788 samples, 0.01%)</title><rect x="818.6" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="821.62" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,220,933,718 samples, 0.02%)</title><rect x="236.6" y="501" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="239.56" y="511.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,640,180,757 samples, 0.05%)</title><rect x="902.7" y="357" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="905.67" y="367.5" ></text>
</g>
<g >
<title>pgstat_clear_snapshot (1,114,257,497 samples, 0.01%)</title><rect x="1171.5" y="757" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="1174.46" y="767.5" ></text>
</g>
<g >
<title>new_list (2,171,099,031 samples, 0.02%)</title><rect x="457.6" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="460.64" y="447.5" ></text>
</g>
<g >
<title>select_task_rq (2,595,754,308 samples, 0.03%)</title><rect x="494.3" y="293" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="497.28" y="303.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,462,468,590 samples, 0.02%)</title><rect x="453.2" y="341" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="456.18" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,579,653,355 samples, 0.02%)</title><rect x="1053.1" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1056.12" y="399.5" ></text>
</g>
<g >
<title>update_load_avg (4,876,690,983 samples, 0.05%)</title><rect x="488.1" y="213" width="0.6" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="491.07" y="223.5" ></text>
</g>
<g >
<title>index_getnext_tid (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="325" width="1.9" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="129.31" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberSnapshot (1,581,593,361 samples, 0.02%)</title><rect x="236.2" y="485" width="0.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="239.24" y="495.5" ></text>
</g>
<g >
<title>ReindexIsProcessingIndex (1,859,989,349 samples, 0.02%)</title><rect x="89.1" y="757" width="0.3" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="92.12" y="767.5" ></text>
</g>
<g >
<title>AllocSetDelete (7,679,477,701 samples, 0.08%)</title><rect x="252.2" y="437" width="1.0" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="255.24" y="447.5" ></text>
</g>
<g >
<title>ReadBufferExtended (22,879,234,343 samples, 0.24%)</title><rect x="407.0" y="373" width="2.9" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="410.03" y="383.5" ></text>
</g>
<g >
<title>get_futex_key (3,517,670,368 samples, 0.04%)</title><rect x="492.1" y="325" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="495.12" y="335.5" ></text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (4,247,516,880 samples, 0.04%)</title><rect x="967.2" y="357" width="0.5" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="970.15" y="367.5" ></text>
</g>
<g >
<title>tag_hash (2,975,687,136 samples, 0.03%)</title><rect x="239.4" y="389" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="242.41" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,323,916,233 samples, 0.01%)</title><rect x="234.4" y="517" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="237.45" y="527.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,132,954,763 samples, 0.01%)</title><rect x="836.9" y="341" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="839.86" y="351.5" ></text>
</g>
<g >
<title>palloc0 (3,157,248,767 samples, 0.03%)</title><rect x="1016.5" y="325" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1019.51" y="335.5" ></text>
</g>
<g >
<title>bms_difference (2,450,600,563 samples, 0.03%)</title><rect x="883.2" y="517" width="0.3" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="886.19" y="527.5" ></text>
</g>
<g >
<title>tag_hash (3,399,482,524 samples, 0.04%)</title><rect x="862.9" y="421" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="865.89" y="431.5" ></text>
</g>
<g >
<title>initStringInfoInternal (3,979,722,288 samples, 0.04%)</title><rect x="1077.9" y="581" width="0.5" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1080.89" y="591.5" ></text>
</g>
<g >
<title>update_curr (811,145,682 samples, 0.01%)</title><rect x="206.7" y="261" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="209.67" y="271.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (860,219,683 samples, 0.01%)</title><rect x="819.9" y="405" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="822.90" y="415.5" ></text>
</g>
<g >
<title>remove_entity_load_avg (2,499,879,702 samples, 0.03%)</title><rect x="206.1" y="293" width="0.3" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="209.12" y="303.5" ></text>
</g>
<g >
<title>index_endscan (61,138,221,938 samples, 0.64%)</title><rect x="241.0" y="421" width="7.6" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="244.04" y="431.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,437,127,128 samples, 0.02%)</title><rect x="1148.3" y="613" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1151.29" y="623.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,014,988,348 samples, 0.02%)</title><rect x="834.7" y="453" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="837.68" y="463.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,572,164,823 samples, 0.03%)</title><rect x="250.5" y="421" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="253.49" y="431.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (3,120,520,447 samples, 0.03%)</title><rect x="1060.4" y="437" width="0.3" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1063.36" y="447.5" ></text>
</g>
<g >
<title>PageRepairFragmentation (1,817,068,179 samples, 0.02%)</title><rect x="1146.4" y="725" width="0.2" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="1149.36" y="735.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,262,946,556 samples, 0.03%)</title><rect x="124.8" y="213" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="127.77" y="223.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,014,057,584 samples, 0.02%)</title><rect x="37.9" y="757" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="40.91" y="767.5" ></text>
</g>
<g >
<title>ExecReadyExpr (6,779,093,045 samples, 0.07%)</title><rect x="429.3" y="357" width="0.8" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="432.29" y="367.5" ></text>
</g>
<g >
<title>table_slot_callbacks (1,001,258,981 samples, 0.01%)</title><rect x="281.4" y="405" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="284.36" y="415.5" ></text>
</g>
<g >
<title>calc_bucket (1,054,624,034 samples, 0.01%)</title><rect x="524.5" y="389" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="527.53" y="399.5" ></text>
</g>
<g >
<title>pfree (2,783,058,499 samples, 0.03%)</title><rect x="189.4" y="565" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="192.44" y="575.5" ></text>
</g>
<g >
<title>make_pathtarget_from_tlist (8,501,297,821 samples, 0.09%)</title><rect x="919.3" y="485" width="1.0" height="15.0" fill="rgb(213,37,9)" rx="2" ry="2" />
<text  x="922.27" y="495.5" ></text>
</g>
<g >
<title>__pick_next_task (6,084,801,312 samples, 0.06%)</title><rect x="483.9" y="277" width="0.8" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="486.94" y="287.5" ></text>
</g>
<g >
<title>palloc0 (1,656,215,987 samples, 0.02%)</title><rect x="995.0" y="309" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="997.95" y="319.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (2,945,262,746 samples, 0.03%)</title><rect x="795.7" y="453" width="0.4" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="798.73" y="463.5" ></text>
</g>
<g >
<title>__futex_wait (1,495,688,324 samples, 0.02%)</title><rect x="1148.5" y="485" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1151.51" y="495.5" ></text>
</g>
<g >
<title>LockAcquireExtended (28,702,703,826 samples, 0.30%)</title><rect x="821.2" y="389" width="3.5" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="824.16" y="399.5" ></text>
</g>
<g >
<title>cost_bitmap_heap_scan (9,764,122,749 samples, 0.10%)</title><rect x="988.7" y="373" width="1.2" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="991.68" y="383.5" ></text>
</g>
<g >
<title>newNode (3,361,901,701 samples, 0.04%)</title><rect x="817.5" y="421" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="820.54" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,035,946,103 samples, 0.01%)</title><rect x="945.0" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="948.04" y="383.5" ></text>
</g>
<g >
<title>table_openrv_extended (105,249,311,606 samples, 1.11%)</title><rect x="820.3" y="453" width="13.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="823.31" y="463.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (5,723,253,840 samples, 0.06%)</title><rect x="366.6" y="277" width="0.7" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="369.56" y="287.5" ></text>
</g>
<g >
<title>initStringInfo (4,165,784,888 samples, 0.04%)</title><rect x="1077.9" y="597" width="0.5" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="1080.86" y="607.5" ></text>
</g>
<g >
<title>sock_alloc_send_pskb (32,222,117,747 samples, 0.34%)</title><rect x="196.5" y="405" width="4.0" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="199.51" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,955,203,653 samples, 0.02%)</title><rect x="239.2" y="389" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="242.17" y="399.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,145,351,918 samples, 0.01%)</title><rect x="84.8" y="213" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="87.84" y="223.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (5,920,628,837 samples, 0.06%)</title><rect x="757.9" y="501" width="0.7" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="760.91" y="511.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (1,220,476,765 samples, 0.01%)</title><rect x="125.8" y="261" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="128.76" y="271.5" ></text>
</g>
<g >
<title>tag_hash (2,168,652,806 samples, 0.02%)</title><rect x="948.2" y="325" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="951.25" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (964,236,396 samples, 0.01%)</title><rect x="866.6" y="453" width="0.2" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="869.64" y="463.5" ></text>
</g>
<g >
<title>tag_hash (7,479,983,999 samples, 0.08%)</title><rect x="524.7" y="421" width="0.9" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="527.67" y="431.5" ></text>
</g>
<g >
<title>preprocess_function_rtes (1,438,560,793 samples, 0.02%)</title><rect x="1053.6" y="501" width="0.2" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="1056.59" y="511.5" ></text>
</g>
<g >
<title>LockRelease (1,150,501,189 samples, 0.01%)</title><rect x="59.3" y="757" width="0.1" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="62.27" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,738,703,109 samples, 0.04%)</title><rect x="1148.3" y="629" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1151.25" y="639.5" ></text>
</g>
<g >
<title>ServerLoop (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="741" width="2.8" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="87.32" y="751.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (2,922,522,312 samples, 0.03%)</title><rect x="478.1" y="437" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="481.07" y="447.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableFullXid (1,756,993,529 samples, 0.02%)</title><rect x="312.4" y="229" width="0.2" height="15.0" fill="rgb(232,128,30)" rx="2" ry="2" />
<text  x="315.35" y="239.5" ></text>
</g>
<g >
<title>SearchSysCacheExists (862,481,638 samples, 0.01%)</title><rect x="95.3" y="757" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="98.26" y="767.5" ></text>
</g>
<g >
<title>pgstat_report_query_id (1,560,068,935 samples, 0.02%)</title><rect x="1071.9" y="581" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="1074.94" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,412,965,046 samples, 0.01%)</title><rect x="1023.8" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1026.77" y="367.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,270,816,429 samples, 0.01%)</title><rect x="970.0" y="309" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="972.98" y="319.5" ></text>
</g>
<g >
<title>hash_bytes (4,130,705,565 samples, 0.04%)</title><rect x="868.8" y="421" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="871.76" y="431.5" ></text>
</g>
<g >
<title>standard_ExecutorEnd (232,055,080,763 samples, 2.44%)</title><rect x="236.9" y="517" width="28.9" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="239.94" y="527.5" >st..</text>
</g>
<g >
<title>ReleaseSysCache (1,187,208,003 samples, 0.01%)</title><rect x="836.9" y="357" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="839.86" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,882,833,711 samples, 0.04%)</title><rect x="917.2" y="373" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="920.15" y="383.5" ></text>
</g>
<g >
<title>ExecInitExprRec (1,103,708,270 samples, 0.01%)</title><rect x="43.6" y="757" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="46.63" y="767.5" ></text>
</g>
<g >
<title>ScanKeyEntryInitialize (2,260,580,554 samples, 0.02%)</title><rect x="434.4" y="405" width="0.3" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="437.41" y="415.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,728,716,251 samples, 0.05%)</title><rect x="233.9" y="517" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="236.86" y="527.5" ></text>
</g>
<g >
<title>hash_bytes (4,558,271,014 samples, 0.05%)</title><rect x="452.5" y="309" width="0.6" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="455.53" y="319.5" ></text>
</g>
<g >
<title>new_list (2,215,514,251 samples, 0.02%)</title><rect x="911.3" y="453" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="914.32" y="463.5" ></text>
</g>
<g >
<title>kfree (7,570,888,086 samples, 0.08%)</title><rect x="182.0" y="341" width="1.0" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="185.04" y="351.5" ></text>
</g>
<g >
<title>newNode (4,050,132,211 samples, 0.04%)</title><rect x="1115.6" y="709" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1118.60" y="719.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,968,241,020 samples, 0.03%)</title><rect x="867.2" y="421" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="870.18" y="431.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (3,926,156,190 samples, 0.04%)</title><rect x="389.2" y="261" width="0.5" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="392.23" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,055,398,598 samples, 0.01%)</title><rect x="345.4" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="348.43" y="351.5" ></text>
</g>
<g >
<title>LWLockRelease (1,226,026,173 samples, 0.01%)</title><rect x="327.1" y="181" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="330.08" y="191.5" ></text>
</g>
<g >
<title>btgettuple (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="341" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1161.90" y="351.5" ></text>
</g>
<g >
<title>hash_bytes (3,289,191,641 samples, 0.03%)</title><rect x="862.9" y="405" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="865.90" y="415.5" ></text>
</g>
<g >
<title>ExecCloseRangeTableRelations (2,884,013,671 samples, 0.03%)</title><rect x="237.4" y="485" width="0.4" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="240.41" y="495.5" ></text>
</g>
<g >
<title>avc_lookup (2,376,305,533 samples, 0.03%)</title><rect x="194.1" y="357" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="197.05" y="367.5" ></text>
</g>
<g >
<title>bms_add_member (2,800,520,650 samples, 0.03%)</title><rect x="277.0" y="405" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="279.95" y="415.5" ></text>
</g>
<g >
<title>generate_base_implied_equalities (1,318,840,310 samples, 0.01%)</title><rect x="1140.2" y="757" width="0.1" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="1143.16" y="767.5" ></text>
</g>
<g >
<title>new_list (1,878,697,773 samples, 0.02%)</title><rect x="1014.4" y="293" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1017.36" y="303.5" ></text>
</g>
<g >
<title>list_free (1,732,324,943 samples, 0.02%)</title><rect x="953.9" y="453" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="956.91" y="463.5" ></text>
</g>
<g >
<title>LockRelationOid (9,349,424,074 samples, 0.10%)</title><rect x="401.6" y="357" width="1.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="404.63" y="367.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (3,790,276,814 samples, 0.04%)</title><rect x="99.7" y="181" width="0.4" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="102.65" y="191.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u64 (1,113,383,833 samples, 0.01%)</title><rect x="497.3" y="453" width="0.2" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="500.34" y="463.5" ></text>
</g>
<g >
<title>__errno_location (1,469,933,128 samples, 0.02%)</title><rect x="191.3" y="501" width="0.1" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="194.26" y="511.5" ></text>
</g>
<g >
<title>exprType (1,333,759,840 samples, 0.01%)</title><rect x="430.4" y="357" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="433.37" y="367.5" ></text>
</g>
<g >
<title>list_nth (1,109,537,172 samples, 0.01%)</title><rect x="447.6" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="450.62" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (839,021,855 samples, 0.01%)</title><rect x="241.8" y="373" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="244.77" y="383.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,251,756,595 samples, 0.01%)</title><rect x="299.0" y="213" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="302.01" y="223.5" ></text>
</g>
<g >
<title>newNode (5,894,889,773 samples, 0.06%)</title><rect x="1119.8" y="741" width="0.8" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1122.84" y="751.5" ></text>
</g>
<g >
<title>bms_next_member (1,980,612,201 samples, 0.02%)</title><rect x="979.7" y="453" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="982.67" y="463.5" ></text>
</g>
<g >
<title>oper (22,492,685,916 samples, 0.24%)</title><rect x="838.2" y="373" width="2.7" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="841.16" y="383.5" ></text>
</g>
<g >
<title>_bt_moveright (1,409,295,037 samples, 0.01%)</title><rect x="128.0" y="261" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="130.99" y="271.5" ></text>
</g>
<g >
<title>futex_wake (1,305,666,798 samples, 0.01%)</title><rect x="478.2" y="309" width="0.1" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="481.15" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,279,315,481 samples, 0.01%)</title><rect x="1067.9" y="421" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1070.92" y="431.5" ></text>
</g>
<g >
<title>palloc (915,278,355 samples, 0.01%)</title><rect x="920.2" y="469" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="923.21" y="479.5" ></text>
</g>
<g >
<title>bms_add_members (4,401,299,626 samples, 0.05%)</title><rect x="950.8" y="437" width="0.6" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="953.82" y="447.5" ></text>
</g>
<g >
<title>IndexNext (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="421" width="6.8" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="98.68" y="431.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (3,493,545,851 samples, 0.04%)</title><rect x="367.8" y="229" width="0.4" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="370.80" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,353,800,544 samples, 0.01%)</title><rect x="345.7" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="348.71" y="351.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,357,161,805 samples, 0.01%)</title><rect x="341.4" y="197" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="344.38" y="207.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,511,578,029 samples, 0.02%)</title><rect x="353.5" y="325" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="356.54" y="335.5" ></text>
</g>
<g >
<title>newNode (3,408,809,301 samples, 0.04%)</title><rect x="103.2" y="469" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="106.20" y="479.5" ></text>
</g>
<g >
<title>BufferGetPage (1,195,037,619 samples, 0.01%)</title><rect x="328.6" y="229" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="331.61" y="239.5" ></text>
</g>
<g >
<title>index_beginscan_internal (24,602,440,588 samples, 0.26%)</title><rect x="292.6" y="309" width="3.0" height="15.0" fill="rgb(252,217,52)" rx="2" ry="2" />
<text  x="295.57" y="319.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,896,858,983 samples, 0.03%)</title><rect x="422.4" y="373" width="0.3" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="425.38" y="383.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (1,005,548,752 samples, 0.01%)</title><rect x="90.3" y="757" width="0.2" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="93.35" y="767.5" ></text>
</g>
<g >
<title>fix_expr_common (3,653,011,601 samples, 0.04%)</title><rect x="903.7" y="437" width="0.5" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="906.70" y="447.5" ></text>
</g>
<g >
<title>OidFunctionCall4Coll (812,916,542 samples, 0.01%)</title><rect x="80.9" y="757" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="83.89" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,365,209,069 samples, 0.05%)</title><rect x="415.4" y="405" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="418.44" y="415.5" ></text>
</g>
<g >
<title>_bt_steppage (7,280,356,717 samples, 0.08%)</title><rect x="283.3" y="277" width="0.9" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="286.27" y="287.5" ></text>
</g>
<g >
<title>ReadBufferExtended (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="261" width="0.5" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="89.39" y="271.5" ></text>
</g>
<g >
<title>LWLockRelease (993,047,267 samples, 0.01%)</title><rect x="941.7" y="341" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="944.73" y="351.5" ></text>
</g>
<g >
<title>TupleDescAttr (2,576,767,360 samples, 0.03%)</title><rect x="1003.7" y="165" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1006.66" y="175.5" ></text>
</g>
<g >
<title>LockHeldByMe (1,117,642,159 samples, 0.01%)</title><rect x="58.8" y="757" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="61.85" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,114,544,618 samples, 0.01%)</title><rect x="910.4" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="913.43" y="431.5" ></text>
</g>
<g >
<title>lappend (2,556,490,731 samples, 0.03%)</title><rect x="859.9" y="517" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="862.90" y="527.5" ></text>
</g>
<g >
<title>__virt_addr_valid (1,103,515,581 samples, 0.01%)</title><rect x="186.7" y="277" width="0.1" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="189.67" y="287.5" ></text>
</g>
<g >
<title>schedule (1,124,399,590 samples, 0.01%)</title><rect x="177.1" y="421" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="180.09" y="431.5" ></text>
</g>
<g >
<title>heap_page_prune_and_freeze (19,655,044,252 samples, 0.21%)</title><rect x="1146.4" y="757" width="2.4" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1149.36" y="767.5" ></text>
</g>
<g >
<title>available_idle_cpu (874,037,622 samples, 0.01%)</title><rect x="203.3" y="277" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="206.32" y="287.5" ></text>
</g>
<g >
<title>pstrdup (2,501,076,316 samples, 0.03%)</title><rect x="819.1" y="437" width="0.3" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="822.07" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,148,975,165 samples, 0.01%)</title><rect x="234.0" y="485" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="236.99" y="495.5" ></text>
</g>
<g >
<title>HeapTupleHeaderSetCmin (891,257,308 samples, 0.01%)</title><rect x="375.0" y="357" width="0.1" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="378.03" y="367.5" ></text>
</g>
<g >
<title>LockErrorCleanup (1,395,368,622 samples, 0.01%)</title><rect x="516.1" y="469" width="0.2" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="519.10" y="479.5" ></text>
</g>
<g >
<title>table_close (1,800,970,999 samples, 0.02%)</title><rect x="1066.0" y="485" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="1069.02" y="495.5" ></text>
</g>
<g >
<title>palloc (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="309" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="89.91" y="319.5" ></text>
</g>
<g >
<title>index_getnext_slot (373,200,660,108 samples, 3.93%)</title><rect x="296.0" y="325" width="46.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="299.00" y="335.5" >inde..</text>
</g>
<g >
<title>pg_parse_query (25,454,919,217 samples, 0.27%)</title><rect x="870.8" y="581" width="3.2" height="15.0" fill="rgb(209,18,4)" rx="2" ry="2" />
<text  x="873.82" y="591.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,552,856,632 samples, 0.02%)</title><rect x="128.7" y="757" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="131.74" y="767.5" ></text>
</g>
<g >
<title>int4eq (1,352,198,242 samples, 0.01%)</title><rect x="1152.1" y="757" width="0.2" height="15.0" fill="rgb(206,9,2)" rx="2" ry="2" />
<text  x="1155.10" y="767.5" ></text>
</g>
<g >
<title>PortalRun (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="613" width="1.9" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="126.99" y="623.5" ></text>
</g>
<g >
<title>fmgr_info (1,216,890,669 samples, 0.01%)</title><rect x="434.5" y="389" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="437.54" y="399.5" ></text>
</g>
<g >
<title>palloc (1,231,937,294 samples, 0.01%)</title><rect x="1070.8" y="453" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1073.78" y="463.5" ></text>
</g>
<g >
<title>exprLocation (2,114,322,923 samples, 0.02%)</title><rect x="810.6" y="309" width="0.3" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="813.63" y="319.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,791,589,359 samples, 0.02%)</title><rect x="1148.5" y="581" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1151.50" y="591.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,920,521,076 samples, 0.04%)</title><rect x="97.2" y="165" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="100.21" y="175.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,324,462,737 samples, 0.01%)</title><rect x="522.6" y="421" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="525.57" y="431.5" ></text>
</g>
<g >
<title>futex_wake (26,591,944,975 samples, 0.28%)</title><rect x="491.7" y="341" width="3.3" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="494.73" y="351.5" ></text>
</g>
<g >
<title>RelationPutHeapTuple (30,517,267,698 samples, 0.32%)</title><rect x="378.7" y="357" width="3.8" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="381.71" y="367.5" ></text>
</g>
<g >
<title>ExecScanExtended (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="389" width="1.9" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="129.31" y="399.5" ></text>
</g>
<g >
<title>charhashfast (955,156,840 samples, 0.01%)</title><rect x="1032.8" y="149" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="1035.77" y="159.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="597" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.62" y="607.5" ></text>
</g>
<g >
<title>ExecCreateExprSetupSteps (19,087,440,569 samples, 0.20%)</title><rect x="424.3" y="357" width="2.4" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="427.28" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache (6,430,995,411 samples, 0.07%)</title><rect x="1021.1" y="277" width="0.8" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="1024.11" y="287.5" ></text>
</g>
<g >
<title>ExecEvalStepOp (7,385,925,519 samples, 0.08%)</title><rect x="269.9" y="341" width="0.9" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="272.86" y="351.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,304,037,044 samples, 0.05%)</title><rect x="1013.7" y="261" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1016.69" y="271.5" ></text>
</g>
<g >
<title>bms_add_member (2,141,060,282 samples, 0.02%)</title><rect x="967.3" y="325" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="970.32" y="335.5" ></text>
</g>
<g >
<title>LockRelationOid (32,080,485,456 samples, 0.34%)</title><rect x="821.0" y="405" width="4.0" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="824.00" y="415.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (5,153,671,973 samples, 0.05%)</title><rect x="108.8" y="757" width="0.6" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="111.75" y="767.5" ></text>
</g>
<g >
<title>StartReadBuffer (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="229" width="0.5" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="89.39" y="239.5" ></text>
</g>
<g >
<title>LWLockWakeup (34,185,353,139 samples, 0.36%)</title><rect x="491.1" y="453" width="4.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="494.09" y="463.5" ></text>
</g>
<g >
<title>PredicateLockPage (974,552,226 samples, 0.01%)</title><rect x="87.5" y="757" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="90.49" y="767.5" ></text>
</g>
<g >
<title>palloc (956,594,838 samples, 0.01%)</title><rect x="974.3" y="405" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="977.29" y="415.5" ></text>
</g>
<g >
<title>table_slot_create (24,774,420,965 samples, 0.26%)</title><rect x="278.4" y="421" width="3.1" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="281.41" y="431.5" ></text>
</g>
<g >
<title>syscall_return_via_sysret (1,765,807,666 samples, 0.02%)</title><rect x="177.6" y="469" width="0.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="180.56" y="479.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (808,341,922 samples, 0.01%)</title><rect x="292.9" y="293" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="295.94" y="303.5" ></text>
</g>
<g >
<title>avc_has_perm (2,908,291,418 samples, 0.03%)</title><rect x="194.0" y="389" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="196.99" y="399.5" ></text>
</g>
<g >
<title>GetNewTransactionId (32,393,267,991 samples, 0.34%)</title><rect x="364.5" y="325" width="4.0" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="367.51" y="335.5" ></text>
</g>
<g >
<title>dlist_is_empty (829,801,280 samples, 0.01%)</title><rect x="59.5" y="741" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="62.48" y="751.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (960,450,893 samples, 0.01%)</title><rect x="1171.7" y="757" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="1174.72" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,017,945,992 samples, 0.01%)</title><rect x="819.1" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="822.15" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberBuffer (1,001,025,620 samples, 0.01%)</title><rect x="409.2" y="261" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="412.15" y="271.5" ></text>
</g>
<g >
<title>StartTransactionCommand (36,679,316,687 samples, 0.39%)</title><rect x="1073.0" y="565" width="4.6" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1076.02" y="575.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,412,630,249 samples, 0.01%)</title><rect x="165.1" y="325" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="168.08" y="335.5" ></text>
</g>
<g >
<title>ExecEndModifyTable (67,713,679,545 samples, 0.71%)</title><rect x="240.3" y="469" width="8.4" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="243.26" y="479.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (3,635,318,000 samples, 0.04%)</title><rect x="1036.2" y="293" width="0.4" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="1039.15" y="303.5" ></text>
</g>
<g >
<title>wake_up_q (19,702,523,623 samples, 0.21%)</title><rect x="492.6" y="325" width="2.4" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="495.58" y="335.5" ></text>
</g>
<g >
<title>GrantLockLocal (1,238,606,875 samples, 0.01%)</title><rect x="369.4" y="277" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="372.40" y="287.5" ></text>
</g>
<g >
<title>dequeue_entity (15,942,957,838 samples, 0.17%)</title><rect x="486.8" y="229" width="1.9" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="489.75" y="239.5" ></text>
</g>
<g >
<title>int4hashfast (2,120,637,656 samples, 0.02%)</title><rect x="828.0" y="293" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="830.96" y="303.5" ></text>
</g>
<g >
<title>ReadBufferExtended (18,820,852,453 samples, 0.20%)</title><rect x="353.9" y="357" width="2.4" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="356.95" y="367.5" ></text>
</g>
<g >
<title>ReleaseSysCache (3,146,415,862 samples, 0.03%)</title><rect x="825.6" y="357" width="0.4" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="828.62" y="367.5" ></text>
</g>
<g >
<title>compute_bitmap_pages (3,362,577,762 samples, 0.04%)</title><rect x="989.1" y="357" width="0.4" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="992.06" y="367.5" ></text>
</g>
<g >
<title>palloc (3,990,442,079 samples, 0.04%)</title><rect x="357.2" y="341" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="360.19" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,742,334,118 samples, 0.03%)</title><rect x="97.4" y="149" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="100.35" y="159.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (1,042,896,149 samples, 0.01%)</title><rect x="370.2" y="229" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="373.22" y="239.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (1,138,581,373 samples, 0.01%)</title><rect x="873.8" y="501" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="876.79" y="511.5" ></text>
</g>
<g >
<title>query_planner (963,190,410,006 samples, 10.14%)</title><rect x="925.1" y="485" width="119.7" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="928.13" y="495.5" >query_planner</text>
</g>
<g >
<title>IndexNext (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="405" width="2.6" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="87.32" y="415.5" ></text>
</g>
<g >
<title>palloc (2,291,355,049 samples, 0.02%)</title><rect x="1053.0" y="405" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1056.04" y="415.5" ></text>
</g>
<g >
<title>VARATT_IS_EXTERNAL (1,046,413,027 samples, 0.01%)</title><rect x="109.7" y="757" width="0.2" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="112.74" y="767.5" ></text>
</g>
<g >
<title>send@plt (1,105,338,909 samples, 0.01%)</title><rect x="209.8" y="501" width="0.1" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="212.80" y="511.5" ></text>
</g>
<g >
<title>CommitTransactionCommand (526,259,410,775 samples, 5.54%)</title><rect x="465.6" y="565" width="65.3" height="15.0" fill="rgb(205,4,1)" rx="2" ry="2" />
<text  x="468.57" y="575.5" >CommitT..</text>
</g>
<g >
<title>ReleaseCatCache (1,119,325,385 samples, 0.01%)</title><rect x="104.0" y="437" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="106.97" y="447.5" ></text>
</g>
<g >
<title>_bt_check_compare (4,599,176,588 samples, 0.05%)</title><rect x="124.2" y="245" width="0.6" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="127.19" y="255.5" ></text>
</g>
<g >
<title>slot_attisnull (4,844,697,695 samples, 0.05%)</title><rect x="349.7" y="389" width="0.6" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="352.71" y="399.5" ></text>
</g>
<g >
<title>vruntime_eligible (987,295,502 samples, 0.01%)</title><rect x="174.3" y="277" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="177.34" y="287.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,133,976,642 samples, 0.01%)</title><rect x="475.6" y="405" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="478.58" y="415.5" ></text>
</g>
<g >
<title>lappend (3,219,592,861 samples, 0.03%)</title><rect x="1011.8" y="277" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1014.84" y="287.5" ></text>
</g>
<g >
<title>copyObjectImpl (4,297,512,527 samples, 0.05%)</title><rect x="888.8" y="341" width="0.6" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="891.82" y="351.5" ></text>
</g>
<g >
<title>hash_search (3,211,762,045 samples, 0.03%)</title><rect x="924.7" y="421" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="927.71" y="431.5" ></text>
</g>
<g >
<title>bms_add_members (3,256,051,842 samples, 0.03%)</title><rect x="878.6" y="485" width="0.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="881.63" y="495.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,232,056,034 samples, 0.01%)</title><rect x="825.8" y="293" width="0.2" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="828.84" y="303.5" ></text>
</g>
<g >
<title>__strlen_avx2 (836,122,364 samples, 0.01%)</title><rect x="214.6" y="517" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="217.62" y="527.5" ></text>
</g>
<g >
<title>newNode (4,792,628,272 samples, 0.05%)</title><rect x="944.6" y="405" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="947.57" y="415.5" ></text>
</g>
<g >
<title>get_index_paths (216,436,292,596 samples, 2.28%)</title><rect x="991.3" y="389" width="26.9" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="994.32" y="399.5" >g..</text>
</g>
<g >
<title>hash_bytes (2,397,582,789 samples, 0.03%)</title><rect x="448.6" y="325" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="451.58" y="335.5" ></text>
</g>
<g >
<title>_bt_fix_scankey_strategy (1,095,728,195 samples, 0.01%)</title><rect x="324.2" y="245" width="0.1" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="327.19" y="255.5" ></text>
</g>
<g >
<title>add_path (2,115,393,494 samples, 0.02%)</title><rect x="909.1" y="485" width="0.3" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="912.11" y="495.5" ></text>
</g>
<g >
<title>unix_stream_read_generic (46,092,188,540 samples, 0.49%)</title><rect x="181.1" y="389" width="5.7" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="184.10" y="399.5" ></text>
</g>
<g >
<title>ktime_get_coarse_real_ts64_mg (1,215,218,933 samples, 0.01%)</title><rect x="502.0" y="325" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="505.04" y="335.5" ></text>
</g>
<g >
<title>pollwake (59,866,983,005 samples, 0.63%)</title><rect x="201.0" y="357" width="7.4" height="15.0" fill="rgb(238,154,37)" rx="2" ry="2" />
<text  x="203.97" y="367.5" ></text>
</g>
<g >
<title>new_list (1,219,472,025 samples, 0.01%)</title><rect x="909.2" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="912.22" y="447.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (937,444,094 samples, 0.01%)</title><rect x="1071.6" y="565" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1074.61" y="575.5" ></text>
</g>
<g >
<title>SearchSysCache (25,626,045,781 samples, 0.27%)</title><rect x="826.0" y="357" width="3.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="829.04" y="367.5" ></text>
</g>
<g >
<title>PushActiveSnapshotWithLevel (9,283,627,367 samples, 0.10%)</title><rect x="463.5" y="565" width="1.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="466.49" y="575.5" ></text>
</g>
<g >
<title>ExecFetchSlotHeapTuple (950,846,379 samples, 0.01%)</title><rect x="43.1" y="757" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="46.11" y="767.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (2,057,683,763 samples, 0.02%)</title><rect x="757.3" y="469" width="0.3" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="760.35" y="479.5" ></text>
</g>
<g >
<title>table_index_fetch_reset (3,742,920,704 samples, 0.04%)</title><rect x="284.2" y="309" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="287.20" y="319.5" ></text>
</g>
<g >
<title>set_rel_size (127,412,889,253 samples, 1.34%)</title><rect x="1026.3" y="437" width="15.8" height="15.0" fill="rgb(243,175,42)" rx="2" ry="2" />
<text  x="1029.28" y="447.5" ></text>
</g>
<g >
<title>PopActiveSnapshot (4,248,790,197 samples, 0.04%)</title><rect x="234.8" y="549" width="0.5" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="237.79" y="559.5" ></text>
</g>
<g >
<title>sentinel_ok (910,601,769 samples, 0.01%)</title><rect x="243.7" y="357" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="246.71" y="367.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,132,900,839 samples, 0.01%)</title><rect x="299.0" y="165" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="302.03" y="175.5" ></text>
</g>
<g >
<title>propagate_entity_load_avg (1,569,357,349 samples, 0.02%)</title><rect x="174.0" y="261" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="176.97" y="271.5" ></text>
</g>
<g >
<title>IOContextForStrategy (875,214,546 samples, 0.01%)</title><rect x="53.1" y="757" width="0.1" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="56.13" y="767.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,466,910,755 samples, 0.02%)</title><rect x="443.8" y="309" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="446.79" y="319.5" ></text>
</g>
<g >
<title>newNode (2,881,448,044 samples, 0.03%)</title><rect x="1020.5" y="309" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1023.47" y="319.5" ></text>
</g>
<g >
<title>palloc (11,943,796,388 samples, 0.13%)</title><rect x="294.1" y="277" width="1.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="297.13" y="287.5" ></text>
</g>
<g >
<title>AllocSetReset (9,497,104,373 samples, 0.10%)</title><rect x="134.8" y="501" width="1.2" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="137.80" y="511.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,131,799,659 samples, 0.01%)</title><rect x="127.9" y="181" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="130.85" y="191.5" ></text>
</g>
<g >
<title>malloc (7,452,318,330 samples, 0.08%)</title><rect x="294.6" y="229" width="0.9" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="297.58" y="239.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (2,841,645,866 samples, 0.03%)</title><rect x="84.3" y="325" width="0.4" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="87.32" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerSort (1,141,456,518 samples, 0.01%)</title><rect x="228.3" y="533" width="0.1" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="231.30" y="543.5" ></text>
</g>
<g >
<title>palloc0 (3,048,576,034 samples, 0.03%)</title><rect x="934.7" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="937.66" y="367.5" ></text>
</g>
<g >
<title>psi_task_switch (8,738,820,336 samples, 0.09%)</title><rect x="167.3" y="341" width="1.1" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="170.31" y="351.5" ></text>
</g>
<g >
<title>socket_putmessage (2,430,021,455 samples, 0.03%)</title><rect x="1183.8" y="757" width="0.3" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="1186.84" y="767.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (960,272,963 samples, 0.01%)</title><rect x="201.9" y="309" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="204.87" y="319.5" ></text>
</g>
<g >
<title>int4hashfast (2,004,762,779 samples, 0.02%)</title><rect x="1152.4" y="757" width="0.3" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1155.43" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,057,936,202 samples, 0.02%)</title><rect x="526.9" y="437" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="529.88" y="447.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (866,383,336 samples, 0.01%)</title><rect x="940.1" y="341" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="943.11" y="351.5" ></text>
</g>
<g >
<title>__sigsetjmp (1,026,240,918 samples, 0.01%)</title><rect x="463.3" y="565" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="466.33" y="575.5" ></text>
</g>
<g >
<title>list_insert_nth (1,756,386,510 samples, 0.02%)</title><rect x="909.2" y="469" width="0.2" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="912.15" y="479.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,245,492,556 samples, 0.01%)</title><rect x="508.5" y="421" width="0.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="511.46" y="431.5" ></text>
</g>
<g >
<title>newNode (3,105,002,276 samples, 0.03%)</title><rect x="910.2" y="453" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="913.19" y="463.5" ></text>
</g>
<g >
<title>lappend (1,655,640,312 samples, 0.02%)</title><rect x="456.0" y="437" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="459.01" y="447.5" ></text>
</g>
<g >
<title>TransactionIdSetTreeStatus (26,445,719,303 samples, 0.28%)</title><rect x="477.4" y="485" width="3.2" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="480.36" y="495.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop (3,550,778,225 samples, 0.04%)</title><rect x="1054.3" y="469" width="0.4" height="15.0" fill="rgb(243,175,41)" rx="2" ry="2" />
<text  x="1057.28" y="479.5" ></text>
</g>
<g >
<title>LWLockDequeueSelf (1,395,366,970 samples, 0.01%)</title><rect x="366.2" y="293" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="369.18" y="303.5" ></text>
</g>
<g >
<title>palloc (3,374,849,495 samples, 0.04%)</title><rect x="293.7" y="261" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="296.71" y="271.5" ></text>
</g>
<g >
<title>LockHeldByMe (11,065,463,735 samples, 0.12%)</title><rect x="1066.3" y="437" width="1.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1069.31" y="447.5" ></text>
</g>
<g >
<title>query_planner (838,410,516 samples, 0.01%)</title><rect x="128.2" y="501" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="131.20" y="511.5" ></text>
</g>
<g >
<title>x64_sys_call (1,116,046,047 samples, 0.01%)</title><rect x="209.5" y="453" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="212.48" y="463.5" ></text>
</g>
<g >
<title>UnregisterSnapshot (2,350,632,165 samples, 0.02%)</title><rect x="265.4" y="501" width="0.3" height="15.0" fill="rgb(212,33,7)" rx="2" ry="2" />
<text  x="268.44" y="511.5" ></text>
</g>
<g >
<title>gup_fast_fallback (1,222,575,048 samples, 0.01%)</title><rect x="368.0" y="133" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="370.98" y="143.5" ></text>
</g>
<g >
<title>is_valid_ascii (834,119,446 samples, 0.01%)</title><rect x="1154.1" y="757" width="0.1" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1157.15" y="767.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (928,310,150 samples, 0.01%)</title><rect x="305.6" y="197" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="308.63" y="207.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,203,054,071 samples, 0.01%)</title><rect x="214.4" y="517" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="217.41" y="527.5" ></text>
</g>
<g >
<title>op_mergejoinable (6,342,979,552 samples, 0.07%)</title><rect x="963.4" y="389" width="0.8" height="15.0" fill="rgb(248,197,47)" rx="2" ry="2" />
<text  x="966.39" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,397,061,570 samples, 0.03%)</title><rect x="1051.6" y="373" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1054.57" y="383.5" ></text>
</g>
<g >
<title>new_list (1,676,573,773 samples, 0.02%)</title><rect x="1047.4" y="485" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1050.41" y="495.5" ></text>
</g>
<g >
<title>__x64_sys_futex (2,392,798,823 samples, 0.03%)</title><rect x="375.9" y="229" width="0.3" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="378.86" y="239.5" ></text>
</g>
<g >
<title>__update_load_avg_se (1,239,364,806 samples, 0.01%)</title><rect x="488.4" y="197" width="0.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="491.40" y="207.5" ></text>
</g>
<g >
<title>ExecGetRangeTableRelation (30,024,469,675 samples, 0.32%)</title><rect x="450.7" y="437" width="3.8" height="15.0" fill="rgb(241,167,39)" rx="2" ry="2" />
<text  x="453.72" y="447.5" ></text>
</g>
<g >
<title>do_futex (1,305,666,798 samples, 0.01%)</title><rect x="478.2" y="325" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="481.15" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="613" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1188.13" y="623.5" ></text>
</g>
<g >
<title>CreateExecutorState (1,032,775,724 samples, 0.01%)</title><rect x="39.7" y="757" width="0.2" height="15.0" fill="rgb(228,105,25)" rx="2" ry="2" />
<text  x="42.73" y="767.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (839,546,280 samples, 0.01%)</title><rect x="975.0" y="357" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="978.02" y="367.5" ></text>
</g>
<g >
<title>new_list (1,100,695,879 samples, 0.01%)</title><rect x="456.1" y="421" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="459.08" y="431.5" ></text>
</g>
<g >
<title>pg_comp_crc32c_sse42 (1,663,235,732 samples, 0.02%)</title><rect x="1169.6" y="757" width="0.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="1172.60" y="767.5" ></text>
</g>
<g >
<title>pg_utf8_verifystr (8,914,909,446 samples, 0.09%)</title><rect x="1080.7" y="533" width="1.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1083.71" y="543.5" ></text>
</g>
<g >
<title>hash_search (4,162,627,933 samples, 0.04%)</title><rect x="924.0" y="405" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="927.05" y="415.5" ></text>
</g>
<g >
<title>list_copy (5,143,230,083 samples, 0.05%)</title><rect x="990.6" y="357" width="0.7" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="993.62" y="367.5" ></text>
</g>
<g >
<title>ExecIndexScan (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="469" width="2.6" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="87.32" y="479.5" ></text>
</g>
<g >
<title>ServerLoop (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="709" width="2.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="126.99" y="719.5" ></text>
</g>
<g >
<title>create_tidscan_paths (1,230,716,041 samples, 0.01%)</title><rect x="1131.4" y="757" width="0.1" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="1134.36" y="767.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,327,674,209 samples, 0.01%)</title><rect x="419.9" y="405" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="422.95" y="415.5" ></text>
</g>
<g >
<title>ShowTransactionState (1,164,933,399 samples, 0.01%)</title><rect x="1076.6" y="533" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1079.59" y="543.5" ></text>
</g>
<g >
<title>palloc (1,388,234,589 samples, 0.01%)</title><rect x="890.5" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="893.54" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,061,072,580 samples, 0.02%)</title><rect x="945.7" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="948.70" y="399.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,844,478,100 samples, 0.05%)</title><rect x="522.2" y="453" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="525.22" y="463.5" ></text>
</g>
<g >
<title>set_plan_references (69,493,311,425 samples, 0.73%)</title><rect x="895.5" y="517" width="8.7" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="898.55" y="527.5" ></text>
</g>
<g >
<title>exec_simple_query (1,061,920,638 samples, 0.01%)</title><rect x="1135.6" y="757" width="0.1" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="1138.60" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,623,458,395 samples, 0.04%)</title><rect x="972.3" y="373" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="975.33" y="383.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,159,079,234 samples, 0.02%)</title><rect x="862.6" y="421" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="865.62" y="431.5" ></text>
</g>
<g >
<title>add_placeholders_to_base_rels (894,340,103 samples, 0.01%)</title><rect x="1083.2" y="757" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="1086.21" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetLock (2,375,394,155 samples, 0.03%)</title><rect x="526.2" y="453" width="0.3" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="529.22" y="463.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="485" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="129.16" y="495.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (7,413,640,058 samples, 0.08%)</title><rect x="150.9" y="533" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="153.88" y="543.5" ></text>
</g>
<g >
<title>newNode (2,454,567,287 samples, 0.03%)</title><rect x="927.8" y="405" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="930.82" y="415.5" ></text>
</g>
<g >
<title>new_list (2,295,978,078 samples, 0.02%)</title><rect x="985.0" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="987.97" y="367.5" ></text>
</g>
<g >
<title>scanNSItemForColumn (18,123,674,758 samples, 0.19%)</title><rect x="856.1" y="373" width="2.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="859.12" y="383.5" ></text>
</g>
<g >
<title>pairingheap_add (1,117,279,169 samples, 0.01%)</title><rect x="1165.1" y="757" width="0.2" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1168.13" y="767.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVacuumHorizon (2,622,298,886 samples, 0.03%)</title><rect x="1149.8" y="693" width="0.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="1152.82" y="703.5" ></text>
</g>
<g >
<title>BackendStartup (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="709" width="0.4" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1161.90" y="719.5" ></text>
</g>
<g >
<title>ExecProcNode (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="517" width="6.8" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="98.68" y="527.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,176,052,692 samples, 0.04%)</title><rect x="97.2" y="181" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="100.18" y="191.5" ></text>
</g>
<g >
<title>hash_search (4,000,715,024 samples, 0.04%)</title><rect x="863.3" y="453" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="866.33" y="463.5" ></text>
</g>
<g >
<title>LockReleaseAll (1,442,267,877 samples, 0.02%)</title><rect x="59.4" y="757" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="62.41" y="767.5" ></text>
</g>
<g >
<title>fix_expr_common (2,123,995,767 samples, 0.02%)</title><rect x="903.4" y="405" width="0.3" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="906.43" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetLock (831,020,084 samples, 0.01%)</title><rect x="227.9" y="501" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="230.94" y="511.5" ></text>
</g>
<g >
<title>ReservePrivateRefCountEntry (815,901,971 samples, 0.01%)</title><rect x="409.3" y="277" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="412.31" y="287.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,881,562,583 samples, 0.02%)</title><rect x="397.5" y="309" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="400.51" y="319.5" ></text>
</g>
<g >
<title>IsTransactionState (1,378,722,894 samples, 0.01%)</title><rect x="55.5" y="757" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="58.48" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="645" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1188.13" y="655.5" ></text>
</g>
<g >
<title>PopActiveSnapshot (3,820,703,223 samples, 0.04%)</title><rect x="224.2" y="581" width="0.5" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="227.18" y="591.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (4,616,388,160 samples, 0.05%)</title><rect x="823.8" y="357" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="826.84" y="367.5" ></text>
</g>
<g >
<title>get_leftop (1,034,757,919 samples, 0.01%)</title><rect x="966.3" y="373" width="0.1" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="969.30" y="383.5" ></text>
</g>
<g >
<title>make_restrictinfo (26,491,333,097 samples, 0.28%)</title><rect x="964.4" y="405" width="3.3" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="967.40" y="415.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (895,182,564 samples, 0.01%)</title><rect x="299.2" y="229" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="302.23" y="239.5" ></text>
</g>
<g >
<title>ExecReadyExpr (3,180,225,220 samples, 0.03%)</title><rect x="275.3" y="405" width="0.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="278.35" y="415.5" ></text>
</g>
<g >
<title>new_list (1,910,584,983 samples, 0.02%)</title><rect x="860.0" y="501" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="862.98" y="511.5" ></text>
</g>
<g >
<title>new_list (1,948,139,412 samples, 0.02%)</title><rect x="953.3" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="956.33" y="431.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (1,886,624,159 samples, 0.02%)</title><rect x="439.3" y="357" width="0.2" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="442.29" y="367.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,010,516,896 samples, 0.06%)</title><rect x="955.2" y="357" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="958.22" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,521,508,156 samples, 0.02%)</title><rect x="456.6" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="459.65" y="383.5" ></text>
</g>
<g >
<title>RegisterSnapshotOnOwner (1,172,958,126 samples, 0.01%)</title><rect x="460.2" y="485" width="0.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="463.19" y="495.5" ></text>
</g>
<g >
<title>limit_needed (1,026,637,660 samples, 0.01%)</title><rect x="918.3" y="485" width="0.1" height="15.0" fill="rgb(232,125,29)" rx="2" ry="2" />
<text  x="921.28" y="495.5" ></text>
</g>
<g >
<title>set_base_rel_sizes (130,366,339,502 samples, 1.37%)</title><rect x="1025.9" y="453" width="16.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1028.91" y="463.5" ></text>
</g>
<g >
<title>palloc (3,197,995,114 samples, 0.03%)</title><rect x="403.7" y="389" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="406.71" y="399.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="341" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="128.92" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,022,885,485 samples, 0.01%)</title><rect x="401.4" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="404.44" y="335.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,333,420,571 samples, 0.01%)</title><rect x="452.9" y="293" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="455.94" y="303.5" ></text>
</g>
<g >
<title>pq_beginmessage (807,056,749 samples, 0.01%)</title><rect x="1173.2" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="1176.18" y="767.5" ></text>
</g>
<g >
<title>bms_is_subset (989,120,837 samples, 0.01%)</title><rect x="1121.7" y="757" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="1124.72" y="767.5" ></text>
</g>
<g >
<title>new_list (1,487,295,154 samples, 0.02%)</title><rect x="992.8" y="325" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="995.77" y="335.5" ></text>
</g>
<g >
<title>newNode (3,739,873,460 samples, 0.04%)</title><rect x="277.8" y="405" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="280.81" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,690,885,827 samples, 0.03%)</title><rect x="97.4" y="133" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="100.36" y="143.5" ></text>
</g>
<g >
<title>put_prev_entity (1,013,625,244 samples, 0.01%)</title><rect x="162.8" y="309" width="0.1" height="15.0" fill="rgb(211,28,6)" rx="2" ry="2" />
<text  x="165.82" y="319.5" ></text>
</g>
<g >
<title>CopyXLogRecordToWAL (9,770,282,317 samples, 0.10%)</title><rect x="386.4" y="309" width="1.3" height="15.0" fill="rgb(213,36,8)" rx="2" ry="2" />
<text  x="389.44" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (869,146,004 samples, 0.01%)</title><rect x="1167.7" y="741" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1170.72" y="751.5" ></text>
</g>
<g >
<title>perf_ctx_disable (6,913,438,706 samples, 0.07%)</title><rect x="165.7" y="293" width="0.8" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="168.66" y="303.5" ></text>
</g>
<g >
<title>update_process_times (1,309,762,202 samples, 0.01%)</title><rect x="757.4" y="421" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="760.39" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (844,290,066 samples, 0.01%)</title><rect x="1008.2" y="261" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1011.25" y="271.5" ></text>
</g>
<g >
<title>_bt_preprocess_array_keys (1,607,516,114 samples, 0.02%)</title><rect x="124.0" y="277" width="0.2" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="126.99" y="287.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (905,156,695 samples, 0.01%)</title><rect x="84.7" y="213" width="0.1" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="87.73" y="223.5" ></text>
</g>
<g >
<title>int4hashfast (1,278,465,017 samples, 0.01%)</title><rect x="1009.4" y="213" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1012.35" y="223.5" ></text>
</g>
<g >
<title>make_plain_restrictinfo (24,635,849,149 samples, 0.26%)</title><rect x="964.6" y="389" width="3.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="967.62" y="399.5" ></text>
</g>
<g >
<title>ExecProcNode (930,623,721 samples, 0.01%)</title><rect x="409.9" y="453" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="412.90" y="463.5" ></text>
</g>
<g >
<title>match_clause_to_indexcol (21,719,196,567 samples, 0.23%)</title><rect x="1019.2" y="341" width="2.7" height="15.0" fill="rgb(232,128,30)" rx="2" ry="2" />
<text  x="1022.22" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,354,039,007 samples, 0.01%)</title><rect x="401.0" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="404.00" y="335.5" ></text>
</g>
<g >
<title>__kmalloc_node_track_caller_noprof (9,832,126,277 samples, 0.10%)</title><rect x="197.2" y="341" width="1.2" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="200.20" y="351.5" ></text>
</g>
<g >
<title>shmem_write_begin (11,160,859,065 samples, 0.12%)</title><rect x="504.4" y="357" width="1.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="507.40" y="367.5" ></text>
</g>
<g >
<title>int4eq (1,439,650,447 samples, 0.02%)</title><rect x="1158.9" y="229" width="0.2" height="15.0" fill="rgb(206,9,2)" rx="2" ry="2" />
<text  x="1161.91" y="239.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,873,485,042 samples, 0.06%)</title><rect x="850.1" y="389" width="0.7" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="853.08" y="399.5" ></text>
</g>
<g >
<title>kmem_cache_free (7,073,059,340 samples, 0.07%)</title><rect x="184.1" y="373" width="0.9" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="187.14" y="383.5" ></text>
</g>
<g >
<title>markVarForSelectPriv (2,087,959,396 samples, 0.02%)</title><rect x="842.6" y="325" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="845.56" y="335.5" ></text>
</g>
<g >
<title>newNode (3,787,359,554 samples, 0.04%)</title><rect x="891.8" y="373" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="894.76" y="383.5" ></text>
</g>
<g >
<title>get_hash_value (2,893,586,506 samples, 0.03%)</title><rect x="100.2" y="165" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="103.16" y="175.5" ></text>
</g>
<g >
<title>MarkPortalDone (1,534,139,172 samples, 0.02%)</title><rect x="230.2" y="565" width="0.2" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="233.18" y="575.5" ></text>
</g>
<g >
<title>table_index_fetch_begin (2,990,008,869 samples, 0.03%)</title><rect x="295.6" y="309" width="0.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="298.62" y="319.5" ></text>
</g>
<g >
<title>_int_malloc (6,439,871,489 samples, 0.07%)</title><rect x="294.7" y="213" width="0.8" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="297.71" y="223.5" ></text>
</g>
<g >
<title>AllocSetAlloc (998,445,658 samples, 0.01%)</title><rect x="942.3" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="945.28" y="335.5" ></text>
</g>
<g >
<title>lappend (3,200,535,908 samples, 0.03%)</title><rect x="968.9" y="373" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="971.88" y="383.5" ></text>
</g>
<g >
<title>create_plan (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="549" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="128.92" y="559.5" ></text>
</g>
<g >
<title>list_make1_impl (5,185,370,834 samples, 0.05%)</title><rect x="1118.2" y="725" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1121.16" y="735.5" ></text>
</g>
<g >
<title>ProcessClientReadInterrupt (915,716,303 samples, 0.01%)</title><rect x="87.8" y="757" width="0.1" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="90.83" y="767.5" ></text>
</g>
<g >
<title>ServerLoop (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="693" width="2.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="129.31" y="703.5" ></text>
</g>
<g >
<title>ExecModifyTable (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="469" width="1.9" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="129.31" y="479.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturnSwitchContext (49,959,633,713 samples, 0.53%)</title><rect x="285.7" y="341" width="6.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="288.67" y="351.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (1,188,103,721 samples, 0.01%)</title><rect x="284.5" y="245" width="0.2" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="287.52" y="255.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,720,019,454 samples, 0.04%)</title><rect x="373.9" y="261" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="376.92" y="271.5" ></text>
</g>
<g >
<title>PageGetFreeSpace (1,156,551,096 samples, 0.01%)</title><rect x="81.4" y="757" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="84.36" y="767.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,131,154,245 samples, 0.01%)</title><rect x="1080.0" y="581" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1083.00" y="591.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,942,247,167 samples, 0.02%)</title><rect x="474.5" y="469" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="477.48" y="479.5" ></text>
</g>
<g >
<title>LWLockRelease (1,158,067,955 samples, 0.01%)</title><rect x="341.8" y="197" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="344.78" y="207.5" ></text>
</g>
<g >
<title>_bt_checkkeys (1,283,821,015 samples, 0.01%)</title><rect x="129.3" y="757" width="0.2" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="132.34" y="767.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (17,848,614,773 samples, 0.19%)</title><rect x="916.1" y="469" width="2.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="919.06" y="479.5" ></text>
</g>
<g >
<title>hash_search (3,516,555,231 samples, 0.04%)</title><rect x="948.1" y="341" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="951.08" y="351.5" ></text>
</g>
<g >
<title>__x64_sys_futex (928,684,905 samples, 0.01%)</title><rect x="370.2" y="165" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="373.23" y="175.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (7,541,582,859 samples, 0.08%)</title><rect x="425.7" y="293" width="1.0" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="428.72" y="303.5" ></text>
</g>
<g >
<title>palloc (2,986,769,002 samples, 0.03%)</title><rect x="436.5" y="405" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="439.47" y="415.5" ></text>
</g>
<g >
<title>RelationBuildPublicationDesc (1,141,379,256 samples, 0.01%)</title><rect x="89.4" y="757" width="0.1" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="92.35" y="767.5" ></text>
</g>
<g >
<title>PushActiveSnapshotWithLevel (2,283,556,558 samples, 0.02%)</title><rect x="461.9" y="517" width="0.3" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="464.90" y="527.5" ></text>
</g>
<g >
<title>planner (3,069,363,779 samples, 0.03%)</title><rect x="125.9" y="581" width="0.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="128.92" y="591.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,196,507,255 samples, 0.02%)</title><rect x="86.1" y="181" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="89.11" y="191.5" ></text>
</g>
<g >
<title>ForEachLWLockHeldByMe (1,734,105,171 samples, 0.02%)</title><rect x="30.1" y="741" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="33.08" y="751.5" ></text>
</g>
<g >
<title>AllocSetAlloc (920,103,228 samples, 0.01%)</title><rect x="438.1" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="441.09" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,575,819,936 samples, 0.03%)</title><rect x="439.9" y="357" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="442.86" y="367.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (997,951,721 samples, 0.01%)</title><rect x="363.2" y="341" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="366.25" y="351.5" ></text>
</g>
<g >
<title>psi_task_change (939,045,438 samples, 0.01%)</title><rect x="207.5" y="293" width="0.1" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="210.51" y="303.5" ></text>
</g>
<g >
<title>LWLockRelease (923,349,874 samples, 0.01%)</title><rect x="355.4" y="261" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="358.42" y="271.5" ></text>
</g>
<g >
<title>btgettuple (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="309" width="1.9" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="129.31" y="319.5" ></text>
</g>
<g >
<title>bms_is_valid_set (1,256,087,417 samples, 0.01%)</title><rect x="359.0" y="357" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="361.96" y="367.5" ></text>
</g>
<g >
<title>list_make1_impl (2,535,639,769 samples, 0.03%)</title><rect x="1056.9" y="453" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1059.95" y="463.5" ></text>
</g>
<g >
<title>evaluate_function (1,837,831,932 samples, 0.02%)</title><rect x="104.7" y="453" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="107.69" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (1,156,659,716 samples, 0.01%)</title><rect x="368.4" y="261" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="371.36" y="271.5" ></text>
</g>
<g >
<title>EvalPlanQualEnd (1,737,117,897 samples, 0.02%)</title><rect x="41.5" y="757" width="0.3" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="44.55" y="767.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesUpdate (1,387,003,555 samples, 0.01%)</title><rect x="375.2" y="357" width="0.1" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="378.17" y="367.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,004,310,455 samples, 0.01%)</title><rect x="1021.0" y="261" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1023.98" y="271.5" ></text>
</g>
<g >
<title>_find_next_bit (1,676,754,597 samples, 0.02%)</title><rect x="204.6" y="245" width="0.2" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="207.56" y="255.5" ></text>
</g>
<g >
<title>max_parallel_hazard_checker (6,020,998,326 samples, 0.06%)</title><rect x="916.9" y="405" width="0.7" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="919.90" y="415.5" ></text>
</g>
<g >
<title>IsTidEqualClause (3,566,937,538 samples, 0.04%)</title><rect x="1024.8" y="357" width="0.4" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1027.80" y="367.5" ></text>
</g>
<g >
<title>hash_initial_lookup (3,031,752,228 samples, 0.03%)</title><rect x="524.3" y="405" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="527.29" y="415.5" ></text>
</g>
<g >
<title>PageIsNew (1,988,501,731 samples, 0.02%)</title><rect x="337.6" y="197" width="0.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="340.56" y="207.5" ></text>
</g>
<g >
<title>LWLockReleaseClearVar (5,349,562,114 samples, 0.06%)</title><rect x="389.1" y="293" width="0.7" height="15.0" fill="rgb(253,221,52)" rx="2" ry="2" />
<text  x="392.14" y="303.5" ></text>
</g>
<g >
<title>pfree (1,114,827,926 samples, 0.01%)</title><rect x="235.0" y="517" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="237.98" y="527.5" ></text>
</g>
<g >
<title>BufferIsPermanent (2,476,208,705 samples, 0.03%)</title><rect x="307.7" y="197" width="0.3" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="310.66" y="207.5" ></text>
</g>
<g >
<title>exit_to_user_mode_loop (3,262,404,173 samples, 0.03%)</title><rect x="209.1" y="453" width="0.4" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="212.07" y="463.5" ></text>
</g>
<g >
<title>AllocSetFree (1,025,195,027 samples, 0.01%)</title><rect x="994.4" y="325" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="997.40" y="335.5" ></text>
</g>
<g >
<title>coerce_type_typmod (1,892,182,413 samples, 0.02%)</title><rect x="844.2" y="421" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="847.16" y="431.5" ></text>
</g>
<g >
<title>preprocess_function_rtes (1,044,756,345 samples, 0.01%)</title><rect x="1173.8" y="757" width="0.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="1176.76" y="767.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,114,530,750 samples, 0.01%)</title><rect x="924.8" y="389" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="927.82" y="399.5" ></text>
</g>
<g >
<title>remove_useless_groupby_columns (811,568,995 samples, 0.01%)</title><rect x="1176.3" y="757" width="0.1" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="1179.28" y="767.5" ></text>
</g>
<g >
<title>_bt_readpage (2,463,738,124 samples, 0.03%)</title><rect x="84.7" y="309" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="87.68" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,052,906,206 samples, 0.01%)</title><rect x="1018.0" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1021.04" y="335.5" ></text>
</g>
<g >
<title>entry_SYSRETQ_unsafe_stack (891,998,899 samples, 0.01%)</title><rect x="209.6" y="485" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="212.62" y="495.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (4,789,015,248 samples, 0.05%)</title><rect x="264.5" y="389" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="267.53" y="399.5" ></text>
</g>
<g >
<title>att_isnull (1,633,336,112 samples, 0.02%)</title><rect x="1006.8" y="197" width="0.2" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="1009.77" y="207.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (1,783,291,477 samples, 0.02%)</title><rect x="233.5" y="501" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="236.49" y="511.5" ></text>
</g>
<g >
<title>clauselist_selectivity_ext (2,319,787,388 samples, 0.02%)</title><rect x="1011.2" y="277" width="0.3" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="1014.25" y="287.5" ></text>
</g>
<g >
<title>get_opfamily_member (13,190,974,401 samples, 0.14%)</title><rect x="1008.1" y="293" width="1.6" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1011.05" y="303.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (19,542,980,433 samples, 0.21%)</title><rect x="866.9" y="485" width="2.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="869.87" y="495.5" ></text>
</g>
<g >
<title>fetch_upper_rel (13,161,366,356 samples, 0.14%)</title><rect x="914.1" y="485" width="1.7" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="917.13" y="495.5" ></text>
</g>
<g >
<title>preprocess_expression (971,078,648 samples, 0.01%)</title><rect x="128.4" y="501" width="0.1" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="131.36" y="511.5" ></text>
</g>
<g >
<title>setTargetTable (165,346,498,511 samples, 1.74%)</title><rect x="813.0" y="485" width="20.5" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="815.97" y="495.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,168,931,656 samples, 0.01%)</title><rect x="513.6" y="405" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="516.60" y="415.5" ></text>
</g>
<g >
<title>select_idle_sibling (19,562,222,842 samples, 0.21%)</title><rect x="203.0" y="293" width="2.4" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="206.00" y="303.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,338,042,560 samples, 0.05%)</title><rect x="223.0" y="533" width="0.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="226.03" y="543.5" ></text>
</g>
<g >
<title>string_hash (3,046,824,152 samples, 0.03%)</title><rect x="229.2" y="549" width="0.3" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="232.15" y="559.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,197,986,898 samples, 0.01%)</title><rect x="388.9" y="197" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="391.91" y="207.5" ></text>
</g>
<g >
<title>pfree (1,668,178,936 samples, 0.02%)</title><rect x="461.0" y="517" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="464.04" y="527.5" ></text>
</g>
<g >
<title>tag_hash (1,960,021,384 samples, 0.02%)</title><rect x="942.8" y="325" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="945.78" y="335.5" ></text>
</g>
<g >
<title>pull_varattnos_walker (3,459,619,492 samples, 0.04%)</title><rect x="996.5" y="293" width="0.5" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="999.52" y="303.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,142,115,739 samples, 0.01%)</title><rect x="128.6" y="757" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="131.60" y="767.5" ></text>
</g>
<g >
<title>ExecutePlan (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="565" width="2.6" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="87.32" y="575.5" ></text>
</g>
<g >
<title>check_index_predicates (2,460,744,679 samples, 0.03%)</title><rect x="1027.2" y="405" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1030.18" y="415.5" ></text>
</g>
<g >
<title>tts_buffer_heap_getsomeattrs (3,518,378,876 samples, 0.04%)</title><rect x="349.9" y="341" width="0.4" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="352.87" y="351.5" ></text>
</g>
<g >
<title>resetStringInfo (1,119,678,228 samples, 0.01%)</title><rect x="188.5" y="549" width="0.1" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="191.46" y="559.5" ></text>
</g>
<g >
<title>record_times (1,128,311,386 samples, 0.01%)</title><rect x="485.6" y="261" width="0.1" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="488.60" y="271.5" ></text>
</g>
<g >
<title>bms_del_members (955,351,431 samples, 0.01%)</title><rect x="348.7" y="389" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="351.68" y="399.5" ></text>
</g>
<g >
<title>create_modifytable_plan (920,533,213 samples, 0.01%)</title><rect x="1130.7" y="757" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1133.73" y="767.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (3,274,627,784 samples, 0.03%)</title><rect x="1054.3" y="453" width="0.4" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1057.30" y="463.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (11,097,733,905 samples, 0.12%)</title><rect x="492.8" y="277" width="1.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="495.77" y="287.5" ></text>
</g>
<g >
<title>scm_recv_unix (1,849,229,949 samples, 0.02%)</title><rect x="185.4" y="373" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="188.37" y="383.5" ></text>
</g>
<g >
<title>table_index_fetch_reset (1,889,036,574 samples, 0.02%)</title><rect x="343.2" y="309" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="346.19" y="319.5" ></text>
</g>
<g >
<title>make_parsestate (4,840,930,247 samples, 0.05%)</title><rect x="803.7" y="549" width="0.6" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="806.74" y="559.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,182,380,192 samples, 0.01%)</title><rect x="851.6" y="341" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="854.58" y="351.5" ></text>
</g>
<g >
<title>palloc (1,184,068,810 samples, 0.01%)</title><rect x="457.3" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="460.35" y="431.5" ></text>
</g>
<g >
<title>new_list (1,673,942,243 samples, 0.02%)</title><rect x="422.8" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="425.84" y="367.5" ></text>
</g>
<g >
<title>XLogNeedsFlush (894,736,978 samples, 0.01%)</title><rect x="111.2" y="757" width="0.1" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="114.23" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (975,361,784 samples, 0.01%)</title><rect x="980.7" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="983.71" y="367.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableXid (971,285,322 samples, 0.01%)</title><rect x="1149.7" y="693" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1152.70" y="703.5" ></text>
</g>
<g >
<title>bms_add_member (2,741,440,157 samples, 0.03%)</title><rect x="416.0" y="469" width="0.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="419.03" y="479.5" ></text>
</g>
<g >
<title>unix_write_space (4,842,054,358 samples, 0.05%)</title><rect x="183.5" y="309" width="0.6" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="186.50" y="319.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (819,432,631 samples, 0.01%)</title><rect x="1023.3" y="309" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1026.33" y="319.5" ></text>
</g>
<g >
<title>BufferGetPage (1,395,738,115 samples, 0.01%)</title><rect x="385.2" y="325" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="388.21" y="335.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (34,941,663,413 samples, 0.37%)</title><rect x="491.1" y="469" width="4.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="494.07" y="479.5" ></text>
</g>
<g >
<title>bms_copy (1,916,906,471 samples, 0.02%)</title><rect x="878.7" y="469" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="881.73" y="479.5" ></text>
</g>
<g >
<title>cpuacct_charge (1,469,997,492 samples, 0.02%)</title><rect x="172.1" y="245" width="0.2" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="175.13" y="255.5" ></text>
</g>
<g >
<title>ExecutorRun (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="581" width="0.3" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="1161.90" y="591.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (47,614,791,684 samples, 0.50%)</title><rect x="66.1" y="757" width="5.9" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="69.13" y="767.5" ></text>
</g>
<g >
<title>palloc (2,360,387,024 samples, 0.02%)</title><rect x="1114.3" y="709" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1117.29" y="719.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,132,900,839 samples, 0.01%)</title><rect x="299.0" y="181" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="302.03" y="191.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,078,210,399 samples, 0.01%)</title><rect x="998.8" y="293" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1001.77" y="303.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,102,338,671 samples, 0.05%)</title><rect x="848.6" y="357" width="0.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="851.62" y="367.5" ></text>
</g>
<g >
<title>tag_hash (2,978,610,082 samples, 0.03%)</title><rect x="402.3" y="309" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="405.25" y="319.5" ></text>
</g>
<g >
<title>LockRelationOid (22,892,716,848 samples, 0.24%)</title><rect x="940.3" y="373" width="2.9" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="943.35" y="383.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (2,917,770,462 samples, 0.03%)</title><rect x="427.9" y="309" width="0.4" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="430.94" y="319.5" ></text>
</g>
<g >
<title>ProcessQuery (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="565" width="1.9" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="129.31" y="575.5" ></text>
</g>
<g >
<title>ExecProcNode (1,149,103,618,719 samples, 12.10%)</title><rect x="267.4" y="485" width="142.7" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="270.39" y="495.5" >ExecProcNode</text>
</g>
<g >
<title>PGSemaphoreLock (1,358,919,675 samples, 0.01%)</title><rect x="299.0" y="229" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="302.01" y="239.5" ></text>
</g>
<g >
<title>bms_copy (1,380,700,368 samples, 0.01%)</title><rect x="974.2" y="421" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="977.23" y="431.5" ></text>
</g>
<g >
<title>AllocSetFree (1,305,449,059 samples, 0.01%)</title><rect x="229.5" y="549" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="232.54" y="559.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,015,717,652 samples, 0.05%)</title><rect x="96.4" y="165" width="0.7" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="99.45" y="175.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (3,210,540,807 samples, 0.03%)</title><rect x="412.3" y="485" width="0.4" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="415.28" y="495.5" ></text>
</g>
<g >
<title>TupleDescAttr (919,882,187 samples, 0.01%)</title><rect x="829.7" y="277" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="832.74" y="287.5" ></text>
</g>
<g >
<title>psi_account_irqtime (6,360,636,705 samples, 0.07%)</title><rect x="166.5" y="341" width="0.8" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="169.52" y="351.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,543,182,330 samples, 0.04%)</title><rect x="438.5" y="341" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="441.48" y="351.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (4,979,302,012 samples, 0.05%)</title><rect x="814.2" y="453" width="0.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="817.22" y="463.5" ></text>
</g>
<g >
<title>TransactionTreeSetCommitTsData (837,088,473 samples, 0.01%)</title><rect x="480.7" y="501" width="0.1" height="15.0" fill="rgb(247,197,47)" rx="2" ry="2" />
<text  x="483.66" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,169,656,453 samples, 0.01%)</title><rect x="1117.9" y="693" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1120.88" y="703.5" ></text>
</g>
<g >
<title>WalSndWakeupProcessRequests (5,725,415,242 samples, 0.06%)</title><rect x="497.8" y="485" width="0.7" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="500.83" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,611,241,905 samples, 0.03%)</title><rect x="969.9" y="325" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="972.87" y="335.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,048,745,353 samples, 0.01%)</title><rect x="917.0" y="373" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="920.02" y="383.5" ></text>
</g>
<g >
<title>pfree (1,190,594,485 samples, 0.01%)</title><rect x="348.9" y="373" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="351.89" y="383.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (2,245,959,022 samples, 0.02%)</title><rect x="55.8" y="757" width="0.3" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="58.84" y="767.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (2,413,723,776 samples, 0.03%)</title><rect x="870.0" y="437" width="0.3" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="872.97" y="447.5" ></text>
</g>
<g >
<title>pgstat_report_query_id (1,634,232,700 samples, 0.02%)</title><rect x="804.3" y="549" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="807.35" y="559.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop (6,914,961,214 samples, 0.07%)</title><rect x="1051.0" y="485" width="0.9" height="15.0" fill="rgb(243,175,41)" rx="2" ry="2" />
<text  x="1054.03" y="495.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (1,220,476,765 samples, 0.01%)</title><rect x="125.8" y="277" width="0.1" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="128.76" y="287.5" ></text>
</g>
<g >
<title>LWLockRelease (1,077,392,055 samples, 0.01%)</title><rect x="101.6" y="181" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="104.61" y="191.5" ></text>
</g>
<g >
<title>heapam_index_fetch_tuple (132,798,454,723 samples, 1.40%)</title><rect x="297.0" y="277" width="16.5" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="299.97" y="287.5" ></text>
</g>
<g >
<title>base_yyparse (282,591,730,063 samples, 2.97%)</title><rect x="1085.5" y="757" width="35.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1088.53" y="767.5" >ba..</text>
</g>
<g >
<title>GetPrivateRefCount (906,288,249 samples, 0.01%)</title><rect x="407.4" y="277" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="410.39" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,431,936,811 samples, 0.02%)</title><rect x="425.0" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="428.02" y="303.5" ></text>
</g>
<g >
<title>hash_search (3,066,634,466 samples, 0.03%)</title><rect x="403.0" y="341" width="0.3" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="405.96" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (943,053,070 samples, 0.01%)</title><rect x="1055.1" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1058.10" y="415.5" ></text>
</g>
<g >
<title>SearchSysCache3 (5,020,673,404 samples, 0.05%)</title><rect x="1013.6" y="293" width="0.6" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1016.61" y="303.5" ></text>
</g>
<g >
<title>new_list (2,010,507,471 samples, 0.02%)</title><rect x="105.5" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="108.55" y="431.5" ></text>
</g>
<g >
<title>create_projection_plan (59,739,141,887 samples, 0.63%)</title><rect x="885.2" y="453" width="7.4" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="888.15" y="463.5" ></text>
</g>
<g >
<title>superuser_arg (920,329,266 samples, 0.01%)</title><rect x="1185.5" y="757" width="0.1" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="1188.47" y="767.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (3,494,236,964 samples, 0.04%)</title><rect x="135.5" y="469" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="138.54" y="479.5" ></text>
</g>
<g >
<title>btgettuple (228,745,579,240 samples, 2.41%)</title><rect x="313.9" y="293" width="28.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="316.93" y="303.5" >bt..</text>
</g>
<g >
<title>do_futex (1,076,031,459 samples, 0.01%)</title><rect x="299.0" y="133" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="302.03" y="143.5" ></text>
</g>
<g >
<title>relation_open (15,236,568,419 samples, 0.16%)</title><rect x="447.8" y="405" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="450.82" y="415.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,924,490,995 samples, 0.04%)</title><rect x="1058.0" y="453" width="0.5" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1061.02" y="463.5" ></text>
</g>
<g >
<title>bms_del_members (831,957,813 samples, 0.01%)</title><rect x="880.5" y="469" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="883.50" y="479.5" ></text>
</g>
<g >
<title>examine_simple_variable (24,765,832,889 samples, 0.26%)</title><rect x="1031.6" y="229" width="3.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1034.63" y="239.5" ></text>
</g>
<g >
<title>StartReadBuffer (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="245" width="4.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="98.68" y="255.5" ></text>
</g>
<g >
<title>preprocess_expression (23,038,794,810 samples, 0.24%)</title><rect x="1050.7" y="501" width="2.9" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1053.73" y="511.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,850,619,288 samples, 0.02%)</title><rect x="1144.3" y="757" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1147.30" y="767.5" ></text>
</g>
<g >
<title>table_open (16,262,042,898 samples, 0.17%)</title><rect x="1066.2" y="485" width="2.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1069.24" y="495.5" ></text>
</g>
<g >
<title>LWLockWakeup (2,049,657,852 samples, 0.02%)</title><rect x="478.1" y="421" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="481.09" y="431.5" ></text>
</g>
<g >
<title>palloc0 (23,616,822,836 samples, 0.25%)</title><rect x="1047.8" y="485" width="2.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1050.79" y="495.5" ></text>
</g>
<g >
<title>hash_search (3,535,381,227 samples, 0.04%)</title><rect x="943.4" y="357" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="946.36" y="367.5" ></text>
</g>
<g >
<title>new_list (1,655,044,967 samples, 0.02%)</title><rect x="914.8" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="917.80" y="463.5" ></text>
</g>
<g >
<title>BufferIsLockedByMeInMode (2,833,370,235 samples, 0.03%)</title><rect x="376.5" y="341" width="0.4" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="379.50" y="351.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (917,874,826 samples, 0.01%)</title><rect x="448.8" y="309" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="451.76" y="319.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="693" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1137.90" y="703.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="485" width="1.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="129.31" y="495.5" ></text>
</g>
<g >
<title>wake_affine (1,515,448,089 samples, 0.02%)</title><rect x="205.4" y="293" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="208.42" y="303.5" ></text>
</g>
<g >
<title>slot_getsomeattrs (4,187,136,202 samples, 0.04%)</title><rect x="349.8" y="373" width="0.5" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="352.78" y="383.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,933,331,688 samples, 0.06%)</title><rect x="1040.6" y="325" width="0.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1043.55" y="335.5" ></text>
</g>
<g >
<title>PinBufferForBlock (34,853,568,543 samples, 0.37%)</title><rect x="95.8" y="213" width="4.3" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="98.79" y="223.5" ></text>
</g>
<g >
<title>malloc (2,486,554,312 samples, 0.03%)</title><rect x="913.1" y="405" width="0.3" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="916.09" y="415.5" ></text>
</g>
<g >
<title>fmgr_info (3,769,143,627 samples, 0.04%)</title><rect x="1036.1" y="309" width="0.5" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="1039.13" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,207,567,708 samples, 0.01%)</title><rect x="1015.7" y="277" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1018.70" y="287.5" ></text>
</g>
<g >
<title>InjectionPointRun (951,370,968 samples, 0.01%)</title><rect x="53.9" y="757" width="0.1" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="56.87" y="767.5" ></text>
</g>
<g >
<title>list_concat (2,792,624,588 samples, 0.03%)</title><rect x="976.6" y="437" width="0.3" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="979.56" y="447.5" ></text>
</g>
<g >
<title>bms_equal (1,060,221,788 samples, 0.01%)</title><rect x="983.8" y="421" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="986.78" y="431.5" ></text>
</g>
<g >
<title>RelationGetIndexList (3,152,636,723 samples, 0.03%)</title><rect x="401.2" y="389" width="0.4" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="404.19" y="399.5" ></text>
</g>
<g >
<title>AllocSetCheck (127,322,707,046 samples, 1.34%)</title><rect x="12.9" y="757" width="15.8" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="15.86" y="767.5" ></text>
</g>
<g >
<title>uint32_hash (1,078,869,119 samples, 0.01%)</title><rect x="833.1" y="373" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="836.13" y="383.5" ></text>
</g>
<g >
<title>get_typcollation (5,632,909,196 samples, 0.06%)</title><rect x="807.6" y="373" width="0.7" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="810.58" y="383.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (2,705,590,665 samples, 0.03%)</title><rect x="222.5" y="549" width="0.4" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="225.55" y="559.5" ></text>
</g>
<g >
<title>CacheInvalidateHeapTupleCommon (940,607,613 samples, 0.01%)</title><rect x="363.9" y="341" width="0.1" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="366.89" y="351.5" ></text>
</g>
<g >
<title>pgstat_report_activity (5,907,338,947 samples, 0.06%)</title><rect x="1071.1" y="581" width="0.7" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1074.11" y="591.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,096,043,251 samples, 0.03%)</title><rect x="452.1" y="325" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="455.14" y="335.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32_impl (872,590,880 samples, 0.01%)</title><rect x="223.8" y="485" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="226.76" y="495.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (884,781,725 samples, 0.01%)</title><rect x="95.7" y="165" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="98.68" y="175.5" ></text>
</g>
<g >
<title>lappend (2,280,877,247 samples, 0.02%)</title><rect x="885.7" y="421" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="888.73" y="431.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (828,740,485 samples, 0.01%)</title><rect x="1115.9" y="661" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1118.93" y="671.5" ></text>
</g>
<g >
<title>try_to_wake_up (1,598,667,031 samples, 0.02%)</title><rect x="376.0" y="165" width="0.2" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="378.96" y="175.5" ></text>
</g>
<g >
<title>index_open (14,709,467,066 samples, 0.15%)</title><rect x="401.6" y="389" width="1.8" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="404.59" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,328,847,846 samples, 0.01%)</title><rect x="408.7" y="277" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="411.74" y="287.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (4,127,903,159 samples, 0.04%)</title><rect x="99.1" y="181" width="0.5" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="102.12" y="191.5" ></text>
</g>
<g >
<title>XLogInsertRecord (32,995,151,930 samples, 0.35%)</title><rect x="385.8" y="325" width="4.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="388.81" y="335.5" ></text>
</g>
<g >
<title>grouping_planner (1,128,905,494,125 samples, 11.88%)</title><rect x="907.1" y="501" width="140.3" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="910.13" y="511.5" >grouping_planner</text>
</g>
<g >
<title>ExecFindJunkAttributeInTlist (3,828,932,063 samples, 0.04%)</title><rect x="420.8" y="453" width="0.5" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="423.78" y="463.5" ></text>
</g>
<g >
<title>standard_planner (1,837,816,106 samples, 0.02%)</title><rect x="86.9" y="597" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="89.91" y="607.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,988,501,053 samples, 0.04%)</title><rect x="1036.8" y="309" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1039.83" y="319.5" ></text>
</g>
<g >
<title>index_open (1,278,451,898 samples, 0.01%)</title><rect x="1151.6" y="757" width="0.2" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="1154.61" y="767.5" ></text>
</g>
<g >
<title>my_log2 (956,309,077 samples, 0.01%)</title><rect x="1063.4" y="421" width="0.1" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="1066.36" y="431.5" ></text>
</g>
<g >
<title>examine_indexcol_variable (5,947,416,700 samples, 0.06%)</title><rect x="1009.8" y="309" width="0.7" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1012.77" y="319.5" ></text>
</g>
<g >
<title>sched_tick (863,800,687 samples, 0.01%)</title><rect x="757.4" y="405" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="760.45" y="415.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (7,406,415,808 samples, 0.08%)</title><rect x="1032.2" y="181" width="1.0" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1035.23" y="191.5" ></text>
</g>
<g >
<title>__futex_wait (931,266,045 samples, 0.01%)</title><rect x="475.6" y="341" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="478.59" y="351.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,798,078,534 samples, 0.02%)</title><rect x="395.2" y="293" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="398.19" y="303.5" ></text>
</g>
<g >
<title>TidQualFromRestrictInfoList (9,029,397,081 samples, 0.10%)</title><rect x="1024.3" y="389" width="1.1" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="1027.25" y="399.5" ></text>
</g>
<g >
<title>exprType (858,194,850 samples, 0.01%)</title><rect x="445.6" y="373" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="448.57" y="383.5" ></text>
</g>
<g >
<title>pull_varnos_walker (3,967,863,747 samples, 0.04%)</title><rect x="967.2" y="341" width="0.5" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="970.18" y="351.5" ></text>
</g>
<g >
<title>set_next_task_idle (3,096,450,415 samples, 0.03%)</title><rect x="163.8" y="325" width="0.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="166.78" y="335.5" ></text>
</g>
<g >
<title>table_open (15,300,212,740 samples, 0.16%)</title><rect x="947.1" y="405" width="1.9" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="950.10" y="415.5" ></text>
</g>
<g >
<title>fix_indexqual_references (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="453" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="89.91" y="463.5" ></text>
</g>
<g >
<title>new_list (1,422,563,372 samples, 0.01%)</title><rect x="897.6" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="900.62" y="463.5" ></text>
</g>
<g >
<title>PostmasterMain (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="693" width="950.7" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="134.98" y="703.5" >PostmasterMain</text>
</g>
<g >
<title>XactLogCommitRecord (29,124,373,583 samples, 0.31%)</title><rect x="510.9" y="501" width="3.6" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="513.90" y="511.5" ></text>
</g>
<g >
<title>exec_simple_query (83,672,568,701 samples, 0.88%)</title><rect x="95.7" y="677" width="10.4" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="98.68" y="687.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (8,862,765,129 samples, 0.09%)</title><rect x="881.9" y="421" width="1.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="884.89" y="431.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (27,750,898,730 samples, 0.29%)</title><rect x="491.6" y="405" width="3.5" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="494.62" y="415.5" ></text>
</g>
<g >
<title>tts_buffer_heap_getsomeattrs (2,808,024,821 samples, 0.03%)</title><rect x="271.5" y="309" width="0.4" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="274.55" y="319.5" ></text>
</g>
<g >
<title>palloc0 (4,183,405,452 samples, 0.04%)</title><rect x="893.7" y="437" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="896.70" y="447.5" ></text>
</g>
<g >
<title>_int_free_merge_chunk (849,837,241 samples, 0.01%)</title><rect x="264.4" y="389" width="0.1" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="267.37" y="399.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (4,551,604,086 samples, 0.05%)</title><rect x="832.6" y="373" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="835.56" y="383.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (883,852,755 samples, 0.01%)</title><rect x="168.4" y="341" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="171.39" y="351.5" ></text>
</g>
<g >
<title>table_close (1,581,829,822 samples, 0.02%)</title><rect x="946.9" y="405" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="949.90" y="415.5" ></text>
</g>
<g >
<title>palloc (1,264,553,553 samples, 0.01%)</title><rect x="963.1" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="966.09" y="351.5" ></text>
</g>
<g >
<title>lappend (2,665,577,687 samples, 0.03%)</title><rect x="975.5" y="421" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="978.47" y="431.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="517" width="3.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="105.66" y="527.5" ></text>
</g>
<g >
<title>secure_raw_read (73,350,805,755 samples, 0.77%)</title><rect x="178.1" y="517" width="9.1" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="181.14" y="527.5" ></text>
</g>
<g >
<title>pull_varnos_walker (4,018,937,062 samples, 0.04%)</title><rect x="973.4" y="325" width="0.5" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="976.43" y="335.5" ></text>
</g>
<g >
<title>ExecProject (1,086,451,118 samples, 0.01%)</title><rect x="45.6" y="757" width="0.2" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="48.63" y="767.5" ></text>
</g>
<g >
<title>table_close (1,700,509,930 samples, 0.02%)</title><rect x="237.6" y="469" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="240.56" y="479.5" ></text>
</g>
<g >
<title>hash_search (3,400,604,199 samples, 0.04%)</title><rect x="1012.3" y="261" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1015.30" y="271.5" ></text>
</g>
<g >
<title>GlobalVisTestFor (865,058,931 samples, 0.01%)</title><rect x="304.5" y="245" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="307.48" y="255.5" ></text>
</g>
<g >
<title>copyObjectImpl (1,309,060,774 samples, 0.01%)</title><rect x="1129.1" y="757" width="0.2" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="1132.12" y="767.5" ></text>
</g>
<g >
<title>lappend (4,856,617,802 samples, 0.05%)</title><rect x="813.3" y="453" width="0.6" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="816.31" y="463.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,724,147,688 samples, 0.02%)</title><rect x="1148.5" y="549" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1151.50" y="559.5" ></text>
</g>
<g >
<title>newNode (2,701,393,903 samples, 0.03%)</title><rect x="990.0" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="992.98" y="383.5" ></text>
</g>
<g >
<title>BufTableHashCode (2,874,944,531 samples, 0.03%)</title><rect x="354.6" y="261" width="0.4" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="357.60" y="271.5" ></text>
</g>
<g >
<title>new_list (1,480,592,708 samples, 0.02%)</title><rect x="1053.4" y="421" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1056.40" y="431.5" ></text>
</g>
<g >
<title>clause_selectivity_ext (75,183,297,361 samples, 0.79%)</title><rect x="1028.0" y="357" width="9.3" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1031.01" y="367.5" ></text>
</g>
<g >
<title>tag_hash (7,039,126,029 samples, 0.07%)</title><rect x="867.6" y="421" width="0.8" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="870.55" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,587,773,483 samples, 0.02%)</title><rect x="976.2" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="979.24" y="415.5" ></text>
</g>
<g >
<title>ExecInitScanTupleSlot (11,453,686,602 samples, 0.12%)</title><rect x="446.0" y="421" width="1.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="448.95" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (855,142,278 samples, 0.01%)</title><rect x="920.2" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="923.21" y="463.5" ></text>
</g>
<g >
<title>enqueue_task_fair (8,433,475,573 samples, 0.09%)</title><rect x="206.5" y="293" width="1.0" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="209.46" y="303.5" ></text>
</g>
<g >
<title>smgrnblocks (851,301,489 samples, 0.01%)</title><rect x="1183.7" y="757" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1186.65" y="767.5" ></text>
</g>
<g >
<title>_bt_compare (1,409,295,037 samples, 0.01%)</title><rect x="128.0" y="245" width="0.2" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="130.99" y="255.5" ></text>
</g>
<g >
<title>pq_sendbyte (1,944,619,695 samples, 0.02%)</title><rect x="190.1" y="581" width="0.3" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="193.13" y="591.5" ></text>
</g>
<g >
<title>ExecInitResultTypeTL (4,308,142,915 samples, 0.05%)</title><rect x="456.4" y="453" width="0.5" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="459.36" y="463.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="213" width="0.5" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="89.39" y="223.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,227,094,015 samples, 0.01%)</title><rect x="369.9" y="245" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="372.93" y="255.5" ></text>
</g>
<g >
<title>list_free_private (1,511,840,648 samples, 0.02%)</title><rect x="254.3" y="405" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="257.34" y="415.5" ></text>
</g>
<g >
<title>add_rtes_to_flat_rtable (20,556,580,195 samples, 0.22%)</title><rect x="895.8" y="501" width="2.5" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="898.77" y="511.5" ></text>
</g>
<g >
<title>_copy_from_iter (1,585,594,409 samples, 0.02%)</title><rect x="196.3" y="389" width="0.2" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="199.31" y="399.5" ></text>
</g>
<g >
<title>index_close (2,364,740,312 samples, 0.02%)</title><rect x="240.7" y="421" width="0.3" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="243.75" y="431.5" ></text>
</g>
<g >
<title>ExecEndPlan (113,465,702,251 samples, 1.19%)</title><rect x="237.3" y="501" width="14.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="240.29" y="511.5" ></text>
</g>
<g >
<title>ExecIndexScan (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="453" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1161.90" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,181,252,467 samples, 0.01%)</title><rect x="433.0" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="436.04" y="335.5" ></text>
</g>
<g >
<title>RelationGetIndexList (4,402,743,062 samples, 0.05%)</title><rect x="930.9" y="405" width="0.6" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="933.93" y="415.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableFullXid (2,665,785,873 samples, 0.03%)</title><rect x="305.7" y="213" width="0.4" height="15.0" fill="rgb(232,128,30)" rx="2" ry="2" />
<text  x="308.74" y="223.5" ></text>
</g>
<g >
<title>ReadBuffer_common (11,333,614,982 samples, 0.12%)</title><rect x="85.0" y="245" width="1.4" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="87.98" y="255.5" ></text>
</g>
<g >
<title>BufferIsLockedByMe (1,885,262,266 samples, 0.02%)</title><rect x="308.2" y="181" width="0.2" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="311.16" y="191.5" ></text>
</g>
<g >
<title>AllocSetFree (1,271,091,921 samples, 0.01%)</title><rect x="403.5" y="341" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="406.52" y="351.5" ></text>
</g>
<g >
<title>RelationGetIndexExpressions (1,069,679,847 samples, 0.01%)</title><rect x="399.8" y="373" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="402.77" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (2,435,176,388 samples, 0.03%)</title><rect x="824.4" y="341" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="827.42" y="351.5" ></text>
</g>
<g >
<title>table_close (2,610,400,400 samples, 0.03%)</title><rect x="866.4" y="517" width="0.4" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="869.45" y="527.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="565" width="0.3" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="1161.90" y="575.5" ></text>
</g>
<g >
<title>DynaHashAlloc (2,096,916,443 samples, 0.02%)</title><rect x="1065.3" y="405" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1068.31" y="415.5" ></text>
</g>
<g >
<title>preprocess_expression (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="517" width="0.1" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="129.16" y="527.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,601,126,352 samples, 0.02%)</title><rect x="857.6" y="325" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="860.57" y="335.5" ></text>
</g>
<g >
<title>schedule_hrtimeout_range_clock (110,051,606,741 samples, 1.16%)</title><rect x="161.2" y="389" width="13.7" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="164.20" y="399.5" ></text>
</g>
<g >
<title>ItemPointerGetOffsetNumberNoCheck (2,837,248,905 samples, 0.03%)</title><rect x="56.3" y="757" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="59.26" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,929,809,315 samples, 0.02%)</title><rect x="1135.1" y="757" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1138.06" y="767.5" ></text>
</g>
<g >
<title>CreateTemplateTupleDesc (2,652,040,082 samples, 0.03%)</title><rect x="456.5" y="405" width="0.3" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="459.51" y="415.5" ></text>
</g>
<g >
<title>set_cheapest (4,543,117,010 samples, 0.05%)</title><rect x="984.0" y="421" width="0.5" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="986.98" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,356,624,300 samples, 0.01%)</title><rect x="1117.5" y="693" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1120.54" y="703.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,361,892,573 samples, 0.04%)</title><rect x="838.7" y="325" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="841.72" y="335.5" ></text>
</g>
<g >
<title>bms_add_member (2,159,335,261 samples, 0.02%)</title><rect x="973.5" y="309" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="976.54" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (12,660,316,483 samples, 0.13%)</title><rect x="1064.1" y="453" width="1.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1067.07" y="463.5" ></text>
</g>
<g >
<title>XLogCheckBufferNeedsBackup (3,002,059,900 samples, 0.03%)</title><rect x="385.1" y="341" width="0.4" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="388.10" y="351.5" ></text>
</g>
<g >
<title>Int32GetDatum (2,898,977,929 samples, 0.03%)</title><rect x="54.0" y="757" width="0.4" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="57.04" y="767.5" ></text>
</g>
<g >
<title>TransactionIdSetStatusBit (3,929,632,614 samples, 0.04%)</title><rect x="480.1" y="437" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="483.14" y="447.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (2,964,597,659 samples, 0.03%)</title><rect x="972.4" y="341" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="975.41" y="351.5" ></text>
</g>
<g >
<title>sem_wait@@GLIBC_2.34 (1,204,923,624 samples, 0.01%)</title><rect x="490.8" y="453" width="0.1" height="15.0" fill="rgb(221,73,17)" rx="2" ry="2" />
<text  x="493.79" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,481,567,100 samples, 0.03%)</title><rect x="882.7" y="373" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="885.68" y="383.5" ></text>
</g>
<g >
<title>palloc0 (1,499,503,398 samples, 0.02%)</title><rect x="834.7" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="837.74" y="447.5" ></text>
</g>
<g >
<title>do_syscall_64 (3,465,355,192 samples, 0.04%)</title><rect x="367.8" y="213" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="370.80" y="223.5" ></text>
</g>
<g >
<title>subquery_planner (1,633,918,386 samples, 0.02%)</title><rect x="126.1" y="549" width="0.2" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="129.10" y="559.5" ></text>
</g>
<g >
<title>relation_close (1,588,282,834 samples, 0.02%)</title><rect x="237.6" y="453" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="240.57" y="463.5" ></text>
</g>
<g >
<title>pfree (5,177,502,547 samples, 0.05%)</title><rect x="977.5" y="421" width="0.7" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="980.53" y="431.5" ></text>
</g>
<g >
<title>get_timeout_active (847,979,845 samples, 0.01%)</title><rect x="1077.7" y="549" width="0.1" height="15.0" fill="rgb(254,225,54)" rx="2" ry="2" />
<text  x="1080.66" y="559.5" ></text>
</g>
<g >
<title>palloc (2,671,086,463 samples, 0.03%)</title><rect x="1115.2" y="693" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1118.15" y="703.5" ></text>
</g>
<g >
<title>lappend (2,686,926,965 samples, 0.03%)</title><rect x="1014.3" y="309" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1017.26" y="319.5" ></text>
</g>
<g >
<title>transformTargetList (64,954,483,950 samples, 0.68%)</title><rect x="835.0" y="469" width="8.1" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="838.02" y="479.5" ></text>
</g>
<g >
<title>palloc (3,141,734,510 samples, 0.03%)</title><rect x="945.2" y="405" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="948.17" y="415.5" ></text>
</g>
<g >
<title>IndexScanEnd (3,512,416,362 samples, 0.04%)</title><rect x="241.3" y="405" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="244.29" y="415.5" ></text>
</g>
<g >
<title>exprType (1,618,498,098 samples, 0.02%)</title><rect x="1034.7" y="229" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1037.71" y="239.5" ></text>
</g>
<g >
<title>planner (923,437,082 samples, 0.01%)</title><rect x="1172.9" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1175.88" y="767.5" ></text>
</g>
<g >
<title>get_relation_notnullatts (65,406,834,547 samples, 0.69%)</title><rect x="1057.7" y="485" width="8.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1060.72" y="495.5" ></text>
</g>
<g >
<title>palloc (1,248,900,354 samples, 0.01%)</title><rect x="1014.4" y="277" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1017.42" y="287.5" ></text>
</g>
<g >
<title>pgstat_count_slru_blocks_hit (1,237,661,849 samples, 0.01%)</title><rect x="309.7" y="149" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="312.70" y="159.5" ></text>
</g>
<g >
<title>available_idle_cpu (5,264,083,379 samples, 0.06%)</title><rect x="204.8" y="245" width="0.6" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="207.77" y="255.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_object (1,440,944,497 samples, 0.02%)</title><rect x="99.9" y="165" width="0.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="102.94" y="175.5" ></text>
</g>
<g >
<title>shmem_file_write_iter (42,762,366,601 samples, 0.45%)</title><rect x="501.5" y="389" width="5.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="504.49" y="399.5" ></text>
</g>
<g >
<title>InjectionPointCacheRefresh (4,105,326,117 samples, 0.04%)</title><rect x="352.3" y="357" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="355.31" y="367.5" ></text>
</g>
<g >
<title>assign_collations_walker (21,356,093,330 samples, 0.22%)</title><rect x="809.4" y="373" width="2.6" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="812.36" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,029,136,620 samples, 0.01%)</title><rect x="1057.1" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1060.11" y="415.5" ></text>
</g>
<g >
<title>palloc0 (5,059,985,914 samples, 0.05%)</title><rect x="952.3" y="389" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="955.30" y="399.5" ></text>
</g>
<g >
<title>detoast_attr (1,020,895,804 samples, 0.01%)</title><rect x="1132.2" y="757" width="0.1" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="1135.19" y="767.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,294,753,459 samples, 0.02%)</title><rect x="481.7" y="437" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="484.73" y="447.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (68,873,321,630 samples, 0.72%)</title><rect x="482.4" y="469" width="8.6" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="485.43" y="479.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,727,597,899 samples, 0.05%)</title><rect x="963.6" y="373" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="966.59" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,157,020,137 samples, 0.01%)</title><rect x="369.8" y="229" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="372.77" y="239.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (2,849,034,554 samples, 0.03%)</title><rect x="258.6" y="421" width="0.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="261.59" y="431.5" ></text>
</g>
<g >
<title>hash_search (3,388,248,583 samples, 0.04%)</title><rect x="449.2" y="373" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="452.25" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,073,799,613 samples, 0.01%)</title><rect x="358.4" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="361.40" y="351.5" ></text>
</g>
<g >
<title>subquery_planner (1,343,590,676,417 samples, 14.14%)</title><rect x="904.2" y="517" width="166.9" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="907.18" y="527.5" >subquery_planner</text>
</g>
<g >
<title>BuildIndexInfo (13,659,638,501 samples, 0.14%)</title><rect x="399.5" y="389" width="1.7" height="15.0" fill="rgb(245,186,44)" rx="2" ry="2" />
<text  x="402.49" y="399.5" ></text>
</g>
<g >
<title>index_getnext_slot (17,089,892,659 samples, 0.18%)</title><rect x="282.6" y="341" width="2.1" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="285.55" y="351.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,531,282,644 samples, 0.02%)</title><rect x="98.4" y="165" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="101.42" y="175.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (16,929,942,608 samples, 0.18%)</title><rect x="134.0" y="549" width="2.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="136.99" y="559.5" ></text>
</g>
<g >
<title>palloc0 (1,722,445,640 samples, 0.02%)</title><rect x="861.7" y="501" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="864.72" y="511.5" ></text>
</g>
<g >
<title>SetHintBits (3,591,410,447 samples, 0.04%)</title><rect x="1147.5" y="693" width="0.4" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1150.49" y="703.5" ></text>
</g>
<g >
<title>AssertBufferLocksPermitCatalogRead (913,257,175 samples, 0.01%)</title><rect x="30.4" y="741" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="33.43" y="751.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,288,764,440 samples, 0.02%)</title><rect x="86.6" y="181" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="89.63" y="191.5" ></text>
</g>
<g >
<title>ReadBuffer (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="293" width="4.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="98.68" y="303.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_safe_stack (4,050,438,946 samples, 0.04%)</title><rect x="1133.9" y="757" width="0.5" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="1136.85" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,347,858,175 samples, 0.01%)</title><rect x="406.2" y="373" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="409.20" y="383.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (1,800,553,780 samples, 0.02%)</title><rect x="115.8" y="741" width="0.2" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="118.76" y="751.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (3,979,965,542 samples, 0.04%)</title><rect x="795.6" y="501" width="0.5" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="798.63" y="511.5" ></text>
</g>
<g >
<title>__futex_wait (1,080,315,050 samples, 0.01%)</title><rect x="353.6" y="213" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="356.57" y="223.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,027,594,859 samples, 0.02%)</title><rect x="1115.2" y="677" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1118.22" y="687.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,473,997,032 samples, 0.02%)</title><rect x="377.6" y="325" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="380.61" y="335.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (1,062,573,431 samples, 0.01%)</title><rect x="1123.1" y="741" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="1126.09" y="751.5" ></text>
</g>
<g >
<title>__futex_wait (879,038,932 samples, 0.01%)</title><rect x="370.0" y="133" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="372.95" y="143.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,581,003,741 samples, 0.03%)</title><rect x="942.5" y="325" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="945.46" y="335.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (3,105,372,898 samples, 0.03%)</title><rect x="989.5" y="357" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="992.51" y="367.5" ></text>
</g>
<g >
<title>makeVar (3,978,205,829 samples, 0.04%)</title><rect x="921.7" y="453" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="924.74" y="463.5" ></text>
</g>
<g >
<title>ExecGetJunkAttribute (921,228,125 samples, 0.01%)</title><rect x="269.0" y="437" width="0.1" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="271.95" y="447.5" ></text>
</g>
<g >
<title>PreCommit_Portals (9,675,377,423 samples, 0.10%)</title><rect x="472.9" y="517" width="1.2" height="15.0" fill="rgb(221,76,18)" rx="2" ry="2" />
<text  x="475.88" y="527.5" ></text>
</g>
<g >
<title>pg_class_aclmask (9,462,703,435 samples, 0.10%)</title><rect x="414.8" y="453" width="1.2" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="417.84" y="463.5" ></text>
</g>
<g >
<title>hash_search (6,616,736,326 samples, 0.07%)</title><rect x="868.5" y="453" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="871.45" y="463.5" ></text>
</g>
<g >
<title>fix_indexqual_clause (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="405" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="128.92" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,059,541,026 samples, 0.02%)</title><rect x="1168.4" y="757" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="1171.38" y="767.5" ></text>
</g>
<g >
<title>match_foreign_keys_to_quals (1,065,602,493 samples, 0.01%)</title><rect x="1161.5" y="757" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1164.50" y="767.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,130,840,438 samples, 0.01%)</title><rect x="1033.7" y="133" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1036.65" y="143.5" ></text>
</g>
<g >
<title>SimpleLruGetBankLock (1,248,430,212 samples, 0.01%)</title><rect x="478.4" y="453" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="481.44" y="463.5" ></text>
</g>
<g >
<title>ExecScanFetch (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="405" width="0.3" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1161.90" y="415.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (861,401,618 samples, 0.01%)</title><rect x="1149.7" y="677" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="1152.71" y="687.5" ></text>
</g>
<g >
<title>newNode (2,914,703,702 samples, 0.03%)</title><rect x="815.3" y="437" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="818.32" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,637,885,536 samples, 0.04%)</title><rect x="1038.2" y="309" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1041.22" y="319.5" ></text>
</g>
<g >
<title>strcmp@plt (857,777,509 samples, 0.01%)</title><rect x="858.3" y="341" width="0.1" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="861.27" y="351.5" ></text>
</g>
<g >
<title>RewriteQuery (1,285,029,963 samples, 0.01%)</title><rect x="93.3" y="757" width="0.2" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="96.32" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,243,767,671 samples, 0.01%)</title><rect x="462.0" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="465.03" y="495.5" ></text>
</g>
<g >
<title>__schedule (1,025,875,864 samples, 0.01%)</title><rect x="1148.5" y="437" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="1151.51" y="447.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (9,190,831,169 samples, 0.10%)</title><rect x="902.3" y="389" width="1.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="905.29" y="399.5" ></text>
</g>
<g >
<title>ReadBuffer (2,510,786,482 samples, 0.03%)</title><rect x="125.2" y="245" width="0.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="128.17" y="255.5" ></text>
</g>
<g >
<title>CheckCmdReplicaIdentity (876,009,001 samples, 0.01%)</title><rect x="38.2" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="41.19" y="767.5" ></text>
</g>
<g >
<title>PageAddItemExtended (2,666,092,026 samples, 0.03%)</title><rect x="81.0" y="757" width="0.4" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="84.02" y="767.5" ></text>
</g>
<g >
<title>pick_task_fair (2,563,077,922 samples, 0.03%)</title><rect x="162.5" y="309" width="0.3" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="165.50" y="319.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (19,975,917,919 samples, 0.21%)</title><rect x="339.4" y="245" width="2.5" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="342.44" y="255.5" ></text>
</g>
<g >
<title>XLogBytePosToEndRecPtr (1,047,573,834 samples, 0.01%)</title><rect x="497.1" y="469" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="500.11" y="479.5" ></text>
</g>
<g >
<title>create_index_paths (1,748,426,209 samples, 0.02%)</title><rect x="1130.1" y="757" width="0.2" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1133.09" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,070,284,669 samples, 0.01%)</title><rect x="298.6" y="229" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="301.65" y="239.5" ></text>
</g>
<g >
<title>ExecAssignScanProjectionInfo (80,949,909,224 samples, 0.85%)</title><rect x="423.4" y="421" width="10.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="426.43" y="431.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,652,768,482 samples, 0.02%)</title><rect x="1012.3" y="245" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1015.33" y="255.5" ></text>
</g>
<g >
<title>avg_vruntime (1,807,248,671 samples, 0.02%)</title><rect x="172.5" y="261" width="0.3" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="175.53" y="271.5" ></text>
</g>
<g >
<title>lappend (2,702,248,037 samples, 0.03%)</title><rect x="919.5" y="469" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="922.50" y="479.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,250,791,968 samples, 0.01%)</title><rect x="1060.7" y="453" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="1063.75" y="463.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,131,896,952 samples, 0.01%)</title><rect x="508.6" y="421" width="0.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="511.62" y="431.5" ></text>
</g>
<g >
<title>palloc0 (2,136,938,649 samples, 0.02%)</title><rect x="295.7" y="277" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="298.73" y="287.5" ></text>
</g>
<g >
<title>palloc (1,193,024,645 samples, 0.01%)</title><rect x="955.8" y="277" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="958.79" y="287.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (834,284,235 samples, 0.01%)</title><rect x="340.4" y="181" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="343.36" y="191.5" ></text>
</g>
<g >
<title>secure_write (153,938,505,102 samples, 1.62%)</title><rect x="190.8" y="533" width="19.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="193.81" y="543.5" ></text>
</g>
<g >
<title>CheckValidResultRel (9,379,171,006 samples, 0.10%)</title><rect x="419.2" y="453" width="1.2" height="15.0" fill="rgb(213,41,9)" rx="2" ry="2" />
<text  x="422.19" y="463.5" ></text>
</g>
<g >
<title>do_syscall_64 (62,401,311,695 samples, 0.66%)</title><rect x="482.9" y="405" width="7.8" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="485.92" y="415.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,274,505,317 samples, 0.02%)</title><rect x="416.1" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="419.08" y="463.5" ></text>
</g>
<g >
<title>pairingheap_remove_first (1,358,461,040 samples, 0.01%)</title><rect x="460.9" y="453" width="0.1" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="463.87" y="463.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,158,744,486 samples, 0.02%)</title><rect x="927.5" y="421" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="930.55" y="431.5" ></text>
</g>
<g >
<title>select_idle_core.isra.0 (10,636,862,659 samples, 0.11%)</title><rect x="204.1" y="261" width="1.3" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="207.10" y="271.5" ></text>
</g>
<g >
<title>palloc0 (2,089,964,897 samples, 0.02%)</title><rect x="919.9" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="922.91" y="463.5" ></text>
</g>
<g >
<title>BufTableHashCode (3,920,005,074 samples, 0.04%)</title><rect x="407.7" y="277" width="0.5" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="410.74" y="287.5" ></text>
</g>
<g >
<title>pairingheap_remove (1,499,436,575 samples, 0.02%)</title><rect x="460.9" y="469" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="463.85" y="479.5" ></text>
</g>
<g >
<title>ExecInterpExpr (8,895,610,604 samples, 0.09%)</title><rect x="270.8" y="357" width="1.1" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="273.79" y="367.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,459,245,168 samples, 0.05%)</title><rect x="865.7" y="453" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="868.74" y="463.5" ></text>
</g>
<g >
<title>newNode (3,356,112,656 samples, 0.04%)</title><rect x="441.0" y="405" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="444.02" y="415.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,111,364,764 samples, 0.01%)</title><rect x="1013.5" y="293" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1016.47" y="303.5" ></text>
</g>
<g >
<title>newNode (2,543,206,963 samples, 0.03%)</title><rect x="914.4" y="453" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="917.43" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,043,452,134 samples, 0.02%)</title><rect x="990.9" y="309" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="993.94" y="319.5" ></text>
</g>
<g >
<title>GlobalVisUpdate (1,581,039,342 samples, 0.02%)</title><rect x="312.4" y="213" width="0.2" height="15.0" fill="rgb(209,18,4)" rx="2" ry="2" />
<text  x="315.37" y="223.5" ></text>
</g>
<g >
<title>ProcessClientWriteInterrupt (3,487,741,602 samples, 0.04%)</title><rect x="191.1" y="517" width="0.4" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="194.09" y="527.5" ></text>
</g>
<g >
<title>sched_tick (1,651,809,250 samples, 0.02%)</title><rect x="795.8" y="405" width="0.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="798.85" y="415.5" ></text>
</g>
<g >
<title>copyObjectImpl (11,519,097,519 samples, 0.12%)</title><rect x="951.6" y="437" width="1.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="954.60" y="447.5" ></text>
</g>
<g >
<title>_bt_check_natts (1,833,667,383 samples, 0.02%)</title><rect x="339.1" y="213" width="0.3" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="342.13" y="223.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,090,849,003 samples, 0.01%)</title><rect x="1070.8" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1073.79" y="447.5" ></text>
</g>
<g >
<title>UnlockRelationId (9,114,444,285 samples, 0.10%)</title><rect x="238.7" y="437" width="1.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="241.66" y="447.5" ></text>
</g>
<g >
<title>ForEachLWLockHeldByMe (831,335,150 samples, 0.01%)</title><rect x="47.7" y="757" width="0.1" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="50.66" y="767.5" ></text>
</g>
<g >
<title>GrantLockLocal (1,086,331,687 samples, 0.01%)</title><rect x="941.4" y="341" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="944.37" y="351.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (7,886,371,088 samples, 0.08%)</title><rect x="276.0" y="405" width="1.0" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="278.97" y="415.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (805,165,882 samples, 0.01%)</title><rect x="808.1" y="309" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="811.05" y="319.5" ></text>
</g>
<g >
<title>index_getattr (3,262,946,556 samples, 0.03%)</title><rect x="124.8" y="245" width="0.4" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="127.77" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,242,631,238 samples, 0.01%)</title><rect x="395.3" y="277" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="398.26" y="287.5" ></text>
</g>
<g >
<title>ExecBSUpdateTriggers (1,019,362,941 samples, 0.01%)</title><rect x="42.0" y="757" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="44.95" y="767.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,189,829,023 samples, 0.01%)</title><rect x="125.6" y="213" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="128.62" y="223.5" ></text>
</g>
<g >
<title>uint32_hash (1,052,820,281 samples, 0.01%)</title><rect x="1068.1" y="421" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1071.08" y="431.5" ></text>
</g>
<g >
<title>cost_qual_eval_node (9,461,220,071 samples, 0.10%)</title><rect x="1045.1" y="469" width="1.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1048.12" y="479.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,136,436,407 samples, 0.05%)</title><rect x="851.1" y="357" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="854.09" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="325" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="128.92" y="335.5" ></text>
</g>
<g >
<title>WaitEventSetWaitBlock (180,303,081,918 samples, 1.90%)</title><rect x="155.4" y="501" width="22.4" height="15.0" fill="rgb(250,211,50)" rx="2" ry="2" />
<text  x="158.39" y="511.5" >W..</text>
</g>
<g >
<title>clause_selectivity_ext (1,757,677,062 samples, 0.02%)</title><rect x="1011.3" y="261" width="0.2" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1014.30" y="271.5" ></text>
</g>
<g >
<title>IsBinaryTidClause (3,218,707,837 samples, 0.03%)</title><rect x="1024.8" y="341" width="0.4" height="15.0" fill="rgb(214,41,9)" rx="2" ry="2" />
<text  x="1027.83" y="351.5" ></text>
</g>
<g >
<title>bms_add_member (1,756,146,984 samples, 0.02%)</title><rect x="897.2" y="469" width="0.2" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="900.21" y="479.5" ></text>
</g>
<g >
<title>is_opclause (2,055,225,190 samples, 0.02%)</title><rect x="1153.2" y="757" width="0.3" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="1156.23" y="767.5" ></text>
</g>
<g >
<title>recomputeNamespacePath (900,990,722 samples, 0.01%)</title><rect x="1175.6" y="757" width="0.1" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="1178.56" y="767.5" ></text>
</g>
<g >
<title>UnregisterSnapshot (3,067,127,739 samples, 0.03%)</title><rect x="460.7" y="517" width="0.3" height="15.0" fill="rgb(212,33,7)" rx="2" ry="2" />
<text  x="463.66" y="527.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (12,786,574,773 samples, 0.13%)</title><rect x="252.0" y="453" width="1.6" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="254.97" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,667,430,260 samples, 0.05%)</title><rect x="1051.3" y="421" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1054.30" y="431.5" ></text>
</g>
<g >
<title>palloc (1,117,422,828 samples, 0.01%)</title><rect x="105.6" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="108.63" y="415.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (8,616,254,190 samples, 0.09%)</title><rect x="367.4" y="293" width="1.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="370.44" y="303.5" ></text>
</g>
<g >
<title>remove_useless_self_joins (1,478,075,117 samples, 0.02%)</title><rect x="1176.5" y="757" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1179.49" y="767.5" ></text>
</g>
<g >
<title>ReleaseCatCache (2,858,182,690 samples, 0.03%)</title><rect x="825.6" y="341" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="828.64" y="351.5" ></text>
</g>
<g >
<title>smgrDoPendingSyncs (1,536,521,933 samples, 0.02%)</title><rect x="530.6" y="517" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="533.60" y="527.5" ></text>
</g>
<g >
<title>clauselist_selectivity (2,414,353,957 samples, 0.03%)</title><rect x="1011.2" y="293" width="0.3" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="1014.24" y="303.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,527,539,068 samples, 0.02%)</title><rect x="700.3" y="517" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="703.34" y="527.5" ></text>
</g>
<g >
<title>add_tabstat_xact_level (8,149,466,275 samples, 0.09%)</title><rect x="394.5" y="325" width="1.0" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="397.48" y="335.5" ></text>
</g>
<g >
<title>ReceiveSharedInvalidMessages (1,036,319,413 samples, 0.01%)</title><rect x="820.6" y="405" width="0.1" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="823.56" y="415.5" ></text>
</g>
<g >
<title>ReadBufferExtended (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="277" width="4.4" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="98.68" y="287.5" ></text>
</g>
<g >
<title>__x64_sys_futex (939,032,409 samples, 0.01%)</title><rect x="369.9" y="181" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="372.95" y="191.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (985,418,336 samples, 0.01%)</title><rect x="370.2" y="197" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="373.23" y="207.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,357,235,434 samples, 0.02%)</title><rect x="1115.8" y="677" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1118.80" y="687.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (42,260,924,630 samples, 0.44%)</title><rect x="324.6" y="261" width="5.2" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="327.58" y="271.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturnSwitchContext (882,247,439 samples, 0.01%)</title><rect x="42.9" y="757" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="45.90" y="767.5" ></text>
</g>
<g >
<title>simplify_function (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="645" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1176.62" y="655.5" ></text>
</g>
<g >
<title>ServerLoop (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="677" width="950.7" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="134.98" y="687.5" >ServerLoop</text>
</g>
<g >
<title>BufferIsPermanent (1,085,533,439 samples, 0.01%)</title><rect x="1147.5" y="677" width="0.1" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="1150.49" y="687.5" ></text>
</g>
<g >
<title>uint32_hash (1,267,514,501 samples, 0.01%)</title><rect x="454.3" y="357" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="457.26" y="367.5" ></text>
</g>
<g >
<title>PortalDefineQuery (1,561,070,689 samples, 0.02%)</title><rect x="224.7" y="581" width="0.1" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="227.65" y="591.5" ></text>
</g>
<g >
<title>HeapTupleIsSurelyDead (6,319,342,600 samples, 0.07%)</title><rect x="305.4" y="245" width="0.8" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="308.39" y="255.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple_internal (2,248,056,616 samples, 0.02%)</title><rect x="271.6" y="277" width="0.3" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="274.61" y="287.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="581" width="2.6" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="87.32" y="591.5" ></text>
</g>
<g >
<title>palloc (1,480,616,207 samples, 0.02%)</title><rect x="275.2" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="278.17" y="383.5" ></text>
</g>
<g >
<title>CreateExprContext (10,720,407,354 samples, 0.11%)</title><rect x="422.1" y="405" width="1.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="425.10" y="415.5" ></text>
</g>
<g >
<title>ExecutorRun (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="597" width="2.6" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="87.32" y="607.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,462,793,306 samples, 0.02%)</title><rect x="911.4" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="914.40" y="431.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (2,873,574,301 samples, 0.03%)</title><rect x="1047.0" y="389" width="0.3" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1049.98" y="399.5" ></text>
</g>
<g >
<title>get_hash_entry (1,278,846,436 samples, 0.01%)</title><rect x="217.0" y="533" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="220.00" y="543.5" ></text>
</g>
<g >
<title>hash_bytes (2,926,031,216 samples, 0.03%)</title><rect x="239.4" y="373" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="242.42" y="383.5" ></text>
</g>
<g >
<title>heapam_tuple_update (1,900,194,037 samples, 0.02%)</title><rect x="1150.9" y="757" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1153.91" y="767.5" ></text>
</g>
<g >
<title>tlist_matches_tupdesc (1,273,436,538 samples, 0.01%)</title><rect x="433.3" y="389" width="0.2" height="15.0" fill="rgb(222,79,19)" rx="2" ry="2" />
<text  x="436.33" y="399.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (853,335,304 samples, 0.01%)</title><rect x="458.9" y="405" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="461.95" y="415.5" ></text>
</g>
<g >
<title>pull_varnos (9,276,758,413 samples, 0.10%)</title><rect x="972.8" y="405" width="1.1" height="15.0" fill="rgb(229,112,26)" rx="2" ry="2" />
<text  x="975.78" y="415.5" ></text>
</g>
<g >
<title>raw_parser (24,091,795,515 samples, 0.25%)</title><rect x="871.0" y="565" width="3.0" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="873.97" y="575.5" ></text>
</g>
<g >
<title>tag_hash (4,028,218,010 samples, 0.04%)</title><rect x="947.6" y="309" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="950.56" y="319.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (3,032,536,838 samples, 0.03%)</title><rect x="263.6" y="389" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="266.61" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCacheListWithOwner (1,343,227,099 samples, 0.01%)</title><rect x="961.5" y="357" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="964.50" y="367.5" ></text>
</g>
<g >
<title>newNode (6,940,980,472 samples, 0.07%)</title><rect x="412.7" y="485" width="0.9" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="415.72" y="495.5" ></text>
</g>
<g >
<title>generate_bitmap_or_paths (883,946,251 samples, 0.01%)</title><rect x="1140.3" y="757" width="0.1" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="1143.34" y="767.5" ></text>
</g>
<g >
<title>LWLockRelease (1,682,077,661 samples, 0.02%)</title><rect x="513.5" y="421" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="516.54" y="431.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,472,065,685 samples, 0.07%)</title><rect x="862.5" y="453" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="865.51" y="463.5" ></text>
</g>
<g >
<title>dlist_init (1,515,478,741 samples, 0.02%)</title><rect x="1132.8" y="757" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="1135.83" y="767.5" ></text>
</g>
<g >
<title>deconstruct_recurse (27,350,000,172 samples, 0.29%)</title><rect x="973.9" y="453" width="3.4" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="976.95" y="463.5" ></text>
</g>
<g >
<title>oper (33,790,488,937 samples, 0.36%)</title><rect x="850.0" y="405" width="4.2" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="853.02" y="415.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple_internal (6,103,149,002 samples, 0.06%)</title><rect x="291.0" y="213" width="0.8" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="294.04" y="223.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,203,778,135 samples, 0.01%)</title><rect x="302.7" y="133" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="305.74" y="143.5" ></text>
</g>
<g >
<title>tag_hash (2,538,361,496 samples, 0.03%)</title><rect x="824.4" y="357" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="827.41" y="367.5" ></text>
</g>
<g >
<title>_bt_search (15,558,409,019 samples, 0.16%)</title><rect x="85.0" y="325" width="1.9" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="87.98" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,711,003,896 samples, 0.02%)</title><rect x="934.8" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="937.83" y="351.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,877,611,726 samples, 0.04%)</title><rect x="963.7" y="341" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="966.70" y="351.5" ></text>
</g>
<g >
<title>SetHintBits (8,169,091,049 samples, 0.09%)</title><rect x="307.6" y="213" width="1.0" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="310.58" y="223.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,177,298,718 samples, 0.04%)</title><rect x="807.8" y="325" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="810.76" y="335.5" ></text>
</g>
<g >
<title>AllocSetFree (4,230,157,226 samples, 0.04%)</title><rect x="28.8" y="757" width="0.5" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="31.81" y="767.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,140,081,123 samples, 0.01%)</title><rect x="271.7" y="245" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="274.73" y="255.5" ></text>
</g>
<g >
<title>assign_collations_walker (2,783,200,458 samples, 0.03%)</title><rect x="807.2" y="341" width="0.4" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="810.21" y="351.5" ></text>
</g>
<g >
<title>palloc (1,860,637,968 samples, 0.02%)</title><rect x="345.7" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="348.67" y="367.5" ></text>
</g>
<g >
<title>ExecScanExtended (24,353,337,778 samples, 0.26%)</title><rect x="281.7" y="389" width="3.0" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="284.70" y="399.5" ></text>
</g>
<g >
<title>create_lateral_join_info (1,199,866,302 samples, 0.01%)</title><rect x="1130.4" y="757" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1133.44" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,031,542,365 samples, 0.01%)</title><rect x="897.9" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="900.91" y="431.5" ></text>
</g>
<g >
<title>func_volatile (6,131,590,802 samples, 0.06%)</title><rect x="959.3" y="309" width="0.8" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="962.34" y="319.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,822,761,138 samples, 0.02%)</title><rect x="973.6" y="293" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="976.58" y="303.5" ></text>
</g>
<g >
<title>sentinel_ok (906,066,789 samples, 0.01%)</title><rect x="264.0" y="389" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="267.02" y="399.5" ></text>
</g>
<g >
<title>ExecAllocTableSlot (6,181,886,718 samples, 0.07%)</title><rect x="432.6" y="373" width="0.7" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="435.55" y="383.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (4,168,536,776 samples, 0.04%)</title><rect x="932.4" y="309" width="0.5" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="935.43" y="319.5" ></text>
</g>
<g >
<title>palloc (1,445,386,929 samples, 0.02%)</title><rect x="872.5" y="501" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="875.49" y="511.5" ></text>
</g>
<g >
<title>tag_hash (2,500,998,837 samples, 0.03%)</title><rect x="96.1" y="149" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="99.12" y="159.5" ></text>
</g>
<g >
<title>bms_num_members (972,660,857 samples, 0.01%)</title><rect x="965.9" y="373" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="968.88" y="383.5" ></text>
</g>
<g >
<title>AllocSetCheck (5,435,251,852 samples, 0.06%)</title><rect x="467.9" y="453" width="0.7" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="470.90" y="463.5" ></text>
</g>
<g >
<title>get_relation_info (166,158,712,493 samples, 1.75%)</title><rect x="928.4" y="421" width="20.6" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="931.39" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (882,689,905 samples, 0.01%)</title><rect x="1029.8" y="245" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1032.81" y="255.5" ></text>
</g>
<g >
<title>cost_seqscan (10,494,040,890 samples, 0.11%)</title><rect x="1022.1" y="389" width="1.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1025.13" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,325,089,505 samples, 0.01%)</title><rect x="818.9" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="821.91" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,424,507,637 samples, 0.01%)</title><rect x="441.3" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="444.25" y="383.5" ></text>
</g>
<g >
<title>LockTagHashCode (2,999,363,135 samples, 0.03%)</title><rect x="370.5" y="277" width="0.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="373.52" y="287.5" ></text>
</g>
<g >
<title>record_times (2,576,479,143 samples, 0.03%)</title><rect x="166.9" y="325" width="0.3" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="169.92" y="335.5" ></text>
</g>
<g >
<title>get_loop_count (932,670,034 samples, 0.01%)</title><rect x="1016.9" y="357" width="0.1" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1019.90" y="367.5" ></text>
</g>
<g >
<title>fmgr_isbuiltin (1,819,566,473 samples, 0.02%)</title><rect x="1036.4" y="277" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1039.38" y="287.5" ></text>
</g>
<g >
<title>SearchSysCache3 (6,232,369,608 samples, 0.07%)</title><rect x="435.0" y="389" width="0.8" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="438.01" y="399.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (5,027,989,614 samples, 0.05%)</title><rect x="449.0" y="389" width="0.7" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="452.05" y="399.5" ></text>
</g>
<g >
<title>XLogInsert (51,403,149,350 samples, 0.54%)</title><rect x="385.5" y="341" width="6.4" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="388.47" y="351.5" ></text>
</g>
<g >
<title>CommitTransactionCommandInternal (525,957,338,666 samples, 5.54%)</title><rect x="465.6" y="549" width="65.3" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="468.61" y="559.5" >CommitT..</text>
</g>
<g >
<title>AtEOXact_RelationCache (1,146,073,206 samples, 0.01%)</title><rect x="32.3" y="757" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="35.32" y="767.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (1,780,223,852 samples, 0.02%)</title><rect x="326.7" y="181" width="0.2" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="329.67" y="191.5" ></text>
</g>
<g >
<title>_bt_binsrch (7,061,984,186 samples, 0.07%)</title><rect x="126.9" y="261" width="0.9" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="129.89" y="271.5" ></text>
</g>
<g >
<title>log_heap_update (86,591,488,933 samples, 0.91%)</title><rect x="383.4" y="357" width="10.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="386.45" y="367.5" ></text>
</g>
<g >
<title>pick_next_task_fair (1,450,384,440 samples, 0.02%)</title><rect x="209.2" y="389" width="0.1" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="212.15" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64_impl (1,579,435,864 samples, 0.02%)</title><rect x="510.4" y="469" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="513.42" y="479.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,422,067,079 samples, 0.01%)</title><rect x="1102.3" y="741" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1105.28" y="751.5" ></text>
</g>
<g >
<title>UnregisterSnapshotFromOwner (2,127,659,819 samples, 0.02%)</title><rect x="265.5" y="485" width="0.2" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="268.47" y="495.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (9,018,183,352 samples, 0.09%)</title><rect x="290.7" y="261" width="1.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="293.68" y="271.5" ></text>
</g>
<g >
<title>DataChecksumsEnabled (1,236,196,135 samples, 0.01%)</title><rect x="40.2" y="757" width="0.2" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="43.20" y="767.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,326,501,154 samples, 0.04%)</title><rect x="959.7" y="261" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="962.68" y="271.5" ></text>
</g>
<g >
<title>pick_next_task_fair (9,499,136,403 samples, 0.10%)</title><rect x="162.4" y="325" width="1.1" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="165.36" y="335.5" ></text>
</g>
<g >
<title>new_list (1,481,222,569 samples, 0.02%)</title><rect x="984.4" y="389" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="987.36" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,047,713,772 samples, 0.01%)</title><rect x="1019.1" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1022.07" y="303.5" ></text>
</g>
<g >
<title>ensure_tabstat_xact_level (9,260,409,533 samples, 0.10%)</title><rect x="394.3" y="341" width="1.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="397.34" y="351.5" ></text>
</g>
<g >
<title>ExecInitQual (34,021,616,794 samples, 0.36%)</title><rect x="437.2" y="421" width="4.2" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="440.22" y="431.5" ></text>
</g>
<g >
<title>BackendMain (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="629" width="950.7" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="134.98" y="639.5" >BackendMain</text>
</g>
<g >
<title>new_list (1,992,794,331 samples, 0.02%)</title><rect x="982.6" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="985.61" y="431.5" ></text>
</g>
<g >
<title>__intel_pmu_enable_all.isra.0 (1,115,051,174 samples, 0.01%)</title><rect x="484.8" y="229" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="487.81" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,174,944,328 samples, 0.01%)</title><rect x="979.5" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="982.46" y="415.5" ></text>
</g>
<g >
<title>new_list (1,758,678,086 samples, 0.02%)</title><rect x="1020.1" y="293" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1023.05" y="303.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,298,905,167 samples, 0.02%)</title><rect x="996.7" y="261" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="999.67" y="271.5" ></text>
</g>
<g >
<title>__memcg_slab_free_hook (4,401,940,673 samples, 0.05%)</title><rect x="182.2" y="325" width="0.6" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="185.21" y="335.5" ></text>
</g>
<g >
<title>all_rows_selectable (10,091,820,964 samples, 0.11%)</title><rect x="1033.2" y="213" width="1.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1036.16" y="223.5" ></text>
</g>
<g >
<title>kmalloc_reserve (11,304,652,183 samples, 0.12%)</title><rect x="197.2" y="357" width="1.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="200.17" y="367.5" ></text>
</g>
<g >
<title>GetSnapshotDataReuse (1,014,414,981 samples, 0.01%)</title><rect x="233.7" y="517" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="236.73" y="527.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (7,535,931,795 samples, 0.08%)</title><rect x="443.3" y="325" width="0.9" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="446.28" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (961,357,959 samples, 0.01%)</title><rect x="855.2" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="858.20" y="351.5" ></text>
</g>
<g >
<title>FullTransactionIdNewer (1,072,332,593 samples, 0.01%)</title><rect x="233.3" y="517" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="236.28" y="527.5" ></text>
</g>
<g >
<title>ProcessQuery (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="597" width="0.3" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="1161.90" y="607.5" ></text>
</g>
<g >
<title>ksys_lseek (3,002,303,051 samples, 0.03%)</title><rect x="932.6" y="277" width="0.3" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="935.56" y="287.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (879,019,800 samples, 0.01%)</title><rect x="449.6" y="341" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="452.56" y="351.5" ></text>
</g>
<g >
<title>tts_buffer_heap_store_tuple (5,544,778,497 samples, 0.06%)</title><rect x="297.5" y="245" width="0.7" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="300.49" y="255.5" ></text>
</g>
<g >
<title>skb_set_owner_w (1,106,692,491 samples, 0.01%)</title><rect x="200.4" y="389" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="203.38" y="399.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,947,894,605 samples, 0.02%)</title><rect x="435.4" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="438.43" y="351.5" ></text>
</g>
<g >
<title>AssignTransactionId (84,900,018,892 samples, 0.89%)</title><rect x="364.2" y="341" width="10.5" height="15.0" fill="rgb(205,2,0)" rx="2" ry="2" />
<text  x="367.20" y="351.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,742,213,195 samples, 0.02%)</title><rect x="496.9" y="389" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="499.86" y="399.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,133,976,642 samples, 0.01%)</title><rect x="475.6" y="421" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="478.58" y="431.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (914,461,069 samples, 0.01%)</title><rect x="847.2" y="357" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="850.15" y="367.5" ></text>
</g>
<g >
<title>uint32_hash (912,972,750 samples, 0.01%)</title><rect x="948.8" y="341" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="951.83" y="351.5" ></text>
</g>
<g >
<title>lappend_int (3,342,691,536 samples, 0.04%)</title><rect x="922.4" y="453" width="0.4" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="925.37" y="463.5" ></text>
</g>
<g >
<title>LockHeldByMe (8,132,022,399 samples, 0.09%)</title><rect x="830.6" y="373" width="1.0" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="833.60" y="383.5" ></text>
</g>
<g >
<title>parse_analyze_fixedparams (834,503,549 samples, 0.01%)</title><rect x="1166.8" y="757" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1169.83" y="767.5" ></text>
</g>
<g >
<title>MemoryContextCheck (2,182,782,120,789 samples, 22.98%)</title><rect x="531.0" y="565" width="271.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="533.97" y="575.5" >MemoryContextCheck</text>
</g>
<g >
<title>__x64_sys_sendto (133,924,531,042 samples, 1.41%)</title><rect x="192.3" y="453" width="16.6" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="195.26" y="463.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (6,647,036,568 samples, 0.07%)</title><rect x="1051.1" y="469" width="0.8" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1054.06" y="479.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (849,705,335 samples, 0.01%)</title><rect x="376.9" y="341" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="379.90" y="351.5" ></text>
</g>
<g >
<title>SearchCatCache3 (4,373,206,228 samples, 0.05%)</title><rect x="1010.0" y="277" width="0.5" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1012.97" y="287.5" ></text>
</g>
<g >
<title>ReadBuffer (23,093,042,986 samples, 0.24%)</title><rect x="407.0" y="389" width="2.9" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="410.01" y="399.5" ></text>
</g>
<g >
<title>BufferGetPage (1,119,619,484 samples, 0.01%)</title><rect x="337.4" y="197" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="340.41" y="207.5" ></text>
</g>
<g >
<title>hash_search (2,920,447,556 samples, 0.03%)</title><rect x="938.9" y="389" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="941.89" y="399.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,464,609,796 samples, 0.02%)</title><rect x="700.4" y="501" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="703.35" y="511.5" ></text>
</g>
<g >
<title>assign_collations_walker (11,805,154,874 samples, 0.12%)</title><rect x="806.8" y="389" width="1.5" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="809.82" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (16,703,105,660 samples, 0.18%)</title><rect x="901.4" y="405" width="2.0" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="904.36" y="415.5" ></text>
</g>
<g >
<title>palloc0 (2,223,660,100 samples, 0.02%)</title><rect x="835.7" y="405" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="838.73" y="415.5" ></text>
</g>
<g >
<title>GetTransactionSnapshot (28,364,077,390 samples, 0.30%)</title><rect x="231.2" y="549" width="3.5" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="234.19" y="559.5" ></text>
</g>
<g >
<title>ExecutePlan (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="517" width="1.9" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="129.31" y="527.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,075,382,853 samples, 0.01%)</title><rect x="104.0" y="421" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="106.97" y="431.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesMVCC (1,166,524,747 samples, 0.01%)</title><rect x="52.7" y="757" width="0.2" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="55.73" y="767.5" ></text>
</g>
<g >
<title>ServerLoop (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="725" width="0.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="1161.90" y="735.5" ></text>
</g>
<g >
<title>uint32_hash (1,305,256,897 samples, 0.01%)</title><rect x="939.1" y="373" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="942.09" y="383.5" ></text>
</g>
<g >
<title>mdnblocks (18,856,658,541 samples, 0.20%)</title><rect x="936.0" y="277" width="2.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="939.01" y="287.5" ></text>
</g>
<g >
<title>choose_nelem_alloc (917,090,933 samples, 0.01%)</title><rect x="1063.2" y="437" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1066.21" y="447.5" ></text>
</g>
<g >
<title>name_matches_visible_ENR (818,451,792 samples, 0.01%)</title><rect x="833.4" y="453" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="836.40" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,042,355,854 samples, 0.02%)</title><rect x="355.0" y="245" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="357.97" y="255.5" ></text>
</g>
<g >
<title>simplify_function (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="661" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1137.90" y="671.5" ></text>
</g>
<g >
<title>dequeue_task (1,470,213,493 samples, 0.02%)</title><rect x="168.6" y="325" width="0.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="171.64" y="335.5" ></text>
</g>
<g >
<title>exprType (869,653,476 samples, 0.01%)</title><rect x="970.6" y="389" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="973.62" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,589,656,573 samples, 0.03%)</title><rect x="1075.9" y="485" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1078.94" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (912,201,975 samples, 0.01%)</title><rect x="420.6" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="423.57" y="431.5" ></text>
</g>
<g >
<title>LockBuffer (1,548,111,851 samples, 0.02%)</title><rect x="58.6" y="757" width="0.2" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="61.64" y="767.5" ></text>
</g>
<g >
<title>heap_form_tuple (22,783,301,473 samples, 0.24%)</title><rect x="396.3" y="373" width="2.9" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="399.33" y="383.5" ></text>
</g>
<g >
<title>ExecInitResultSlot (7,397,664,030 samples, 0.08%)</title><rect x="432.4" y="389" width="0.9" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="435.41" y="399.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,476,295,136 samples, 0.02%)</title><rect x="350.1" y="277" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="353.09" y="287.5" ></text>
</g>
<g >
<title>message_level_is_interesting (907,266,001 samples, 0.01%)</title><rect x="530.1" y="501" width="0.1" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="533.10" y="511.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,315,463,390 samples, 0.06%)</title><rect x="1038.8" y="325" width="0.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1041.82" y="335.5" ></text>
</g>
<g >
<title>MemoryContextDelete (8,699,406,808 samples, 0.09%)</title><rect x="225.1" y="565" width="1.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="228.14" y="575.5" ></text>
</g>
<g >
<title>palloc (1,928,977,454 samples, 0.02%)</title><rect x="1012.0" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1014.99" y="255.5" ></text>
</g>
<g >
<title>_bt_unlockbuf (2,729,292,462 samples, 0.03%)</title><rect x="326.9" y="213" width="0.3" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="329.90" y="223.5" ></text>
</g>
<g >
<title>uint32_hash (1,080,469,251 samples, 0.01%)</title><rect x="403.2" y="325" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="406.21" y="335.5" ></text>
</g>
<g >
<title>merge_collation_state (824,572,524 samples, 0.01%)</title><rect x="810.9" y="309" width="0.1" height="15.0" fill="rgb(220,73,17)" rx="2" ry="2" />
<text  x="813.89" y="319.5" ></text>
</g>
<g >
<title>palloc (2,160,234,594 samples, 0.02%)</title><rect x="1007.8" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1010.78" y="255.5" ></text>
</g>
<g >
<title>hash_bytes (2,702,366,718 samples, 0.03%)</title><rect x="300.2" y="85" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="303.18" y="95.5" ></text>
</g>
<g >
<title>makeString (3,527,472,818 samples, 0.04%)</title><rect x="1118.8" y="725" width="0.4" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="1121.80" y="735.5" ></text>
</g>
<g >
<title>pgstat_report_stat (5,144,382,469 samples, 0.05%)</title><rect x="1079.2" y="597" width="0.6" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1082.19" y="607.5" ></text>
</g>
<g >
<title>fix_expr_common (1,412,138,210 samples, 0.01%)</title><rect x="903.3" y="373" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="906.25" y="383.5" ></text>
</g>
<g >
<title>ReleaseSysCache (974,644,851 samples, 0.01%)</title><rect x="415.2" y="421" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="418.23" y="431.5" ></text>
</g>
<g >
<title>relation_close (2,194,595,434 samples, 0.02%)</title><rect x="803.5" y="517" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="806.47" y="527.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,285,275,842 samples, 0.01%)</title><rect x="407.3" y="293" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="410.34" y="303.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (845,683,774 samples, 0.01%)</title><rect x="304.2" y="229" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="307.20" y="239.5" ></text>
</g>
<g >
<title>newNode (1,960,626,267 samples, 0.02%)</title><rect x="841.4" y="341" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="844.39" y="351.5" ></text>
</g>
<g >
<title>LWLockRelease (1,823,458,955 samples, 0.02%)</title><rect x="57.9" y="757" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="60.88" y="767.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="613" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1137.90" y="623.5" ></text>
</g>
<g >
<title>exprType (868,698,999 samples, 0.01%)</title><rect x="970.4" y="373" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="973.45" y="383.5" ></text>
</g>
<g >
<title>ExecIndexScan (26,252,383,711 samples, 0.28%)</title><rect x="281.6" y="421" width="3.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="284.57" y="431.5" ></text>
</g>
<g >
<title>ReadBufferExtended (1,220,476,765 samples, 0.01%)</title><rect x="125.8" y="229" width="0.1" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="128.76" y="239.5" ></text>
</g>
<g >
<title>cfree@GLIBC_2.2.5 (6,931,937,967 samples, 0.07%)</title><rect x="242.8" y="357" width="0.8" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="245.77" y="367.5" ></text>
</g>
<g >
<title>bms_free (1,300,200,075 samples, 0.01%)</title><rect x="1121.4" y="757" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1124.40" y="767.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (2,254,144,985 samples, 0.02%)</title><rect x="960.4" y="309" width="0.2" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="963.37" y="319.5" ></text>
</g>
<g >
<title>lappend (2,035,850,554 samples, 0.02%)</title><rect x="1019.0" y="341" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1021.96" y="351.5" ></text>
</g>
<g >
<title>bms_add_member (2,930,318,870 samples, 0.03%)</title><rect x="996.6" y="277" width="0.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="999.59" y="287.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,850,301,915 samples, 0.04%)</title><rect x="398.0" y="325" width="0.5" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="400.98" y="335.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,843,610,984 samples, 0.02%)</title><rect x="1148.5" y="597" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1151.49" y="607.5" ></text>
</g>
<g >
<title>_bt_checkkeys (4,599,176,588 samples, 0.05%)</title><rect x="124.2" y="261" width="0.6" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="127.19" y="271.5" ></text>
</g>
<g >
<title>_bt_check_compare (2,002,016,725 samples, 0.02%)</title><rect x="1158.9" y="261" width="0.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1161.90" y="271.5" ></text>
</g>
<g >
<title>do_futex (1,159,361,250 samples, 0.01%)</title><rect x="353.6" y="245" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="356.56" y="255.5" ></text>
</g>
<g >
<title>get_expr_width (8,363,737,268 samples, 0.09%)</title><rect x="1046.3" y="469" width="1.0" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="1049.30" y="479.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (3,068,442,279 samples, 0.03%)</title><rect x="424.8" y="325" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="427.83" y="335.5" ></text>
</g>
<g >
<title>AllocSetFree (1,377,574,450 samples, 0.01%)</title><rect x="224.3" y="533" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="227.34" y="543.5" ></text>
</g>
<g >
<title>CreateExecutorState (14,264,388,931 samples, 0.15%)</title><rect x="411.8" y="501" width="1.8" height="15.0" fill="rgb(228,105,25)" rx="2" ry="2" />
<text  x="414.81" y="511.5" ></text>
</g>
<g >
<title>log_heap_prune_and_freeze (1,320,139,140 samples, 0.01%)</title><rect x="1146.6" y="741" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1149.59" y="751.5" ></text>
</g>
<g >
<title>core_yyalloc (1,290,085,197 samples, 0.01%)</title><rect x="872.3" y="485" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="875.31" y="495.5" ></text>
</g>
<g >
<title>select_task_rq_fair (25,324,989,724 samples, 0.27%)</title><rect x="202.5" y="309" width="3.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="205.47" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,025,742,017 samples, 0.02%)</title><rect x="402.0" y="309" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="405.00" y="319.5" ></text>
</g>
<g >
<title>MakeTupleTableSlot (20,613,715,631 samples, 0.22%)</title><rect x="278.5" y="389" width="2.6" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="281.54" y="399.5" ></text>
</g>
<g >
<title>update_rq_clock (1,020,469,441 samples, 0.01%)</title><rect x="174.5" y="341" width="0.1" height="15.0" fill="rgb(231,119,28)" rx="2" ry="2" />
<text  x="177.51" y="351.5" ></text>
</g>
<g >
<title>palloc0 (3,874,946,738 samples, 0.04%)</title><rect x="273.0" y="357" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="275.99" y="367.5" ></text>
</g>
<g >
<title>RestrictInfoIsTidQual (5,207,209,347 samples, 0.05%)</title><rect x="1024.6" y="373" width="0.7" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1027.63" y="383.5" ></text>
</g>
<g >
<title>list_make1_impl (2,464,495,171 samples, 0.03%)</title><rect x="1020.0" y="309" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1022.98" y="319.5" ></text>
</g>
<g >
<title>InitPlan (373,938,104,811 samples, 3.94%)</title><rect x="413.7" y="501" width="46.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="416.65" y="511.5" >Init..</text>
</g>
<g >
<title>pg_atomic_read_u32 (2,882,942,892 samples, 0.03%)</title><rect x="223.2" y="517" width="0.4" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="226.22" y="527.5" ></text>
</g>
<g >
<title>list_free_deep (6,660,950,972 samples, 0.07%)</title><rect x="977.4" y="453" width="0.8" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="980.35" y="463.5" ></text>
</g>
<g >
<title>__smp_call_single_queue (1,715,457,186 samples, 0.02%)</title><rect x="207.9" y="309" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="210.91" y="319.5" ></text>
</g>
<g >
<title>palloc (967,655,672 samples, 0.01%)</title><rect x="435.9" y="405" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="438.89" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (962,411,422 samples, 0.01%)</title><rect x="223.1" y="517" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="226.10" y="527.5" ></text>
</g>
<g >
<title>build_base_rel_tlists (47,954,430,661 samples, 0.50%)</title><rect x="950.1" y="469" width="5.9" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="953.09" y="479.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (4,647,149,261 samples, 0.05%)</title><rect x="12.0" y="741" width="0.6" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="15.01" y="751.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (842,645,450 samples, 0.01%)</title><rect x="1102.3" y="709" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1105.34" y="719.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,313,668,856 samples, 0.01%)</title><rect x="1102.3" y="725" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="1105.29" y="735.5" ></text>
</g>
<g >
<title>secure_raw_write (148,229,443,391 samples, 1.56%)</title><rect x="191.5" y="517" width="18.4" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="194.52" y="527.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="469" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="128.92" y="479.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,864,039,787 samples, 0.04%)</title><rect x="807.1" y="357" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="810.10" y="367.5" ></text>
</g>
<g >
<title>perf_event_task_tick (862,496,573 samples, 0.01%)</title><rect x="758.2" y="405" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="761.17" y="415.5" ></text>
</g>
<g >
<title>ExecBuildProjectionInfo (70,220,710,197 samples, 0.74%)</title><rect x="423.6" y="373" width="8.8" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="426.64" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,151,762,859 samples, 0.01%)</title><rect x="931.3" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="934.30" y="351.5" ></text>
</g>
<g >
<title>FreeSnapshot (1,402,240,458 samples, 0.01%)</title><rect x="234.9" y="533" width="0.2" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="237.94" y="543.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="533" width="0.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="129.16" y="543.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (842,466,714 samples, 0.01%)</title><rect x="201.9" y="277" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="204.88" y="287.5" ></text>
</g>
<g >
<title>__x64_sys_futex (4,084,323,253 samples, 0.04%)</title><rect x="366.7" y="213" width="0.5" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="369.66" y="223.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (12,429,046,130 samples, 0.13%)</title><rect x="881.4" y="453" width="1.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="884.45" y="463.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,607,597,318 samples, 0.05%)</title><rect x="1057.9" y="469" width="0.6" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1060.94" y="479.5" ></text>
</g>
<g >
<title>LockReassignCurrentOwner (5,268,468,147 samples, 0.06%)</title><rect x="227.4" y="533" width="0.7" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="230.44" y="543.5" ></text>
</g>
<g >
<title>AllocSetCheck (2,006,501,442 samples, 0.02%)</title><rect x="78.7" y="741" width="0.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="81.68" y="751.5" ></text>
</g>
<g >
<title>ExecTypeFromTLInternal (3,521,668,026 samples, 0.04%)</title><rect x="456.4" y="421" width="0.5" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="459.45" y="431.5" ></text>
</g>
<g >
<title>PortalRun (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="629" width="0.3" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="1161.90" y="639.5" ></text>
</g>
<g >
<title>bms_is_member (1,163,224,068 samples, 0.01%)</title><rect x="457.1" y="453" width="0.1" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="460.06" y="463.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturn (18,342,592,921 samples, 0.19%)</title><rect x="269.6" y="389" width="2.3" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="272.62" y="399.5" ></text>
</g>
<g >
<title>ReleaseSysCache (2,499,400,377 samples, 0.03%)</title><rect x="91.2" y="757" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="94.21" y="767.5" ></text>
</g>
<g >
<title>tas (1,971,937,571 samples, 0.02%)</title><rect x="510.7" y="485" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="513.65" y="495.5" ></text>
</g>
<g >
<title>AllocSetFree (941,209,339 samples, 0.01%)</title><rect x="965.7" y="341" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="968.73" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,380,762,066 samples, 0.01%)</title><rect x="273.3" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="276.29" y="351.5" ></text>
</g>
<g >
<title>expr_setup_walker (3,831,205,821 samples, 0.04%)</title><rect x="438.4" y="357" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="441.44" y="367.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,403,820,256 samples, 0.01%)</title><rect x="249.4" y="437" width="0.2" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="252.38" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_read_membarrier_u64_impl (1,602,670,032 samples, 0.02%)</title><rect x="497.5" y="453" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="500.54" y="463.5" ></text>
</g>
<g >
<title>transformUpdateTargetList (85,790,551,445 samples, 0.90%)</title><rect x="833.8" y="485" width="10.7" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="836.81" y="495.5" ></text>
</g>
<g >
<title>ItemPointerGetOffsetNumber (1,019,371,740 samples, 0.01%)</title><rect x="384.7" y="341" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="387.74" y="351.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (2,311,850,812 samples, 0.02%)</title><rect x="440.3" y="389" width="0.2" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="443.25" y="399.5" ></text>
</g>
<g >
<title>AtEOXact_SPI (884,138,263 samples, 0.01%)</title><rect x="472.2" y="517" width="0.1" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="475.18" y="527.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (989,377,248 samples, 0.01%)</title><rect x="128.5" y="757" width="0.1" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="131.48" y="767.5" ></text>
</g>
<g >
<title>StartTransaction (34,349,667,076 samples, 0.36%)</title><rect x="1073.3" y="549" width="4.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1076.30" y="559.5" ></text>
</g>
<g >
<title>makeConst (3,028,017,921 samples, 0.03%)</title><rect x="841.3" y="357" width="0.3" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="844.25" y="367.5" ></text>
</g>
<g >
<title>set_pathtarget_cost_width (20,332,361,205 samples, 0.21%)</title><rect x="1044.8" y="485" width="2.5" height="15.0" fill="rgb(213,41,9)" rx="2" ry="2" />
<text  x="1047.81" y="495.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (2,507,474,067 samples, 0.03%)</title><rect x="233.4" y="517" width="0.3" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="236.42" y="527.5" ></text>
</g>
<g >
<title>equal (5,450,526,052 samples, 0.06%)</title><rect x="913.5" y="485" width="0.6" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="916.45" y="495.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="325" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="89.91" y="335.5" ></text>
</g>
<g >
<title>WALInsertLockRelease (5,746,045,270 samples, 0.06%)</title><rect x="389.1" y="309" width="0.7" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="392.09" y="319.5" ></text>
</g>
<g >
<title>BufferGetPage (1,143,949,173 samples, 0.01%)</title><rect x="304.3" y="245" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="307.30" y="255.5" ></text>
</g>
<g >
<title>palloc0 (1,886,519,888 samples, 0.02%)</title><rect x="459.5" y="469" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="462.52" y="479.5" ></text>
</g>
<g >
<title>tts_buffer_heap_get_heap_tuple (935,071,196 samples, 0.01%)</title><rect x="1188.2" y="757" width="0.2" height="15.0" fill="rgb(213,37,9)" rx="2" ry="2" />
<text  x="1191.24" y="767.5" ></text>
</g>
<g >
<title>sysvec_thermal (923,566,761 samples, 0.01%)</title><rect x="757.6" y="501" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="760.62" y="511.5" ></text>
</g>
<g >
<title>BackendStartup (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="661" width="950.7" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="134.98" y="671.5" >BackendStartup</text>
</g>
<g >
<title>newNode (3,558,396,307 samples, 0.04%)</title><rect x="1117.3" y="725" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1120.27" y="735.5" ></text>
</g>
<g >
<title>secure_read (865,425,836 samples, 0.01%)</title><rect x="1177.7" y="757" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="1180.67" y="767.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,719,755,924 samples, 0.02%)</title><rect x="387.4" y="293" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="390.42" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,227,969,591 samples, 0.01%)</title><rect x="347.6" y="309" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="350.63" y="319.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (900,213,628 samples, 0.01%)</title><rect x="906.2" y="501" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="909.23" y="511.5" ></text>
</g>
<g >
<title>table_close (1,685,973,160 samples, 0.02%)</title><rect x="862.2" y="517" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="865.16" y="527.5" ></text>
</g>
<g >
<title>palloc (1,458,323,546 samples, 0.02%)</title><rect x="931.3" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="934.28" y="367.5" ></text>
</g>
<g >
<title>eval_const_expressions (1,179,819,471 samples, 0.01%)</title><rect x="1173.6" y="741" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1176.61" y="751.5" ></text>
</g>
<g >
<title>__libc_recv (70,858,963,896 samples, 0.75%)</title><rect x="178.3" y="501" width="8.8" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="181.31" y="511.5" ></text>
</g>
<g >
<title>MemoryContextReset (9,319,088,324 samples, 0.10%)</title><rect x="467.5" y="501" width="1.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="470.51" y="511.5" ></text>
</g>
<g >
<title>AllocSetFree (869,006,467 samples, 0.01%)</title><rect x="235.0" y="501" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="237.99" y="511.5" ></text>
</g>
<g >
<title>eqsel_internal (52,506,232,691 samples, 0.55%)</title><rect x="1029.6" y="277" width="6.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1032.58" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,760,677,219 samples, 0.02%)</title><rect x="403.0" y="325" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="405.99" y="335.5" ></text>
</g>
<g >
<title>__alloc_skb (28,806,849,351 samples, 0.30%)</title><rect x="196.8" y="373" width="3.6" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="199.79" y="383.5" ></text>
</g>
<g >
<title>pgstat_get_xact_stack_level (3,747,034,649 samples, 0.04%)</title><rect x="395.0" y="309" width="0.5" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="398.03" y="319.5" ></text>
</g>
<g >
<title>ReindexIsProcessingIndex (890,045,309 samples, 0.01%)</title><rect x="292.8" y="293" width="0.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="295.83" y="303.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (8,473,538,731 samples, 0.09%)</title><rect x="1022.4" y="373" width="1.0" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1025.38" y="383.5" ></text>
</g>
<g >
<title>newNode (7,362,381,084 samples, 0.08%)</title><rect x="458.2" y="453" width="0.9" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="461.17" y="463.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,002,740,373 samples, 0.01%)</title><rect x="716.8" y="517" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="719.81" y="527.5" ></text>
</g>
<g >
<title>create_bitmap_heap_path (14,178,456,686 samples, 0.15%)</title><rect x="988.6" y="389" width="1.7" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="991.56" y="399.5" ></text>
</g>
<g >
<title>index_rescan (8,654,904,616 samples, 0.09%)</title><rect x="342.4" y="325" width="1.0" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="345.35" y="335.5" ></text>
</g>
<g >
<title>__sysvec_thermal (1,796,385,233 samples, 0.02%)</title><rect x="758.7" y="501" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="761.68" y="511.5" ></text>
</g>
<g >
<title>makeVar (4,856,979,870 samples, 0.05%)</title><rect x="934.4" y="389" width="0.6" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="937.44" y="399.5" ></text>
</g>
<g >
<title>ttwu_queue_wakelist (2,690,036,969 samples, 0.03%)</title><rect x="494.6" y="293" width="0.4" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="497.64" y="303.5" ></text>
</g>
<g >
<title>make_one_rel (1,382,885,742 samples, 0.01%)</title><rect x="1160.2" y="757" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1163.22" y="767.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (1,300,997,649 samples, 0.01%)</title><rect x="208.9" y="453" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="211.90" y="463.5" ></text>
</g>
<g >
<title>add_base_rels_to_query (191,200,975,109 samples, 2.01%)</title><rect x="926.1" y="469" width="23.8" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="929.14" y="479.5" >a..</text>
</g>
<g >
<title>__sigsetjmp (1,543,698,367 samples, 0.02%)</title><rect x="187.7" y="549" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="190.67" y="559.5" ></text>
</g>
<g >
<title>palloc (1,340,895,630 samples, 0.01%)</title><rect x="878.8" y="453" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="881.80" y="463.5" ></text>
</g>
<g >
<title>newNode (4,713,835,695 samples, 0.05%)</title><rect x="1119.2" y="725" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1122.24" y="735.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="661" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1188.13" y="671.5" ></text>
</g>
<g >
<title>bms_free (1,866,175,187 samples, 0.02%)</title><rect x="348.8" y="389" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="351.80" y="399.5" ></text>
</g>
<g >
<title>_int_free_merge_chunk (3,077,173,146 samples, 0.03%)</title><rect x="150.4" y="533" width="0.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="153.44" y="543.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,301,975,661 samples, 0.01%)</title><rect x="430.7" y="309" width="0.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="433.69" y="319.5" ></text>
</g>
<g >
<title>SearchSysCache3 (4,903,138,442 samples, 0.05%)</title><rect x="1009.9" y="293" width="0.6" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1012.90" y="303.5" ></text>
</g>
<g >
<title>table_index_fetch_tuple (134,714,766,553 samples, 1.42%)</title><rect x="296.7" y="293" width="16.8" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="299.73" y="303.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (971,078,648 samples, 0.01%)</title><rect x="128.4" y="517" width="0.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="131.36" y="527.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (1,094,972,911 samples, 0.01%)</title><rect x="700.4" y="469" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="703.40" y="479.5" ></text>
</g>
<g >
<title>get_typavgwidth (5,012,345,057 samples, 0.05%)</title><rect x="1046.7" y="453" width="0.6" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="1049.71" y="463.5" ></text>
</g>
<g >
<title>list_concat (2,483,982,814 samples, 0.03%)</title><rect x="899.5" y="485" width="0.3" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="902.51" y="495.5" ></text>
</g>
<g >
<title>pg_leftmost_one_pos32 (3,611,597,302 samples, 0.04%)</title><rect x="1170.0" y="757" width="0.4" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1172.96" y="767.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,332,299,509 samples, 0.01%)</title><rect x="353.5" y="277" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="356.55" y="287.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (1,119,748,847 samples, 0.01%)</title><rect x="343.9" y="437" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="346.87" y="447.5" ></text>
</g>
<g >
<title>initStringInfoInternal (2,060,931,240 samples, 0.02%)</title><rect x="189.1" y="549" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="192.09" y="559.5" ></text>
</g>
<g >
<title>RelationClose (1,687,787,884 samples, 0.02%)</title><rect x="803.5" y="501" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="806.53" y="511.5" ></text>
</g>
<g >
<title>BuildQueryCompletionString (8,586,789,117 samples, 0.09%)</title><rect x="217.8" y="565" width="1.0" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="220.78" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,010,116,149 samples, 0.01%)</title><rect x="804.2" y="517" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="807.21" y="527.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVacuumHorizon (10,507,186,579 samples, 0.11%)</title><rect x="1147.5" y="709" width="1.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="1150.49" y="719.5" ></text>
</g>
<g >
<title>GetMemoryChunkMethodID (1,231,635,752 samples, 0.01%)</title><rect x="49.5" y="757" width="0.1" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="52.48" y="767.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (992,589,045 samples, 0.01%)</title><rect x="416.6" y="469" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="419.56" y="479.5" ></text>
</g>
<g >
<title>newNode (1,802,259,594 samples, 0.02%)</title><rect x="855.1" y="373" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="858.09" y="383.5" ></text>
</g>
<g >
<title>table_open (16,116,025,680 samples, 0.17%)</title><rect x="862.4" y="517" width="2.0" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="865.37" y="527.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (909,258,733 samples, 0.01%)</title><rect x="527.2" y="405" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="530.21" y="415.5" ></text>
</g>
<g >
<title>pg_ulltoa_n (1,060,818,876 samples, 0.01%)</title><rect x="1171.0" y="757" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1174.04" y="767.5" ></text>
</g>
<g >
<title>palloc (1,274,648,296 samples, 0.01%)</title><rect x="447.2" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="450.18" y="367.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,169,007,255 samples, 0.01%)</title><rect x="1067.1" y="357" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1070.08" y="367.5" ></text>
</g>
<g >
<title>ExecProject (53,270,691,348 samples, 0.56%)</title><rect x="285.3" y="357" width="6.6" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="288.27" y="367.5" ></text>
</g>
<g >
<title>MemoryContextStrdup (2,392,756,520 samples, 0.03%)</title><rect x="819.1" y="421" width="0.3" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="822.08" y="431.5" ></text>
</g>
<g >
<title>new_list (2,355,390,454 samples, 0.02%)</title><rect x="817.2" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="820.17" y="431.5" ></text>
</g>
<g >
<title>LockBuffer (7,826,489,698 samples, 0.08%)</title><rect x="298.4" y="261" width="0.9" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="301.37" y="271.5" ></text>
</g>
<g >
<title>scanner_init (21,312,651,118 samples, 0.22%)</title><rect x="871.3" y="549" width="2.7" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="874.32" y="559.5" ></text>
</g>
<g >
<title>makeSimpleA_Expr (14,497,614,755 samples, 0.15%)</title><rect x="1118.0" y="741" width="1.8" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="1121.03" y="751.5" ></text>
</g>
<g >
<title>do_syscall_64 (2,429,826,462 samples, 0.03%)</title><rect x="375.9" y="245" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="378.86" y="255.5" ></text>
</g>
<g >
<title>BufferAlloc (14,280,389,768 samples, 0.15%)</title><rect x="407.7" y="293" width="1.7" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="410.65" y="303.5" ></text>
</g>
<g >
<title>task_tick_fair (1,634,035,115 samples, 0.02%)</title><rect x="758.3" y="405" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="761.32" y="415.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (8,059,178,501 samples, 0.08%)</title><rect x="99.1" y="197" width="1.0" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="102.12" y="207.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (2,043,681,896 samples, 0.02%)</title><rect x="496.8" y="437" width="0.3" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="499.83" y="447.5" ></text>
</g>
<g >
<title>LWLockRelease (3,110,380,159 samples, 0.03%)</title><rect x="478.0" y="453" width="0.4" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="481.05" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,058,863,698 samples, 0.01%)</title><rect x="422.9" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="425.91" y="335.5" ></text>
</g>
<g >
<title>palloc0 (1,624,127,061 samples, 0.02%)</title><rect x="460.4" y="501" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="463.37" y="511.5" ></text>
</g>
<g >
<title>AllocSetAllocFromNewBlock (14,901,972,023 samples, 0.16%)</title><rect x="1048.8" y="453" width="1.8" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1051.79" y="463.5" ></text>
</g>
<g >
<title>ReleaseCatCache (940,355,641 samples, 0.01%)</title><rect x="1013.5" y="277" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1016.49" y="287.5" ></text>
</g>
<g >
<title>__schedule (2,862,292,208 samples, 0.03%)</title><rect x="209.1" y="421" width="0.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="212.12" y="431.5" ></text>
</g>
<g >
<title>XLogBeginInsert (816,248,209 samples, 0.01%)</title><rect x="511.5" y="485" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="514.54" y="495.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,142,155,808 samples, 0.05%)</title><rect x="850.2" y="357" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="853.17" y="367.5" ></text>
</g>
<g >
<title>ReadBuffer (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="293" width="2.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="103.12" y="303.5" ></text>
</g>
<g >
<title>AllocSetFreeIndex (4,024,081,545 samples, 0.04%)</title><rect x="29.3" y="757" width="0.5" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="32.33" y="767.5" ></text>
</g>
<g >
<title>heap_hot_search_buffer (1,268,353,177 samples, 0.01%)</title><rect x="1146.2" y="757" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="1149.20" y="767.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (1,042,896,149 samples, 0.01%)</title><rect x="370.2" y="213" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="373.22" y="223.5" ></text>
</g>
<g >
<title>__ceil_sse41 (1,640,365,336 samples, 0.02%)</title><rect x="1011.0" y="293" width="0.2" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1013.97" y="303.5" ></text>
</g>
<g >
<title>__memcg_slab_free_hook (2,924,991,926 samples, 0.03%)</title><rect x="184.5" y="357" width="0.4" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="187.50" y="367.5" ></text>
</g>
<g >
<title>bms_add_members (2,912,886,645 samples, 0.03%)</title><rect x="357.9" y="373" width="0.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="360.95" y="383.5" ></text>
</g>
<g >
<title>hash_search (14,018,803,430 samples, 0.15%)</title><rect x="838.7" y="341" width="1.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="841.69" y="351.5" ></text>
</g>
<g >
<title>__memcg_slab_post_alloc_hook (3,997,365,074 samples, 0.04%)</title><rect x="197.8" y="325" width="0.5" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="200.85" y="335.5" ></text>
</g>
<g >
<title>CleanupTempFiles (898,295,428 samples, 0.01%)</title><rect x="469.6" y="501" width="0.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="472.59" y="511.5" ></text>
</g>
<g >
<title>bms_union (4,309,413,053 samples, 0.05%)</title><rect x="880.7" y="469" width="0.5" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="883.70" y="479.5" ></text>
</g>
<g >
<title>IsSharedRelation (985,478,905 samples, 0.01%)</title><rect x="824.9" y="373" width="0.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="827.85" y="383.5" ></text>
</g>
<g >
<title>palloc0 (5,624,354,747 samples, 0.06%)</title><rect x="915.1" y="453" width="0.7" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="918.05" y="463.5" ></text>
</g>
<g >
<title>main (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="741" width="2.3" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="126.99" y="751.5" ></text>
</g>
<g >
<title>perf_adjust_freq_unthr_context (829,911,732 samples, 0.01%)</title><rect x="758.2" y="389" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="761.18" y="399.5" ></text>
</g>
<g >
<title>match_opclause_to_indexcol (19,415,346,635 samples, 0.20%)</title><rect x="1019.5" y="325" width="2.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1022.50" y="335.5" ></text>
</g>
<g >
<title>int4pl (1,328,731,727 samples, 0.01%)</title><rect x="1152.7" y="757" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1155.68" y="767.5" ></text>
</g>
<g >
<title>LWLockRelease (1,510,380,699 samples, 0.02%)</title><rect x="474.7" y="501" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="477.74" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,154,751,353 samples, 0.01%)</title><rect x="817.3" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="820.28" y="399.5" ></text>
</g>
<g >
<title>palloc (1,125,713,903 samples, 0.01%)</title><rect x="975.9" y="389" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="978.93" y="399.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="501" width="6.8" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="98.68" y="511.5" ></text>
</g>
<g >
<title>PageGetItemId (1,309,363,456 samples, 0.01%)</title><rect x="378.4" y="341" width="0.2" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="381.42" y="351.5" ></text>
</g>
<g >
<title>ExecUpdate (1,024,370,211 samples, 0.01%)</title><rect x="46.4" y="757" width="0.2" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="49.45" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,473,220,600 samples, 0.05%)</title><rect x="837.0" y="341" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="840.04" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,251,540,050 samples, 0.01%)</title><rect x="481.6" y="453" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="484.57" y="463.5" ></text>
</g>
<g >
<title>btgettuple (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="373" width="6.8" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="98.68" y="383.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,022,726,833 samples, 0.01%)</title><rect x="919.0" y="421" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="921.98" y="431.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,365,648,447 samples, 0.01%)</title><rect x="402.6" y="341" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="405.63" y="351.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (27,614,468,811 samples, 0.29%)</title><rect x="299.6" y="181" width="3.4" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="302.57" y="191.5" ></text>
</g>
<g >
<title>ExecInitIndexScan (1,419,654,076 samples, 0.01%)</title><rect x="43.8" y="757" width="0.2" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="46.81" y="767.5" ></text>
</g>
<g >
<title>get_hash_entry (10,595,588,826 samples, 0.11%)</title><rect x="1064.3" y="437" width="1.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1067.26" y="447.5" ></text>
</g>
<g >
<title>OidFunctionCall4Coll (59,296,769,116 samples, 0.62%)</title><rect x="1029.2" y="325" width="7.4" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="1032.24" y="335.5" ></text>
</g>
<g >
<title>PageGetItemId (5,720,175,042 samples, 0.06%)</title><rect x="381.5" y="325" width="0.8" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="384.55" y="335.5" ></text>
</g>
<g >
<title>fastgetattr (47,587,988,114 samples, 0.50%)</title><rect x="1001.1" y="229" width="5.9" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="1004.09" y="239.5" ></text>
</g>
<g >
<title>expand_virtual_generated_columns (1,339,622,241 samples, 0.01%)</title><rect x="1057.6" y="485" width="0.1" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1060.55" y="495.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (1,145,867,747 samples, 0.01%)</title><rect x="57.1" y="757" width="0.1" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="60.07" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,720,528,223 samples, 0.03%)</title><rect x="1076.9" y="517" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1079.89" y="527.5" ></text>
</g>
<g >
<title>hash_search (6,398,303,154 samples, 0.07%)</title><rect x="216.7" y="565" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="219.72" y="575.5" ></text>
</g>
<g >
<title>AtEOXact_Namespace (992,747,665 samples, 0.01%)</title><rect x="31.9" y="757" width="0.1" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="34.86" y="767.5" ></text>
</g>
<g >
<title>__strncpy_avx2 (3,401,895,061 samples, 0.04%)</title><rect x="444.3" y="341" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="447.35" y="351.5" ></text>
</g>
<g >
<title>PinBufferForBlock (18,622,825,598 samples, 0.20%)</title><rect x="407.5" y="309" width="2.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="410.55" y="319.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,955,762,576 samples, 0.04%)</title><rect x="104.2" y="437" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="107.19" y="447.5" ></text>
</g>
<g >
<title>exec_rt_fetch (1,357,216,704 samples, 0.01%)</title><rect x="447.6" y="421" width="0.2" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="450.59" y="431.5" ></text>
</g>
<g >
<title>table_close (1,591,714,420 samples, 0.02%)</title><rect x="922.9" y="469" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="925.89" y="479.5" ></text>
</g>
<g >
<title>CreateExprContext (9,638,262,546 samples, 0.10%)</title><rect x="346.6" y="373" width="1.2" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="349.60" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (7,027,172,539 samples, 0.07%)</title><rect x="524.7" y="405" width="0.9" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="527.72" y="415.5" ></text>
</g>
<g >
<title>newNode (1,808,640,712 samples, 0.02%)</title><rect x="818.8" y="437" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="821.85" y="447.5" ></text>
</g>
<g >
<title>parserOpenTable (107,105,006,756 samples, 1.13%)</title><rect x="820.1" y="469" width="13.3" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="823.08" y="479.5" ></text>
</g>
<g >
<title>subquery_planner (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="597" width="3.4" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="105.66" y="607.5" ></text>
</g>
<g >
<title>palloc (1,446,961,588 samples, 0.02%)</title><rect x="1057.1" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1060.06" y="431.5" ></text>
</g>
<g >
<title>tas (817,403,001 samples, 0.01%)</title><rect x="373.5" y="229" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="376.48" y="239.5" ></text>
</g>
<g >
<title>tas (1,310,923,419 samples, 0.01%)</title><rect x="512.9" y="437" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="515.91" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,294,883,064 samples, 0.02%)</title><rect x="436.2" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="439.18" y="399.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,654,762,377 samples, 0.02%)</title><rect x="356.0" y="261" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="358.96" y="271.5" ></text>
</g>
<g >
<title>LockBuffer (6,404,083,422 samples, 0.07%)</title><rect x="375.5" y="357" width="0.8" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="378.46" y="367.5" ></text>
</g>
<g >
<title>preprocess_expression (1,623,858,946 samples, 0.02%)</title><rect x="1173.6" y="757" width="0.2" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1176.56" y="767.5" ></text>
</g>
<g >
<title>table_tuple_update (362,290,379,192 samples, 3.81%)</title><rect x="350.5" y="405" width="45.0" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="353.54" y="415.5" >tabl..</text>
</g>
<g >
<title>RemoveLocalLock (21,577,847,645 samples, 0.23%)</title><rect x="523.5" y="453" width="2.7" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="526.54" y="463.5" ></text>
</g>
<g >
<title>do_syscall_64 (2,560,841,102 samples, 0.03%)</title><rect x="389.3" y="181" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="392.33" y="191.5" ></text>
</g>
<g >
<title>newNode (3,105,755,563 samples, 0.03%)</title><rect x="1118.9" y="709" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1121.86" y="719.5" ></text>
</g>
<g >
<title>ExecCheckOneRelPerms (10,729,063,846 samples, 0.11%)</title><rect x="414.7" y="469" width="1.3" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="417.69" y="479.5" ></text>
</g>
<g >
<title>__get_user_8 (3,582,605,400 samples, 0.04%)</title><rect x="176.5" y="373" width="0.4" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="179.48" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (860,739,598 samples, 0.01%)</title><rect x="456.2" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="459.25" y="431.5" ></text>
</g>
<g >
<title>palloc0 (3,386,812,845 samples, 0.04%)</title><rect x="347.4" y="325" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="350.37" y="335.5" ></text>
</g>
<g >
<title>select_idle_sibling (998,523,712 samples, 0.01%)</title><rect x="494.4" y="261" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="497.38" y="271.5" ></text>
</g>
<g >
<title>BlockNumberIsValid (3,319,414,135 samples, 0.03%)</title><rect x="33.7" y="757" width="0.5" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="36.75" y="767.5" ></text>
</g>
<g >
<title>heap_getattr (4,816,202,866 samples, 0.05%)</title><rect x="360.6" y="373" width="0.6" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="363.58" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,065,557,850 samples, 0.01%)</title><rect x="1020.1" y="261" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1023.11" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,963,384,221 samples, 0.03%)</title><rect x="1120.2" y="709" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1123.20" y="719.5" ></text>
</g>
<g >
<title>index_getattr (3,750,110,238 samples, 0.04%)</title><rect x="335.8" y="213" width="0.5" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="338.79" y="223.5" ></text>
</g>
<g >
<title>DataChecksumsEnabled (2,519,069,873 samples, 0.03%)</title><rect x="325.3" y="213" width="0.3" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="328.32" y="223.5" ></text>
</g>
<g >
<title>hash_seq_init (1,564,119,665 samples, 0.02%)</title><rect x="473.1" y="501" width="0.2" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="476.07" y="511.5" ></text>
</g>
<g >
<title>contain_volatile_functions (1,155,061,140 samples, 0.01%)</title><rect x="1019.8" y="309" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1022.81" y="319.5" ></text>
</g>
<g >
<title>palloc (1,725,777,066 samples, 0.02%)</title><rect x="459.9" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="462.87" y="479.5" ></text>
</g>
<g >
<title>tag_hash (2,565,523,029 samples, 0.03%)</title><rect x="941.9" y="309" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="944.91" y="319.5" ></text>
</g>
<g >
<title>build_path_tlist (10,975,456,450 samples, 0.12%)</title><rect x="885.4" y="437" width="1.4" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="888.43" y="447.5" ></text>
</g>
<g >
<title>sched_clock (901,013,050 samples, 0.01%)</title><rect x="174.2" y="245" width="0.1" height="15.0" fill="rgb(212,33,7)" rx="2" ry="2" />
<text  x="177.17" y="255.5" ></text>
</g>
<g >
<title>hash_bytes (2,690,927,558 samples, 0.03%)</title><rect x="374.4" y="245" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="377.39" y="255.5" ></text>
</g>
<g >
<title>get_tablespace (2,949,882,865 samples, 0.03%)</title><rect x="989.5" y="341" width="0.4" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="992.53" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,798,892,536 samples, 0.03%)</title><rect x="436.5" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="439.47" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,269,414,067 samples, 0.01%)</title><rect x="299.2" y="245" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="302.19" y="255.5" ></text>
</g>
<g >
<title>palloc0 (2,379,722,436 samples, 0.03%)</title><rect x="921.9" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="924.93" y="431.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,433,159,472 samples, 0.02%)</title><rect x="824.2" y="341" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="827.23" y="351.5" ></text>
</g>
<g >
<title>ExecInitResultRelation (46,416,045,119 samples, 0.49%)</title><rect x="450.6" y="453" width="5.8" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="453.59" y="463.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,076,031,459 samples, 0.01%)</title><rect x="299.0" y="149" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="302.03" y="159.5" ></text>
</g>
<g >
<title>QueryRewrite (97,752,273,266 samples, 1.03%)</title><rect x="858.7" y="549" width="12.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="861.67" y="559.5" ></text>
</g>
<g >
<title>tag_hash (2,643,448,698 samples, 0.03%)</title><rect x="924.2" y="389" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="927.24" y="399.5" ></text>
</g>
<g >
<title>ProcessQuery (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="581" width="1.9" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="126.99" y="591.5" ></text>
</g>
<g >
<title>add_rte_to_flat_rtable (17,346,201,916 samples, 0.18%)</title><rect x="896.1" y="485" width="2.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="899.15" y="495.5" ></text>
</g>
<g >
<title>TupleDescInitEntryCollation (1,099,723,413 samples, 0.01%)</title><rect x="445.2" y="373" width="0.1" height="15.0" fill="rgb(206,8,1)" rx="2" ry="2" />
<text  x="448.17" y="383.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (126,619,588,249 samples, 1.33%)</title><rect x="136.1" y="581" width="15.7" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="139.09" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,032,174,357 samples, 0.01%)</title><rect x="1058.7" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1061.65" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,280,465,590 samples, 0.01%)</title><rect x="513.2" y="405" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="516.19" y="415.5" ></text>
</g>
<g >
<title>_int_free_merge_chunk (3,443,088,962 samples, 0.04%)</title><rect x="243.2" y="341" width="0.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="246.20" y="351.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,005,342,993 samples, 0.01%)</title><rect x="96.9" y="149" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="99.95" y="159.5" ></text>
</g>
<g >
<title>get_hash_value (2,969,344,742 samples, 0.03%)</title><rect x="370.5" y="261" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="373.52" y="271.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (2,325,544,446 samples, 0.02%)</title><rect x="210.2" y="581" width="0.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="213.21" y="591.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (1,165,256,242,214 samples, 12.27%)</title><rect x="266.5" y="517" width="144.7" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="269.46" y="527.5" >standard_ExecutorRun</text>
</g>
<g >
<title>ReleaseBuffer (2,705,207,434 samples, 0.03%)</title><rect x="282.2" y="309" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="285.21" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_or_u32 (994,938,998 samples, 0.01%)</title><rect x="326.0" y="197" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="328.97" y="207.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (169,171,375,849 samples, 1.78%)</title><rect x="156.5" y="469" width="21.0" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="159.46" y="479.5" ></text>
</g>
<g >
<title>exit_to_user_mode_loop (9,995,977,451 samples, 0.11%)</title><rect x="176.0" y="437" width="1.2" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="178.99" y="447.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,243,406,618 samples, 0.03%)</title><rect x="513.1" y="437" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="516.08" y="447.5" ></text>
</g>
<g >
<title>palloc (7,272,655,412 samples, 0.08%)</title><rect x="946.0" y="405" width="0.9" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="949.00" y="415.5" ></text>
</g>
<g >
<title>add_row_identity_columns (12,547,417,716 samples, 0.13%)</title><rect x="920.7" y="469" width="1.5" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="923.67" y="479.5" ></text>
</g>
<g >
<title>set_plan_refs (46,896,551,139 samples, 0.49%)</title><rect x="898.4" y="501" width="5.8" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="901.35" y="511.5" ></text>
</g>
<g >
<title>bms_difference (2,699,995,990 samples, 0.03%)</title><rect x="358.5" y="373" width="0.4" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="361.54" y="383.5" ></text>
</g>
<g >
<title>security_socket_sendmsg (6,625,148,312 samples, 0.07%)</title><rect x="193.5" y="421" width="0.8" height="15.0" fill="rgb(243,174,41)" rx="2" ry="2" />
<text  x="196.53" y="431.5" ></text>
</g>
<g >
<title>hash_search (7,303,152,287 samples, 0.08%)</title><rect x="830.7" y="357" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="833.71" y="367.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (14,536,357,597 samples, 0.15%)</title><rect x="121.0" y="741" width="1.8" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="123.96" y="751.5" ></text>
</g>
<g >
<title>fmgr_info (3,038,949,307 samples, 0.03%)</title><rect x="427.9" y="325" width="0.4" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="430.93" y="335.5" ></text>
</g>
<g >
<title>fix_indexqual_clause (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="437" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="89.91" y="447.5" ></text>
</g>
<g >
<title>MakeTupleTableSlot (7,891,948,058 samples, 0.08%)</title><rect x="446.1" y="389" width="0.9" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="449.06" y="399.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (19,953,793,157 samples, 0.21%)</title><rect x="901.2" y="421" width="2.5" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="904.22" y="431.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (1,517,520,292 samples, 0.02%)</title><rect x="1188.0" y="757" width="0.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="1191.05" y="767.5" ></text>
</g>
<g >
<title>avc_has_perm_noaudit (1,640,177,286 samples, 0.02%)</title><rect x="180.7" y="357" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="183.72" y="367.5" ></text>
</g>
<g >
<title>perf_ctx_enable (4,000,200,386 samples, 0.04%)</title><rect x="164.5" y="309" width="0.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="167.51" y="319.5" ></text>
</g>
<g >
<title>MemoryContextDelete (13,720,581,297 samples, 0.14%)</title><rect x="251.9" y="469" width="1.7" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="254.86" y="479.5" ></text>
</g>
<g >
<title>get_oprrest (5,598,164,402 samples, 0.06%)</title><rect x="1036.6" y="325" width="0.7" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="1039.63" y="335.5" ></text>
</g>
<g >
<title>BufferDescriptorGetBuffer (958,600,686 samples, 0.01%)</title><rect x="101.8" y="165" width="0.2" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="104.85" y="175.5" ></text>
</g>
<g >
<title>contain_volatile_functions (15,414,103,464 samples, 0.16%)</title><rect x="958.8" y="389" width="1.9" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="961.75" y="399.5" ></text>
</g>
<g >
<title>make_fn_arguments (1,533,658,112 samples, 0.02%)</title><rect x="849.5" y="405" width="0.2" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="852.50" y="415.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,934,088,530 samples, 0.04%)</title><rect x="233.9" y="501" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="236.91" y="511.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (2,321,089,140 samples, 0.02%)</title><rect x="85.4" y="181" width="0.3" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="88.43" y="191.5" ></text>
</g>
<g >
<title>ExecInterpExprStillValid (1,454,045,854 samples, 0.02%)</title><rect x="44.9" y="757" width="0.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="47.88" y="767.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,490,671,235 samples, 0.02%)</title><rect x="39.2" y="757" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="42.25" y="767.5" ></text>
</g>
<g >
<title>newNode (2,205,756,683 samples, 0.02%)</title><rect x="837.9" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="840.88" y="383.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,681,513,101 samples, 0.02%)</title><rect x="388.9" y="277" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="391.88" y="287.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (8,860,694,746 samples, 0.09%)</title><rect x="827.8" y="309" width="1.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="830.80" y="319.5" ></text>
</g>
<g >
<title>do_futex (1,523,372,546 samples, 0.02%)</title><rect x="1148.5" y="517" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1151.51" y="527.5" ></text>
</g>
<g >
<title>RegisterSnapshotOnOwner (3,346,047,211 samples, 0.04%)</title><rect x="236.1" y="501" width="0.4" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="239.10" y="511.5" ></text>
</g>
<g >
<title>ExecTypeFromTL (35,857,944,324 samples, 0.38%)</title><rect x="441.5" y="405" width="4.4" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="444.49" y="415.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,330,345,544 samples, 0.01%)</title><rect x="886.6" y="357" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="889.57" y="367.5" ></text>
</g>
<g >
<title>_bt_lockbuf (966,676,862 samples, 0.01%)</title><rect x="125.5" y="245" width="0.1" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="128.48" y="255.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,637,450,430 samples, 0.02%)</title><rect x="430.7" y="341" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="433.67" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (10,188,174,359 samples, 0.11%)</title><rect x="10.4" y="757" width="1.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="13.36" y="767.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (10,854,884,136 samples, 0.11%)</title><rect x="1052.2" y="453" width="1.4" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1055.24" y="463.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,074,809,871 samples, 0.01%)</title><rect x="475.6" y="389" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="478.58" y="399.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,092,541,087 samples, 0.01%)</title><rect x="429.1" y="341" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="432.08" y="351.5" ></text>
</g>
<g >
<title>table_open (1,357,231,705 samples, 0.01%)</title><rect x="1186.0" y="757" width="0.2" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1189.02" y="767.5" ></text>
</g>
<g >
<title>ExecInitExprRec (10,632,105,674 samples, 0.11%)</title><rect x="438.9" y="405" width="1.3" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="441.92" y="415.5" ></text>
</g>
<g >
<title>ReadBuffer (1,220,476,765 samples, 0.01%)</title><rect x="125.8" y="245" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="128.76" y="255.5" ></text>
</g>
<g >
<title>sched_clock (908,296,298 samples, 0.01%)</title><rect x="174.5" y="309" width="0.1" height="15.0" fill="rgb(212,33,7)" rx="2" ry="2" />
<text  x="177.53" y="319.5" ></text>
</g>
<g >
<title>ExecInitNode (236,074,396,716 samples, 2.49%)</title><rect x="421.3" y="453" width="29.3" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="424.27" y="463.5" >Ex..</text>
</g>
<g >
<title>new_list (1,629,593,801 samples, 0.02%)</title><rect x="815.1" y="421" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="818.10" y="431.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,472,176,913 samples, 0.02%)</title><rect x="471.3" y="485" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="474.27" y="495.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,392,260,087 samples, 0.06%)</title><rect x="973.3" y="341" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="976.26" y="351.5" ></text>
</g>
<g >
<title>dequeue_entity (38,910,817,626 samples, 0.41%)</title><rect x="169.6" y="293" width="4.9" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="172.63" y="303.5" ></text>
</g>
<g >
<title>palloc (1,422,263,113 samples, 0.01%)</title><rect x="976.7" y="389" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="979.72" y="399.5" ></text>
</g>
<g >
<title>__task_rq_lock (11,195,369,100 samples, 0.12%)</title><rect x="492.8" y="293" width="1.3" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="495.76" y="303.5" ></text>
</g>
<g >
<title>palloc0 (3,212,267,982 samples, 0.03%)</title><rect x="1117.3" y="709" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1120.31" y="719.5" ></text>
</g>
<g >
<title>newNode (6,023,954,176 samples, 0.06%)</title><rect x="915.0" y="469" width="0.8" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="918.00" y="479.5" ></text>
</g>
<g >
<title>set_cheapest (3,139,797,377 samples, 0.03%)</title><rect x="1070.6" y="501" width="0.3" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1073.55" y="511.5" ></text>
</g>
<g >
<title>palloc (1,599,079,676 samples, 0.02%)</title><rect x="982.6" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="985.64" y="415.5" ></text>
</g>
<g >
<title>table_close (1,209,596,048 samples, 0.01%)</title><rect x="1185.8" y="757" width="0.1" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="1188.79" y="767.5" ></text>
</g>
<g >
<title>match_foreign_keys_to_quals (1,260,291,617 samples, 0.01%)</title><rect x="1042.2" y="469" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1045.21" y="479.5" ></text>
</g>
<g >
<title>FullTransactionIdAdvance (1,150,850,547 samples, 0.01%)</title><rect x="47.9" y="757" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="50.92" y="767.5" ></text>
</g>
<g >
<title>ExecOpenScanRelation (1,632,367,972 samples, 0.02%)</title><rect x="447.4" y="421" width="0.2" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="450.37" y="431.5" ></text>
</g>
<g >
<title>AllocSetFree (1,650,738,252 samples, 0.02%)</title><rect x="995.3" y="309" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="998.28" y="319.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (1,209,906,031 samples, 0.01%)</title><rect x="1051.7" y="357" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1054.71" y="367.5" ></text>
</g>
<g >
<title>schedule (2,921,975,741 samples, 0.03%)</title><rect x="209.1" y="437" width="0.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="212.12" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (3,138,386,281 samples, 0.03%)</title><rect x="918.8" y="437" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="921.82" y="447.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,766,461,597 samples, 0.03%)</title><rect x="301.6" y="117" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="304.57" y="127.5" ></text>
</g>
<g >
<title>btint4cmp (2,462,263,923 samples, 0.03%)</title><rect x="126.9" y="213" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="129.92" y="223.5" ></text>
</g>
<g >
<title>PredicateLockPage (1,014,689,819 samples, 0.01%)</title><rect x="329.1" y="229" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="332.12" y="239.5" ></text>
</g>
<g >
<title>bms_copy (3,120,360,996 samples, 0.03%)</title><rect x="880.8" y="453" width="0.4" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="883.77" y="463.5" ></text>
</g>
<g >
<title>IncrBufferRefCount (3,750,077,779 samples, 0.04%)</title><rect x="297.7" y="229" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="300.71" y="239.5" ></text>
</g>
<g >
<title>find_oper_cache_entry (18,469,993,783 samples, 0.19%)</title><rect x="850.8" y="389" width="2.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="853.81" y="399.5" ></text>
</g>
<g >
<title>palloc0 (4,273,196,698 samples, 0.04%)</title><rect x="1119.3" y="709" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1122.30" y="719.5" ></text>
</g>
<g >
<title>bms_add_member (2,587,559,067 samples, 0.03%)</title><rect x="979.3" y="453" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="982.29" y="463.5" ></text>
</g>
<g >
<title>btrescan (4,351,736,056 samples, 0.05%)</title><rect x="342.6" y="309" width="0.6" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="345.64" y="319.5" ></text>
</g>
<g >
<title>var_eq_const (3,293,837,182 samples, 0.03%)</title><rect x="1035.7" y="261" width="0.4" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="1038.69" y="271.5" ></text>
</g>
<g >
<title>make_rel_from_joinlist (1,463,993,323 samples, 0.02%)</title><rect x="1160.9" y="757" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1163.93" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,063,872,614 samples, 0.03%)</title><rect x="1045.9" y="437" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1048.89" y="447.5" ></text>
</g>
<g >
<title>sentinel_ok (6,141,065,098 samples, 0.06%)</title><rect x="148.9" y="533" width="0.8" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="151.93" y="543.5" ></text>
</g>
<g >
<title>__GI___strlcpy (2,873,225,329 samples, 0.03%)</title><rect x="853.7" y="373" width="0.3" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="856.66" y="383.5" ></text>
</g>
<g >
<title>list_nth (870,322,333 samples, 0.01%)</title><rect x="978.4" y="453" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="981.41" y="463.5" ></text>
</g>
<g >
<title>DynaHashAlloc (3,880,307,056 samples, 0.04%)</title><rect x="1060.3" y="453" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1063.26" y="463.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,829,340,167 samples, 0.04%)</title><rect x="1055.5" y="405" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1058.46" y="415.5" ></text>
</g>
<g >
<title>__sysvec_thermal (897,509,062 samples, 0.01%)</title><rect x="757.6" y="485" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="760.62" y="495.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (1,180,539,107 samples, 0.01%)</title><rect x="1071.5" y="565" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1074.46" y="575.5" ></text>
</g>
<g >
<title>VARATT_IS_EXTERNAL_ONDISK (939,112,972 samples, 0.01%)</title><rect x="109.9" y="757" width="0.1" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="112.89" y="767.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (966,676,862 samples, 0.01%)</title><rect x="125.5" y="197" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="128.48" y="207.5" ></text>
</g>
<g >
<title>ExecInitInterpreter (858,165,128 samples, 0.01%)</title><rect x="429.9" y="325" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="432.94" y="335.5" ></text>
</g>
<g >
<title>ReleaseCatCache (958,184,329 samples, 0.01%)</title><rect x="1045.3" y="405" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1048.30" y="415.5" ></text>
</g>
<g >
<title>bms_make_singleton (3,673,297,637 samples, 0.04%)</title><rect x="906.4" y="501" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="909.36" y="511.5" ></text>
</g>
<g >
<title>GetMemoryChunkMethodID (937,198,025 samples, 0.01%)</title><rect x="978.0" y="405" width="0.2" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="981.04" y="415.5" ></text>
</g>
<g >
<title>pfree (1,320,298,348 samples, 0.01%)</title><rect x="1068.8" y="469" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1071.79" y="479.5" ></text>
</g>
<g >
<title>ExecScan (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="469" width="6.8" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="98.68" y="479.5" ></text>
</g>
<g >
<title>BufferGetLSNAtomic (10,926,695,097 samples, 0.12%)</title><rect x="325.0" y="229" width="1.3" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="327.97" y="239.5" ></text>
</g>
<g >
<title>list_make1_impl (2,252,599,335 samples, 0.02%)</title><rect x="975.8" y="421" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="978.80" y="431.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,192,203,662 samples, 0.01%)</title><rect x="499.5" y="453" width="0.1" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="502.50" y="463.5" ></text>
</g>
<g >
<title>LockHeldByMe (10,936,743,092 samples, 0.12%)</title><rect x="862.5" y="469" width="1.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="865.46" y="479.5" ></text>
</g>
<g >
<title>GrantLockLocal (1,412,840,194 samples, 0.01%)</title><rect x="448.1" y="357" width="0.2" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="451.08" y="367.5" ></text>
</g>
<g >
<title>newNode (3,832,571,342 samples, 0.04%)</title><rect x="971.6" y="389" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="974.61" y="399.5" ></text>
</g>
<g >
<title>AtEOXact_Files (1,085,318,624 samples, 0.01%)</title><rect x="469.6" y="517" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="472.56" y="527.5" ></text>
</g>
<g >
<title>heap_getattr (7,509,511,389 samples, 0.08%)</title><rect x="829.2" y="357" width="1.0" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="832.23" y="367.5" ></text>
</g>
<g >
<title>PortalRunMulti (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="645" width="6.9" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="98.68" y="655.5" ></text>
</g>
<g >
<title>[[vdso]] (1,321,038,397 samples, 0.01%)</title><rect x="210.2" y="565" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="213.23" y="575.5" ></text>
</g>
<g >
<title>ExecIndexScan (473,780,588,722 samples, 4.99%)</title><rect x="284.9" y="405" width="58.8" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="287.87" y="415.5" >ExecIn..</text>
</g>
<g >
<title>core_yylex (87,175,758,287 samples, 0.92%)</title><rect x="1103.1" y="725" width="10.8" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="1106.11" y="735.5" ></text>
</g>
<g >
<title>_bt_readpage (20,772,002,555 samples, 0.22%)</title><rect x="327.2" y="245" width="2.6" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="330.24" y="255.5" ></text>
</g>
<g >
<title>pfree (1,711,801,119 samples, 0.02%)</title><rect x="323.4" y="245" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="326.41" y="255.5" ></text>
</g>
<g >
<title>__update_load_avg_cfs_rq (2,317,953,935 samples, 0.02%)</title><rect x="173.4" y="261" width="0.3" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="176.38" y="271.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,180,245,984 samples, 0.01%)</title><rect x="848.3" y="389" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="851.30" y="399.5" ></text>
</g>
<g >
<title>CheckExprStillValid (8,582,358,154 samples, 0.09%)</title><rect x="269.7" y="357" width="1.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="272.71" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (896,646,103 samples, 0.01%)</title><rect x="1049.5" y="437" width="0.1" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="1052.52" y="447.5" ></text>
</g>
<g >
<title>coerce_to_boolean (894,193,468 samples, 0.01%)</title><rect x="1128.1" y="757" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1131.14" y="767.5" ></text>
</g>
<g >
<title>tag_hash (2,672,700,206 samples, 0.03%)</title><rect x="863.5" y="437" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="866.49" y="447.5" ></text>
</g>
<g >
<title>btgettreeheight (2,450,203,811 samples, 0.03%)</title><rect x="933.1" y="405" width="0.3" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="936.12" y="415.5" ></text>
</g>
<g >
<title>LWLockWakeup (1,222,665,025 samples, 0.01%)</title><rect x="370.2" y="245" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="373.21" y="255.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,254,728,770 samples, 0.01%)</title><rect x="104.0" y="453" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="106.95" y="463.5" ></text>
</g>
<g >
<title>PageGetItemId (1,861,098,815 samples, 0.02%)</title><rect x="313.2" y="229" width="0.2" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="316.15" y="239.5" ></text>
</g>
<g >
<title>LWLockRelease (1,882,196,320 samples, 0.02%)</title><rect x="1077.2" y="517" width="0.3" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="1080.22" y="527.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,270,428,689 samples, 0.01%)</title><rect x="240.9" y="357" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="243.87" y="367.5" ></text>
</g>
<g >
<title>BufferIsLockedByMe (2,528,995,378 samples, 0.03%)</title><rect x="363.5" y="357" width="0.3" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="366.50" y="367.5" ></text>
</g>
<g >
<title>ExecProcNode (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="453" width="1.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="129.31" y="463.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (3,991,187,586 samples, 0.04%)</title><rect x="464.2" y="549" width="0.4" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="467.15" y="559.5" ></text>
</g>
<g >
<title>PostgresMain (83,672,568,701 samples, 0.88%)</title><rect x="95.7" y="693" width="10.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="98.68" y="703.5" ></text>
</g>
<g >
<title>new_list (1,822,162,319 samples, 0.02%)</title><rect x="1018.0" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1020.96" y="367.5" ></text>
</g>
<g >
<title>__schedule (1,124,399,590 samples, 0.01%)</title><rect x="177.1" y="405" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="180.09" y="415.5" ></text>
</g>
<g >
<title>pg_analyze_and_rewrite_fixedparams (550,043,223,008 samples, 5.79%)</title><rect x="802.5" y="581" width="68.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="805.49" y="591.5" >pg_anal..</text>
</g>
<g >
<title>hash_initial_lookup (892,270,982 samples, 0.01%)</title><rect x="1012.4" y="229" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1015.42" y="239.5" ></text>
</g>
<g >
<title>raw_parser (1,575,281,683 samples, 0.02%)</title><rect x="1175.4" y="757" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1178.36" y="767.5" ></text>
</g>
<g >
<title>XLogRegisterBufData (4,217,293,991 samples, 0.04%)</title><rect x="391.9" y="341" width="0.5" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="394.86" y="351.5" ></text>
</g>
<g >
<title>__futex_wait (1,573,188,973 samples, 0.02%)</title><rect x="496.9" y="325" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="499.87" y="335.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,328,469,786 samples, 0.01%)</title><rect x="1077.3" y="501" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1080.29" y="511.5" ></text>
</g>
<g >
<title>get_hash_value (3,017,910,220 samples, 0.03%)</title><rect x="941.9" y="325" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="944.85" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (839,670,432 samples, 0.01%)</title><rect x="829.1" y="293" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="832.08" y="303.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,894,859,417 samples, 0.05%)</title><rect x="365.6" y="293" width="0.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="368.57" y="303.5" ></text>
</g>
<g >
<title>bms_add_members (3,921,033,179 samples, 0.04%)</title><rect x="880.0" y="469" width="0.5" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="883.01" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,385,125,815 samples, 0.01%)</title><rect x="156.3" y="469" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="159.29" y="479.5" ></text>
</g>
<g >
<title>BufferAlloc (21,191,765,814 samples, 0.22%)</title><rect x="300.0" y="149" width="2.6" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="303.00" y="159.5" ></text>
</g>
<g >
<title>seg_alloc (3,983,346,449 samples, 0.04%)</title><rect x="1063.5" y="437" width="0.5" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="1066.49" y="447.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (3,399,137,245 samples, 0.04%)</title><rect x="948.5" y="373" width="0.4" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="951.53" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,123,779,326 samples, 0.01%)</title><rect x="1034.5" y="181" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1037.55" y="191.5" ></text>
</g>
<g >
<title>hash_initial_lookup (983,820,102 samples, 0.01%)</title><rect x="867.4" y="405" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="870.43" y="415.5" ></text>
</g>
<g >
<title>AllocSetCheck (29,591,230,937 samples, 0.31%)</title><rect x="260.5" y="405" width="3.6" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="263.46" y="415.5" ></text>
</g>
<g >
<title>create_empty_pathtarget (2,484,838,322 samples, 0.03%)</title><rect x="927.8" y="421" width="0.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="930.82" y="431.5" ></text>
</g>
<g >
<title>standard_ExecutorFinish (3,972,168,049 samples, 0.04%)</title><rect x="265.8" y="517" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="268.82" y="527.5" ></text>
</g>
<g >
<title>index_can_return (2,584,147,603 samples, 0.03%)</title><rect x="939.7" y="405" width="0.3" height="15.0" fill="rgb(205,4,0)" rx="2" ry="2" />
<text  x="942.68" y="415.5" ></text>
</g>
<g >
<title>pull_up_subqueries_recurse (4,831,311,225 samples, 0.05%)</title><rect x="1069.9" y="485" width="0.6" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1072.91" y="495.5" ></text>
</g>
<g >
<title>distribute_qual_to_rels (129,961,314,320 samples, 1.37%)</title><rect x="957.8" y="421" width="16.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="960.79" y="431.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (3,183,882,121 samples, 0.03%)</title><rect x="902.9" y="341" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="905.86" y="351.5" ></text>
</g>
<g >
<title>finalize_primnode (1,407,547,936 samples, 0.01%)</title><rect x="882.8" y="357" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="885.82" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,333,328,147 samples, 0.01%)</title><rect x="432.2" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="435.18" y="335.5" ></text>
</g>
<g >
<title>distribute_quals_to_rels (131,524,823,477 samples, 1.38%)</title><rect x="957.6" y="437" width="16.3" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="960.61" y="447.5" ></text>
</g>
<g >
<title>palloc (1,217,267,063 samples, 0.01%)</title><rect x="893.5" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="896.45" y="431.5" ></text>
</g>
<g >
<title>UnpinBuffer (3,443,724,290 samples, 0.04%)</title><rect x="326.5" y="197" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="329.47" y="207.5" ></text>
</g>
<g >
<title>GetXLogBuffer (1,298,742,404 samples, 0.01%)</title><rect x="512.3" y="437" width="0.2" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="515.29" y="447.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,656,189,483 samples, 0.02%)</title><rect x="438.0" y="373" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="441.01" y="383.5" ></text>
</g>
<g >
<title>index_getattr (14,797,461,142 samples, 0.16%)</title><rect x="321.5" y="229" width="1.8" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="324.47" y="239.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (4,841,861,153 samples, 0.05%)</title><rect x="367.6" y="261" width="0.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="370.64" y="271.5" ></text>
</g>
<g >
<title>AllocSetFree (1,246,194,738 samples, 0.01%)</title><rect x="248.4" y="357" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="251.43" y="367.5" ></text>
</g>
<g >
<title>pgstat_count_io_op_time (3,577,291,607 samples, 0.04%)</title><rect x="508.3" y="453" width="0.5" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="511.32" y="463.5" ></text>
</g>
<g >
<title>ExecModifyTable (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="533" width="6.9" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="98.68" y="543.5" ></text>
</g>
<g >
<title>ExecUpdateEpilogue (2,927,654,386 samples, 0.03%)</title><rect x="395.5" y="421" width="0.4" height="15.0" fill="rgb(217,58,14)" rx="2" ry="2" />
<text  x="398.54" y="431.5" ></text>
</g>
<g >
<title>restriction_is_always_true (996,072,163 samples, 0.01%)</title><rect x="981.0" y="405" width="0.2" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="984.03" y="415.5" ></text>
</g>
<g >
<title>_bt_compare (4,852,963,898 samples, 0.05%)</title><rect x="338.8" y="229" width="0.6" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="341.83" y="239.5" ></text>
</g>
<g >
<title>security_file_permission (4,127,581,218 samples, 0.04%)</title><rect x="501.0" y="373" width="0.5" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="503.98" y="383.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (835,573,177 samples, 0.01%)</title><rect x="377.8" y="325" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="380.82" y="335.5" ></text>
</g>
<g >
<title>psi_account_irqtime (2,531,487,590 samples, 0.03%)</title><rect x="485.4" y="277" width="0.4" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="488.44" y="287.5" ></text>
</g>
<g >
<title>exec_simple_query (817,100,162 samples, 0.01%)</title><rect x="84.2" y="741" width="0.1" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="87.22" y="751.5" ></text>
</g>
<g >
<title>_bt_preprocess_array_keys (2,841,645,866 samples, 0.03%)</title><rect x="84.3" y="309" width="0.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="87.32" y="319.5" ></text>
</g>
<g >
<title>ExecScan (472,722,226,458 samples, 4.98%)</title><rect x="284.9" y="389" width="58.7" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="287.91" y="399.5" >ExecScan</text>
</g>
<g >
<title>preprocess_expression (26,352,281,467 samples, 0.28%)</title><rect x="1054.0" y="485" width="3.3" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1056.99" y="495.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,908,347,190 samples, 0.04%)</title><rect x="94.3" y="757" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="97.31" y="767.5" ></text>
</g>
<g >
<title>check_list_invariants (1,486,098,367 samples, 0.02%)</title><rect x="1126.2" y="757" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1129.21" y="767.5" ></text>
</g>
<g >
<title>hash_search (10,873,439,606 samples, 0.11%)</title><rect x="867.1" y="437" width="1.3" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="870.08" y="447.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (3,406,487,201 samples, 0.04%)</title><rect x="271.5" y="325" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="274.47" y="335.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,049,007,320 samples, 0.01%)</title><rect x="409.6" y="261" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="412.59" y="271.5" ></text>
</g>
<g >
<title>bms_copy (3,439,666,431 samples, 0.04%)</title><rect x="950.9" y="421" width="0.4" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="953.87" y="431.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (475,221,569,341 samples, 5.00%)</title><rect x="284.8" y="421" width="59.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="287.83" y="431.5" >ExecPr..</text>
</g>
<g >
<title>genericcostestimate (815,978,484 samples, 0.01%)</title><rect x="1140.4" y="757" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1143.45" y="767.5" ></text>
</g>
<g >
<title>PostmasterMain (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="741" width="0.4" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="1161.90" y="751.5" ></text>
</g>
<g >
<title>remove_useless_groupby_columns (1,688,490,611 samples, 0.02%)</title><rect x="1042.9" y="469" width="0.2" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="1045.89" y="479.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,186,043,021 samples, 0.01%)</title><rect x="353.6" y="261" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="356.55" y="271.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturn (49,383,293,415 samples, 0.52%)</title><rect x="285.7" y="325" width="6.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="288.70" y="335.5" ></text>
</g>
<g >
<title>uint32_hash (909,682,450 samples, 0.01%)</title><rect x="1023.3" y="325" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1026.32" y="335.5" ></text>
</g>
<g >
<title>palloc (1,680,203,637 samples, 0.02%)</title><rect x="189.1" y="533" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="192.11" y="543.5" ></text>
</g>
<g >
<title>lappend (2,563,955,020 samples, 0.03%)</title><rect x="1017.9" y="373" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1020.87" y="383.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (1,526,558,245 samples, 0.02%)</title><rect x="332.7" y="213" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="335.72" y="223.5" ></text>
</g>
<g >
<title>ReadBufferExtended (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="277" width="2.4" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="103.12" y="287.5" ></text>
</g>
<g >
<title>gup_fast_fallback (2,837,958,187 samples, 0.03%)</title><rect x="492.2" y="309" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="495.21" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_add_u64_impl (1,387,052,730 samples, 0.01%)</title><rect x="497.6" y="437" width="0.1" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="500.57" y="447.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,271,991,822 samples, 0.01%)</title><rect x="1033.6" y="149" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1036.65" y="159.5" ></text>
</g>
<g >
<title>lappend (2,781,709,260 samples, 0.03%)</title><rect x="874.2" y="565" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="877.21" y="575.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (5,269,209,910 samples, 0.06%)</title><rect x="366.6" y="261" width="0.7" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="369.60" y="271.5" ></text>
</g>
<g >
<title>list_nth_cell (829,244,497 samples, 0.01%)</title><rect x="1043.2" y="453" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1046.24" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,855,028,272 samples, 0.02%)</title><rect x="458.9" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="461.85" y="431.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,676,811,603 samples, 0.03%)</title><rect x="213.6" y="565" width="0.3" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="216.58" y="575.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (9,307,357,470 samples, 0.10%)</title><rect x="1045.1" y="453" width="1.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1048.13" y="463.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,212,147,791 samples, 0.02%)</title><rect x="994.9" y="325" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="997.89" y="335.5" ></text>
</g>
<g >
<title>palloc0 (4,322,150,769 samples, 0.05%)</title><rect x="944.6" y="389" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="947.63" y="399.5" ></text>
</g>
<g >
<title>AtCommit_Memory (9,695,517,494 samples, 0.10%)</title><rect x="467.5" y="517" width="1.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="470.48" y="527.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (1,130,940,652 samples, 0.01%)</title><rect x="434.6" y="373" width="0.1" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="437.55" y="383.5" ></text>
</g>
<g >
<title>PageGetItemId (2,147,840,508 samples, 0.02%)</title><rect x="81.1" y="741" width="0.3" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="84.09" y="751.5" ></text>
</g>
<g >
<title>list_nth (819,657,936 samples, 0.01%)</title><rect x="922.8" y="469" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="925.78" y="479.5" ></text>
</g>
<g >
<title>ReleaseSysCache (972,571,831 samples, 0.01%)</title><rect x="1008.2" y="277" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1011.24" y="287.5" ></text>
</g>
<g >
<title>schedule (1,131,286,277 samples, 0.01%)</title><rect x="496.9" y="293" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="499.88" y="303.5" ></text>
</g>
<g >
<title>PostmasterMain (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="757" width="2.8" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="87.32" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (7,827,516,581 samples, 0.08%)</title><rect x="518.8" y="437" width="0.9" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="521.76" y="447.5" ></text>
</g>
<g >
<title>expression_returns_set_walker (2,234,886,004 samples, 0.02%)</title><rect x="845.5" y="389" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="848.45" y="399.5" ></text>
</g>
<g >
<title>standard_planner (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="613" width="3.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="105.66" y="623.5" ></text>
</g>
<g >
<title>_bt_next (9,369,977,460 samples, 0.10%)</title><rect x="283.0" y="293" width="1.2" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="286.02" y="303.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,546,571,702 samples, 0.04%)</title><rect x="1045.4" y="405" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1048.44" y="415.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (1,001,601,369 samples, 0.01%)</title><rect x="700.4" y="453" width="0.1" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="703.40" y="463.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (3,668,751,795 samples, 0.04%)</title><rect x="1073.9" y="517" width="0.4" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1076.89" y="527.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (979,979,626 samples, 0.01%)</title><rect x="264.1" y="405" width="0.2" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="267.13" y="415.5" ></text>
</g>
<g >
<title>tag_hash (10,400,301,170 samples, 0.11%)</title><rect x="839.1" y="325" width="1.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="842.14" y="335.5" ></text>
</g>
<g >
<title>ExecUpdate (484,478,572,189 samples, 5.10%)</title><rect x="344.0" y="437" width="60.2" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="347.01" y="447.5" >ExecUp..</text>
</g>
<g >
<title>CheckForBufferLeaks (1,901,294,167 samples, 0.02%)</title><rect x="469.3" y="501" width="0.2" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="472.25" y="511.5" ></text>
</g>
<g >
<title>ExecCheckIndexedAttrsForChanges (26,386,302,641 samples, 0.28%)</title><rect x="345.9" y="405" width="3.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="348.93" y="415.5" ></text>
</g>
<g >
<title>palloc (1,822,242,194 samples, 0.02%)</title><rect x="889.8" y="309" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="892.84" y="319.5" ></text>
</g>
<g >
<title>register_seq_scan (999,869,463 samples, 0.01%)</title><rect x="527.9" y="437" width="0.1" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="530.89" y="447.5" ></text>
</g>
<g >
<title>PinBufferForBlock (3,143,518,309 samples, 0.03%)</title><rect x="86.5" y="197" width="0.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="89.52" y="207.5" ></text>
</g>
<g >
<title>lappend (2,075,194,618 samples, 0.02%)</title><rect x="1053.3" y="437" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1056.33" y="447.5" ></text>
</g>
<g >
<title>pick_task_fair (1,238,855,605 samples, 0.01%)</title><rect x="484.1" y="245" width="0.2" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="487.10" y="255.5" ></text>
</g>
<g >
<title>AtEOXact_MultiXact (811,436,456 samples, 0.01%)</title><rect x="31.8" y="757" width="0.1" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="34.76" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (820,047,704 samples, 0.01%)</title><rect x="330.7" y="213" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="333.71" y="223.5" ></text>
</g>
<g >
<title>enqueue_task (2,196,889,534 samples, 0.02%)</title><rect x="202.0" y="325" width="0.3" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="205.02" y="335.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,790,962,900 samples, 0.02%)</title><rect x="1058.6" y="453" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1061.56" y="463.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,020,864,105 samples, 0.05%)</title><rect x="435.2" y="357" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="438.16" y="367.5" ></text>
</g>
<g >
<title>lappend (1,938,886,419 samples, 0.02%)</title><rect x="281.1" y="405" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="284.12" y="415.5" ></text>
</g>
<g >
<title>ExecClearTuple (2,373,490,245 samples, 0.02%)</title><rect x="269.3" y="405" width="0.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="272.30" y="415.5" ></text>
</g>
<g >
<title>makeTargetEntry (6,119,112,734 samples, 0.06%)</title><rect x="886.0" y="421" width="0.8" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="889.02" y="431.5" ></text>
</g>
<g >
<title>tts_virtual_clear (835,305,116 samples, 0.01%)</title><rect x="251.0" y="453" width="0.1" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="254.03" y="463.5" ></text>
</g>
<g >
<title>list_free_private (6,362,728,921 samples, 0.07%)</title><rect x="977.4" y="437" width="0.8" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="980.38" y="447.5" ></text>
</g>
<g >
<title>__task_rq_lock (1,291,805,830 samples, 0.01%)</title><rect x="389.5" y="85" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="392.45" y="95.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (23,498,308,475 samples, 0.25%)</title><rect x="809.1" y="389" width="2.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="812.13" y="399.5" ></text>
</g>
<g >
<title>palloc (2,635,927,941 samples, 0.03%)</title><rect x="236.5" y="517" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="239.52" y="527.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,610,349,409 samples, 0.02%)</title><rect x="101.4" y="149" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="104.40" y="159.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,187,104,939 samples, 0.02%)</title><rect x="431.4" y="293" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="434.36" y="303.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,385,748,817 samples, 0.01%)</title><rect x="1077.1" y="501" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1080.05" y="511.5" ></text>
</g>
<g >
<title>ExecBSUpdateTriggers (960,295,206 samples, 0.01%)</title><rect x="404.6" y="421" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="407.59" y="431.5" ></text>
</g>
<g >
<title>gup_fast_pte_range (2,345,970,185 samples, 0.02%)</title><rect x="489.8" y="245" width="0.3" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="492.77" y="255.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,499,931,219 samples, 0.04%)</title><rect x="838.3" y="325" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="841.25" y="335.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (1,023,745,549 samples, 0.01%)</title><rect x="276.3" y="373" width="0.1" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="279.31" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (810,290,112 samples, 0.01%)</title><rect x="377.0" y="341" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="380.01" y="351.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (845,907,824 samples, 0.01%)</title><rect x="356.2" y="261" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="359.17" y="271.5" ></text>
</g>
<g >
<title>set_baserel_size_estimates (1,423,929,784 samples, 0.01%)</title><rect x="1181.7" y="757" width="0.2" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="1184.73" y="767.5" ></text>
</g>
<g >
<title>pull_varnos_walker (1,371,897,689 samples, 0.01%)</title><rect x="1035.3" y="197" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1038.33" y="207.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (4,073,502,377 samples, 0.04%)</title><rect x="340.1" y="197" width="0.5" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="343.13" y="207.5" ></text>
</g>
<g >
<title>add_function_cost (5,620,161,791 samples, 0.06%)</title><rect x="1045.2" y="437" width="0.7" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1048.19" y="447.5" ></text>
</g>
<g >
<title>PortalRunMulti (1,866,837,160,290 samples, 19.65%)</title><rect x="230.4" y="565" width="231.9" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="233.41" y="575.5" >PortalRunMulti</text>
</g>
<g >
<title>list_concat_copy (5,807,099,477 samples, 0.06%)</title><rect x="990.5" y="373" width="0.8" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="993.54" y="383.5" ></text>
</g>
<g >
<title>markRTEForSelectPriv (7,069,638,533 samples, 0.07%)</title><rect x="857.0" y="341" width="0.9" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="859.98" y="351.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_in (5,316,062,062 samples, 0.06%)</title><rect x="164.4" y="325" width="0.7" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="167.41" y="335.5" ></text>
</g>
<g >
<title>MemoryContextTraverseNext (8,671,946,467 samples, 0.09%)</title><rect x="800.9" y="549" width="1.0" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="803.87" y="559.5" ></text>
</g>
<g >
<title>create_indexscan_plan (40,645,572,052 samples, 0.43%)</title><rect x="887.3" y="405" width="5.1" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="890.33" y="415.5" ></text>
</g>
<g >
<title>WalSndWakeup (4,959,155,408 samples, 0.05%)</title><rect x="497.9" y="469" width="0.6" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="500.91" y="479.5" ></text>
</g>
<g >
<title>LockHeldByMe (8,732,104,450 samples, 0.09%)</title><rect x="452.0" y="357" width="1.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="455.03" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,104,937,725 samples, 0.01%)</title><rect x="1060.8" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1063.76" y="447.5" ></text>
</g>
<g >
<title>palloc (1,264,157,115 samples, 0.01%)</title><rect x="933.9" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="936.87" y="367.5" ></text>
</g>
<g >
<title>ExecBuildUpdateProjection (38,685,262,854 samples, 0.41%)</title><rect x="273.5" y="421" width="4.8" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="276.47" y="431.5" ></text>
</g>
<g >
<title>get_relation_statistics (2,432,832,943 samples, 0.03%)</title><rect x="939.4" y="405" width="0.3" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="942.38" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u64_impl (814,711,503 samples, 0.01%)</title><rect x="497.4" y="437" width="0.1" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="500.37" y="447.5" ></text>
</g>
<g >
<title>CreateExprContextInternal (10,000,665,687 samples, 0.11%)</title><rect x="272.2" y="389" width="1.3" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="275.23" y="399.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (811,432,908 samples, 0.01%)</title><rect x="1115.3" y="661" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1118.32" y="671.5" ></text>
</g>
<g >
<title>transformExprRecurse (101,739,854,475 samples, 1.07%)</title><rect x="845.8" y="453" width="12.7" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="848.83" y="463.5" ></text>
</g>
<g >
<title>ReleaseCatCache (870,664,899 samples, 0.01%)</title><rect x="917.0" y="357" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="920.04" y="367.5" ></text>
</g>
<g >
<title>_bt_compare (2,113,731,581 samples, 0.02%)</title><rect x="129.5" y="757" width="0.3" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="132.51" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="709" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1137.90" y="719.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (1,871,243,432 samples, 0.02%)</title><rect x="269.4" y="389" width="0.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="272.36" y="399.5" ></text>
</g>
<g >
<title>hash_initial_lookup (823,177,425 samples, 0.01%)</title><rect x="868.6" y="421" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="871.64" y="431.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (21,783,123,494 samples, 0.23%)</title><rect x="407.2" y="325" width="2.7" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="410.16" y="335.5" ></text>
</g>
<g >
<title>palloc0 (2,962,850,189 samples, 0.03%)</title><rect x="817.6" y="405" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="820.59" y="415.5" ></text>
</g>
<g >
<title>pq_getmessage (10,474,817,378 samples, 0.11%)</title><rect x="187.3" y="565" width="1.3" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="190.30" y="575.5" ></text>
</g>
<g >
<title>AtEOXact_PgStat (8,374,341,722 samples, 0.09%)</title><rect x="470.5" y="517" width="1.1" height="15.0" fill="rgb(246,193,46)" rx="2" ry="2" />
<text  x="473.52" y="527.5" ></text>
</g>
<g >
<title>deconstruct_distribute (132,531,470,230 samples, 1.40%)</title><rect x="957.5" y="453" width="16.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="960.49" y="463.5" ></text>
</g>
<g >
<title>AllocSetCheck (1,587,694,175 samples, 0.02%)</title><rect x="225.4" y="517" width="0.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="228.37" y="527.5" ></text>
</g>
<g >
<title>hash_seq_init (2,282,240,270 samples, 0.02%)</title><rect x="527.7" y="453" width="0.3" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="530.73" y="463.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (1,607,516,114 samples, 0.02%)</title><rect x="124.0" y="293" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="126.99" y="303.5" ></text>
</g>
<g >
<title>smgrnblocks (11,075,645,823 samples, 0.12%)</title><rect x="931.7" y="389" width="1.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="934.70" y="399.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (8,502,734,615 samples, 0.09%)</title><rect x="318.2" y="229" width="1.0" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="321.17" y="239.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (1,148,582,055,886 samples, 12.09%)</title><rect x="267.5" y="469" width="142.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="270.46" y="479.5" >ExecProcNodeFirst</text>
</g>
<g >
<title>_bt_checkkeys (4,627,104,075 samples, 0.05%)</title><rect x="126.3" y="245" width="0.6" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="129.31" y="255.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (6,894,472,615 samples, 0.07%)</title><rect x="890.1" y="309" width="0.8" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="893.06" y="319.5" ></text>
</g>
<g >
<title>[postgres] (4,620,041,081 samples, 0.05%)</title><rect x="112.0" y="757" width="0.6" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="115.05" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,082,528,780 samples, 0.02%)</title><rect x="934.2" y="357" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="937.18" y="367.5" ></text>
</g>
<g >
<title>bms_copy (1,415,100,591 samples, 0.01%)</title><rect x="968.6" y="357" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="971.62" y="367.5" ></text>
</g>
<g >
<title>_bt_binsrch (3,262,946,556 samples, 0.03%)</title><rect x="124.8" y="277" width="0.4" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="127.77" y="287.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,084,950,092 samples, 0.01%)</title><rect x="351.7" y="357" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="354.72" y="367.5" ></text>
</g>
<g >
<title>ResourceOwnerCreate (13,654,843,872 samples, 0.14%)</title><rect x="1074.6" y="517" width="1.7" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="1077.60" y="527.5" ></text>
</g>
<g >
<title>MemoryContextReset (831,164,191 samples, 0.01%)</title><rect x="404.2" y="437" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="407.21" y="447.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,577,817,837 samples, 0.02%)</title><rect x="1148.5" y="533" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="1151.50" y="543.5" ></text>
</g>
<g >
<title>__cgroup_account_cputime (1,926,491,662 samples, 0.02%)</title><rect x="171.9" y="245" width="0.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="174.89" y="255.5" ></text>
</g>
<g >
<title>_bt_first (966,157,519 samples, 0.01%)</title><rect x="129.9" y="757" width="0.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="132.86" y="767.5" ></text>
</g>
<g >
<title>LWLockRelease (2,059,272,808 samples, 0.02%)</title><rect x="223.6" y="549" width="0.3" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="226.61" y="559.5" ></text>
</g>
<g >
<title>__rint_sse41 (1,569,411,537 samples, 0.02%)</title><rect x="938.5" y="341" width="0.2" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="941.52" y="351.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (12,665,037,636 samples, 0.13%)</title><rect x="1142.7" y="741" width="1.6" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1145.73" y="751.5" ></text>
</g>
<g >
<title>newNode (2,512,721,001 samples, 0.03%)</title><rect x="919.9" y="469" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="922.86" y="479.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (919,554,249 samples, 0.01%)</title><rect x="796.1" y="517" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="799.12" y="527.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (6,733,630,383 samples, 0.07%)</title><rect x="509.2" y="453" width="0.8" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="512.15" y="463.5" ></text>
</g>
<g >
<title>DatumGetPointer (1,699,079,984 samples, 0.02%)</title><rect x="41.1" y="757" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="44.05" y="767.5" ></text>
</g>
<g >
<title>index_getnext_tid (16,454,052,467 samples, 0.17%)</title><rect x="282.6" y="325" width="2.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="285.62" y="335.5" ></text>
</g>
<g >
<title>lappend (2,964,151,139 samples, 0.03%)</title><rect x="1056.4" y="405" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1059.39" y="415.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,365,375,810 samples, 0.01%)</title><rect x="291.6" y="165" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="294.59" y="175.5" ></text>
</g>
<g >
<title>btgettuple (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="357" width="2.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="87.32" y="367.5" ></text>
</g>
<g >
<title>UnregisterSnapshotNoOwner (1,849,062,509 samples, 0.02%)</title><rect x="460.8" y="485" width="0.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="463.81" y="495.5" ></text>
</g>
<g >
<title>new_list (1,440,734,397 samples, 0.02%)</title><rect x="897.9" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="900.87" y="463.5" ></text>
</g>
<g >
<title>relation_open (28,186,893,177 samples, 0.30%)</title><rect x="866.8" y="501" width="3.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="869.81" y="511.5" ></text>
</g>
<g >
<title>new_list (2,320,038,453 samples, 0.02%)</title><rect x="870.5" y="517" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="873.46" y="527.5" ></text>
</g>
<g >
<title>tag_hash (2,567,225,802 samples, 0.03%)</title><rect x="1067.4" y="405" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1070.37" y="415.5" ></text>
</g>
<g >
<title>setup_simple_rel_arrays (4,960,043,149 samples, 0.05%)</title><rect x="1043.5" y="469" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1046.49" y="479.5" ></text>
</g>
<g >
<title>enqueue_task (9,462,436,757 samples, 0.10%)</title><rect x="206.4" y="309" width="1.2" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="209.45" y="319.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (935,760,977 samples, 0.01%)</title><rect x="165.1" y="293" width="0.2" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="168.14" y="303.5" ></text>
</g>
<g >
<title>CopySnapshot (4,013,424,286 samples, 0.04%)</title><rect x="461.4" y="533" width="0.5" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="464.38" y="543.5" ></text>
</g>
<g >
<title>_bt_getbuf (3,477,463,344 samples, 0.04%)</title><rect x="125.2" y="261" width="0.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="128.17" y="271.5" ></text>
</g>
<g >
<title>GetSnapshotData (32,447,403,724 samples, 0.34%)</title><rect x="219.9" y="565" width="4.0" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="222.90" y="575.5" ></text>
</g>
<g >
<title>palloc (1,257,133,242 samples, 0.01%)</title><rect x="438.1" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="441.06" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,295,242,976 samples, 0.01%)</title><rect x="894.1" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="897.05" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,197,166,383 samples, 0.01%)</title><rect x="890.6" y="229" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="893.55" y="239.5" ></text>
</g>
<g >
<title>pfree (1,197,702,173 samples, 0.01%)</title><rect x="861.9" y="501" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="864.93" y="511.5" ></text>
</g>
<g >
<title>lappend (2,950,144,662 samples, 0.03%)</title><rect x="980.5" y="405" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="983.49" y="415.5" ></text>
</g>
<g >
<title>palloc (1,087,446,409 samples, 0.01%)</title><rect x="126.2" y="405" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="129.17" y="415.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,857,502,626 samples, 0.03%)</title><rect x="101.2" y="165" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="104.25" y="175.5" ></text>
</g>
<g >
<title>set_sentinel (1,184,543,732 samples, 0.01%)</title><rect x="1182.7" y="757" width="0.1" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="1185.70" y="767.5" ></text>
</g>
<g >
<title>examine_variable (944,526,695 samples, 0.01%)</title><rect x="1135.4" y="757" width="0.1" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="1138.38" y="767.5" ></text>
</g>
<g >
<title>TupleDescAttr (4,029,220,683 samples, 0.04%)</title><rect x="108.3" y="757" width="0.5" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="111.25" y="767.5" ></text>
</g>
<g >
<title>transformColumnRef (25,343,357,939 samples, 0.27%)</title><rect x="855.3" y="405" width="3.2" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="858.32" y="415.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,756,472,230 samples, 0.05%)</title><rect x="917.7" y="421" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="920.67" y="431.5" ></text>
</g>
<g >
<title>__update_idle_core (2,598,717,311 samples, 0.03%)</title><rect x="163.8" y="309" width="0.4" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="166.84" y="319.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,309,601,826 samples, 0.01%)</title><rect x="833.0" y="357" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="835.96" y="367.5" ></text>
</g>
<g >
<title>hash_search (4,235,145,714 samples, 0.04%)</title><rect x="1144.8" y="757" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1147.79" y="767.5" ></text>
</g>
<g >
<title>_bt_getroot (15,010,179,720 samples, 0.16%)</title><rect x="336.3" y="245" width="1.9" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="339.32" y="255.5" ></text>
</g>
<g >
<title>futex_hash (1,659,986,968 samples, 0.02%)</title><rect x="489.2" y="309" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="492.24" y="319.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (1,799,476,686 samples, 0.02%)</title><rect x="1139.7" y="757" width="0.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="1142.68" y="767.5" ></text>
</g>
<g >
<title>main (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="709" width="950.7" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="134.98" y="719.5" >main</text>
</g>
<g >
<title>expression_returns_set_walker (7,828,084,986 samples, 0.08%)</title><rect x="844.8" y="437" width="1.0" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="847.78" y="447.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (11,016,433,624 samples, 0.12%)</title><rect x="492.8" y="245" width="1.3" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="495.78" y="255.5" ></text>
</g>
<g >
<title>new_list (2,043,197,232 samples, 0.02%)</title><rect x="1057.0" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1060.00" y="447.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,057,368,347 samples, 0.04%)</title><rect x="415.5" y="389" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="418.48" y="399.5" ></text>
</g>
<g >
<title>perf_ctx_disable (1,657,125,432 samples, 0.02%)</title><rect x="485.2" y="229" width="0.2" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="488.24" y="239.5" ></text>
</g>
<g >
<title>pg_plan_queries (3,069,363,779 samples, 0.03%)</title><rect x="125.9" y="613" width="0.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="128.92" y="623.5" ></text>
</g>
<g >
<title>lappend (4,452,296,240 samples, 0.05%)</title><rect x="816.9" y="437" width="0.6" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="819.93" y="447.5" ></text>
</g>
<g >
<title>stack_is_too_deep (2,833,438,359 samples, 0.03%)</title><rect x="1127.5" y="741" width="0.4" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1130.51" y="751.5" ></text>
</g>
<g >
<title>palloc0 (1,557,425,161 samples, 0.02%)</title><rect x="1034.5" y="197" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1037.49" y="207.5" ></text>
</g>
<g >
<title>wipe_mem (34,768,126,424 samples, 0.37%)</title><rect x="243.8" y="357" width="4.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="246.83" y="367.5" ></text>
</g>
<g >
<title>preprocess_rowmarks (1,076,683,116 samples, 0.01%)</title><rect x="1173.9" y="757" width="0.2" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1176.93" y="767.5" ></text>
</g>
<g >
<title>futex_wait (907,110,079 samples, 0.01%)</title><rect x="370.0" y="149" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="372.95" y="159.5" ></text>
</g>
<g >
<title>internal_putbytes (3,358,780,253 samples, 0.04%)</title><rect x="219.1" y="549" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="222.11" y="559.5" ></text>
</g>
<g >
<title>DynaHashAlloc (960,010,806 samples, 0.01%)</title><rect x="41.4" y="757" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="44.36" y="767.5" ></text>
</g>
<g >
<title>RelationGetIndexExpressions (1,639,377,301 samples, 0.02%)</title><rect x="930.7" y="405" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="933.72" y="415.5" ></text>
</g>
<g >
<title>SyncRepWaitForLSN (1,913,969,267 samples, 0.02%)</title><rect x="477.1" y="501" width="0.2" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="480.10" y="511.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,905,458,566 samples, 0.02%)</title><rect x="932.2" y="309" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="935.19" y="319.5" ></text>
</g>
<g >
<title>op_input_types (5,641,073,419 samples, 0.06%)</title><rect x="972.1" y="389" width="0.7" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="975.08" y="399.5" ></text>
</g>
<g >
<title>BackendStartup (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="693" width="2.3" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="126.99" y="703.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (971,760,410 samples, 0.01%)</title><rect x="1040.9" y="309" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1043.85" y="319.5" ></text>
</g>
<g >
<title>hash_bytes (1,875,926,933 samples, 0.02%)</title><rect x="942.8" y="309" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="945.79" y="319.5" ></text>
</g>
<g >
<title>new_list (2,489,338,874 samples, 0.03%)</title><rect x="861.4" y="485" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="864.35" y="495.5" ></text>
</g>
<g >
<title>pfree (942,688,034 samples, 0.01%)</title><rect x="224.5" y="565" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="227.53" y="575.5" ></text>
</g>
<g >
<title>MemoryContextReset (145,830,053,287 samples, 1.54%)</title><rect x="133.7" y="597" width="18.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="136.71" y="607.5" ></text>
</g>
<g >
<title>ProcessClientReadInterrupt (5,651,725,894 samples, 0.06%)</title><rect x="153.7" y="517" width="0.7" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="156.72" y="527.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,294,419,002 samples, 0.02%)</title><rect x="277.0" y="389" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="280.02" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (894,550,064 samples, 0.01%)</title><rect x="1021.0" y="245" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1023.99" y="255.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (836,794,810 samples, 0.01%)</title><rect x="464.5" y="517" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="467.52" y="527.5" ></text>
</g>
<g >
<title>LockRelationOid (9,314,932,247 samples, 0.10%)</title><rect x="447.9" y="389" width="1.1" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="450.89" y="399.5" ></text>
</g>
<g >
<title>avc_has_perm_noaudit (2,686,913,986 samples, 0.03%)</title><rect x="194.0" y="373" width="0.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="197.02" y="383.5" ></text>
</g>
<g >
<title>MemoryContextDeleteChildren (18,556,513,375 samples, 0.20%)</title><rect x="133.8" y="581" width="2.3" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="136.79" y="591.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,001,407,015 samples, 0.01%)</title><rect x="337.4" y="181" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="340.42" y="191.5" ></text>
</g>
<g >
<title>detoast_attr (7,271,220,405 samples, 0.08%)</title><rect x="1007.1" y="261" width="0.9" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="1010.15" y="271.5" ></text>
</g>
<g >
<title>assign_query_collations_walker (3,008,089,712 samples, 0.03%)</title><rect x="1084.8" y="757" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1087.77" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,851,693,476 samples, 0.04%)</title><rect x="838.2" y="357" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="841.21" y="367.5" ></text>
</g>
<g >
<title>_bt_checkkeys (2,463,738,124 samples, 0.03%)</title><rect x="84.7" y="293" width="0.3" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="87.68" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,101,920,593 samples, 0.02%)</title><rect x="1118.5" y="677" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1121.48" y="687.5" ></text>
</g>
<g >
<title>RelationGetIndexScan (6,197,636,254 samples, 0.07%)</title><rect x="293.4" y="277" width="0.7" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="296.36" y="287.5" ></text>
</g>
<g >
<title>CopyXLogRecordToWAL (3,594,581,600 samples, 0.04%)</title><rect x="512.1" y="453" width="0.4" height="15.0" fill="rgb(213,36,8)" rx="2" ry="2" />
<text  x="515.09" y="463.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (6,526,914,420 samples, 0.07%)</title><rect x="366.5" y="293" width="0.9" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="369.54" y="303.5" ></text>
</g>
<g >
<title>SearchCatCache3 (4,608,026,529 samples, 0.05%)</title><rect x="1013.7" y="277" width="0.5" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1016.66" y="287.5" ></text>
</g>
<g >
<title>LWLockConditionalAcquire (3,384,659,164 samples, 0.04%)</title><rect x="474.3" y="501" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="477.31" y="511.5" ></text>
</g>
<g >
<title>sentinel_ok (28,575,533,843 samples, 0.30%)</title><rect x="1178.0" y="757" width="3.6" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="1181.03" y="767.5" ></text>
</g>
<g >
<title>pstrdup (3,991,163,469 samples, 0.04%)</title><rect x="818.0" y="437" width="0.5" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="820.98" y="447.5" ></text>
</g>
<g >
<title>new_list (5,331,389,530 samples, 0.06%)</title><rect x="918.6" y="469" width="0.7" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="921.60" y="479.5" ></text>
</g>
<g >
<title>enable_statement_timeout (1,572,823,924 samples, 0.02%)</title><rect x="1077.6" y="565" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="1080.57" y="575.5" ></text>
</g>
<g >
<title>palloc (1,253,623,175 samples, 0.01%)</title><rect x="1056.6" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1059.58" y="383.5" ></text>
</g>
<g >
<title>palloc0 (3,393,390,120 samples, 0.04%)</title><rect x="277.9" y="389" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="280.86" y="399.5" ></text>
</g>
<g >
<title>socket_set_nonblocking (1,056,861,653 samples, 0.01%)</title><rect x="1184.1" y="757" width="0.2" height="15.0" fill="rgb(228,105,25)" rx="2" ry="2" />
<text  x="1187.14" y="767.5" ></text>
</g>
<g >
<title>bms_free (7,753,747,117 samples, 0.08%)</title><rect x="358.9" y="373" width="0.9" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="361.88" y="383.5" ></text>
</g>
<g >
<title>jit_compile_expr (822,683,942 samples, 0.01%)</title><rect x="1154.3" y="757" width="0.2" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="1157.35" y="767.5" ></text>
</g>
<g >
<title>XLogRegisterData (897,941,538 samples, 0.01%)</title><rect x="514.4" y="485" width="0.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="517.40" y="495.5" ></text>
</g>
<g >
<title>AtStart_ResourceOwner (14,387,624,846 samples, 0.15%)</title><rect x="1074.5" y="533" width="1.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1077.51" y="543.5" ></text>
</g>
<g >
<title>AllocSetFree (2,358,098,746 samples, 0.02%)</title><rect x="226.5" y="533" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="229.46" y="543.5" ></text>
</g>
<g >
<title>palloc0 (5,834,045,378 samples, 0.06%)</title><rect x="1165.7" y="757" width="0.8" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1168.74" y="767.5" ></text>
</g>
<g >
<title>_bt_first (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="325" width="0.3" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="1161.90" y="335.5" ></text>
</g>
<g >
<title>AllocSetAllocFromNewBlock (7,130,309,679 samples, 0.08%)</title><rect x="280.1" y="341" width="0.9" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="283.11" y="351.5" ></text>
</g>
<g >
<title>table_slot_callbacks (868,418,531 samples, 0.01%)</title><rect x="1186.4" y="757" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1189.41" y="767.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (6,851,537,211 samples, 0.07%)</title><rect x="320.1" y="213" width="0.8" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="323.07" y="223.5" ></text>
</g>
<g >
<title>ReadBuffer_common (22,438,003,476 samples, 0.24%)</title><rect x="407.1" y="357" width="2.8" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="410.08" y="367.5" ></text>
</g>
<g >
<title>pfree (1,197,179,823 samples, 0.01%)</title><rect x="994.4" y="341" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="997.39" y="351.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableXid (4,387,636,700 samples, 0.05%)</title><rect x="305.5" y="229" width="0.6" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="308.53" y="239.5" ></text>
</g>
<g >
<title>BTreeTupleIsPosting (975,411,587 samples, 0.01%)</title><rect x="332.9" y="213" width="0.1" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="335.91" y="223.5" ></text>
</g>
<g >
<title>rewriteTargetListIU (13,147,693,076 samples, 0.14%)</title><rect x="860.5" y="517" width="1.7" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="863.53" y="527.5" ></text>
</g>
<g >
<title>_bt_saveitem (1,404,110,424 samples, 0.01%)</title><rect x="329.6" y="229" width="0.2" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="332.62" y="239.5" ></text>
</g>
<g >
<title>distribute_row_identity_vars (2,275,571,757 samples, 0.02%)</title><rect x="978.2" y="469" width="0.3" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="981.24" y="479.5" ></text>
</g>
<g >
<title>ProcArrayEndTransaction (817,814,156 samples, 0.01%)</title><rect x="87.6" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="90.63" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (884,781,725 samples, 0.01%)</title><rect x="95.7" y="181" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="98.68" y="191.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,545,141,511 samples, 0.02%)</title><rect x="388.9" y="261" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="391.88" y="271.5" ></text>
</g>
<g >
<title>PageGetHeapFreeSpace (1,910,859,571 samples, 0.02%)</title><rect x="81.5" y="757" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="84.50" y="767.5" ></text>
</g>
<g >
<title>_bt_relbuf (6,981,656,451 samples, 0.07%)</title><rect x="326.4" y="229" width="0.8" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="329.37" y="239.5" ></text>
</g>
<g >
<title>make_one_rel (487,195,112,386 samples, 5.13%)</title><rect x="981.7" y="469" width="60.5" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="984.66" y="479.5" >make_o..</text>
</g>
<g >
<title>psi_task_change (1,019,531,428 samples, 0.01%)</title><rect x="202.0" y="309" width="0.2" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="205.04" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (937,633,953 samples, 0.01%)</title><rect x="1063.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1066.66" y="399.5" ></text>
</g>
<g >
<title>pgstat_get_xact_stack_level (1,093,104,099 samples, 0.01%)</title><rect x="1172.1" y="757" width="0.1" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="1175.08" y="767.5" ></text>
</g>
<g >
<title>lappend (2,602,745,058 samples, 0.03%)</title><rect x="457.2" y="453" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="460.20" y="463.5" ></text>
</g>
<g >
<title>IsCatalogRelationOid (1,020,373,702 samples, 0.01%)</title><rect x="54.7" y="757" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="57.69" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,116,489,980 samples, 0.01%)</title><rect x="103.5" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="106.47" y="447.5" ></text>
</g>
<g >
<title>LockBuffer (2,621,671,222 samples, 0.03%)</title><rect x="326.9" y="197" width="0.3" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="329.91" y="207.5" ></text>
</g>
<g >
<title>ExecProcNode (502,197,614,417 samples, 5.29%)</title><rect x="281.5" y="437" width="62.4" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="284.49" y="447.5" >ExecPr..</text>
</g>
<g >
<title>wipe_mem (7,648,866,061 samples, 0.08%)</title><rect x="150.9" y="549" width="0.9" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="153.85" y="559.5" ></text>
</g>
<g >
<title>palloc (2,923,490,925 samples, 0.03%)</title><rect x="1078.0" y="565" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1080.96" y="575.5" ></text>
</g>
<g >
<title>create_plan_recurse (45,427,493,229 samples, 0.48%)</title><rect x="886.8" y="437" width="5.6" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="889.79" y="447.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,081,809,951 samples, 0.01%)</title><rect x="330.7" y="229" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="333.67" y="239.5" ></text>
</g>
<g >
<title>make_const (6,045,244,580 samples, 0.06%)</title><rect x="854.6" y="405" width="0.7" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="857.57" y="415.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,195,862,305 samples, 0.01%)</title><rect x="1081.9" y="581" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1084.86" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,020,960,515 samples, 0.01%)</title><rect x="893.5" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="896.48" y="415.5" ></text>
</g>
<g >
<title>palloc0 (1,340,554,996 samples, 0.01%)</title><rect x="1069.6" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1072.62" y="447.5" ></text>
</g>
<g >
<title>palloc0 (1,926,562,203 samples, 0.02%)</title><rect x="927.9" y="389" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="930.89" y="399.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,260,159,562 samples, 0.01%)</title><rect x="165.1" y="309" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="168.10" y="319.5" ></text>
</g>
<g >
<title>process_equivalence (40,878,816,646 samples, 0.43%)</title><rect x="967.7" y="405" width="5.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="970.70" y="415.5" ></text>
</g>
<g >
<title>ExecEvalSysVar (2,421,014,897 samples, 0.03%)</title><rect x="290.2" y="277" width="0.3" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="293.16" y="287.5" ></text>
</g>
<g >
<title>examine_variable (40,094,460,673 samples, 0.42%)</title><rect x="1030.5" y="245" width="5.0" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="1033.52" y="255.5" ></text>
</g>
<g >
<title>pgstat_report_plan_id (2,821,906,507 samples, 0.03%)</title><rect x="875.1" y="533" width="0.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="878.06" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,384,080,596 samples, 0.01%)</title><rect x="922.6" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="925.60" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (832,872,702 samples, 0.01%)</title><rect x="992.8" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="995.84" y="303.5" ></text>
</g>
<g >
<title>palloc (1,285,286,936 samples, 0.01%)</title><rect x="914.8" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="917.82" y="447.5" ></text>
</g>
<g >
<title>get_futex_key (4,636,175,004 samples, 0.05%)</title><rect x="489.5" y="309" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="492.49" y="319.5" ></text>
</g>
<g >
<title>LWLockConditionalAcquire (3,802,505,677 samples, 0.04%)</title><rect x="477.6" y="453" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="480.57" y="463.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,966,317,843 samples, 0.04%)</title><rect x="1055.4" y="421" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1058.44" y="431.5" ></text>
</g>
<g >
<title>ExecutorRun (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="549" width="1.9" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="129.31" y="559.5" ></text>
</g>
<g >
<title>hash_search (4,157,801,442 samples, 0.04%)</title><rect x="831.6" y="373" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="834.63" y="383.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,283,778,308 samples, 0.01%)</title><rect x="1038.0" y="325" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1041.00" y="335.5" ></text>
</g>
<g >
<title>_mm_set1_epi8 (1,523,728,830 samples, 0.02%)</title><rect x="1081.4" y="485" width="0.2" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1084.42" y="495.5" ></text>
</g>
<g >
<title>ExecProcNode (1,029,469,534 samples, 0.01%)</title><rect x="45.4" y="757" width="0.2" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="48.42" y="767.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,147,150,128 samples, 0.01%)</title><rect x="1021.0" y="277" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1023.97" y="287.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (886,557,379 samples, 0.01%)</title><rect x="940.4" y="357" width="0.1" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="943.37" y="367.5" ></text>
</g>
<g >
<title>bms_union (851,917,911 samples, 0.01%)</title><rect x="879.4" y="485" width="0.1" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="882.43" y="495.5" ></text>
</g>
<g >
<title>main (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="757" width="0.4" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="1161.90" y="767.5" ></text>
</g>
<g >
<title>IndexNext (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="357" width="1.9" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="129.31" y="367.5" ></text>
</g>
<g >
<title>AllocSetDelete (6,623,407,519 samples, 0.07%)</title><rect x="225.3" y="533" width="0.8" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="228.28" y="543.5" ></text>
</g>
<g >
<title>postmaster_child_launch (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="693" width="0.4" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1161.90" y="703.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple_internal (2,624,843,423 samples, 0.03%)</title><rect x="350.0" y="309" width="0.3" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="352.97" y="319.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,084,953,129 samples, 0.01%)</title><rect x="104.4" y="405" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="107.42" y="415.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,566,854,814 samples, 0.05%)</title><rect x="1033.8" y="149" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1036.80" y="159.5" ></text>
</g>
<g >
<title>compute_new_xmax_infomask (1,118,848,271 samples, 0.01%)</title><rect x="1128.7" y="757" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1131.70" y="767.5" ></text>
</g>
<g >
<title>new_list (1,778,039,325 samples, 0.02%)</title><rect x="975.9" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="978.86" y="415.5" ></text>
</g>
<g >
<title>table_open (16,415,319,603 samples, 0.17%)</title><rect x="923.1" y="469" width="2.0" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="926.09" y="479.5" ></text>
</g>
<g >
<title>MemoryContextSwitchTo (3,910,100,198 samples, 0.04%)</title><rect x="79.5" y="757" width="0.5" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="82.51" y="767.5" ></text>
</g>
<g >
<title>PageValidateSpecialPointer (829,187,176 samples, 0.01%)</title><rect x="338.7" y="229" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="341.72" y="239.5" ></text>
</g>
<g >
<title>exprType (1,071,916,546 samples, 0.01%)</title><rect x="1046.5" y="453" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1049.52" y="463.5" ></text>
</g>
<g >
<title>AssertCouldGetRelation (2,020,120,019 samples, 0.02%)</title><rect x="30.3" y="757" width="0.2" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="33.29" y="767.5" ></text>
</g>
<g >
<title>tag_hash (2,778,070,350 samples, 0.03%)</title><rect x="374.4" y="261" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="377.38" y="271.5" ></text>
</g>
<g >
<title>lappend_oid (2,859,368,483 samples, 0.03%)</title><rect x="911.2" y="469" width="0.4" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="914.25" y="479.5" ></text>
</g>
<g >
<title>restriction_is_or_clause (1,225,103,693 samples, 0.01%)</title><rect x="1177.0" y="757" width="0.1" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="1179.96" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache3 (5,287,457,532 samples, 0.06%)</title><rect x="435.1" y="373" width="0.7" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="438.13" y="383.5" ></text>
</g>
<g >
<title>index_beginscan (28,993,017,320 samples, 0.31%)</title><rect x="292.4" y="325" width="3.6" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="295.39" y="335.5" ></text>
</g>
<g >
<title>ExecModifyTable (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="517" width="2.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="87.32" y="527.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (11,175,660,902 samples, 0.12%)</title><rect x="862.4" y="485" width="1.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="865.43" y="495.5" ></text>
</g>
<g >
<title>SearchSysCache1 (2,260,079,369 samples, 0.02%)</title><rect x="94.9" y="757" width="0.2" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="97.86" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,796,617,857 samples, 0.02%)</title><rect x="951.1" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="954.06" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,024,835,233 samples, 0.01%)</title><rect x="377.8" y="341" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="380.79" y="351.5" ></text>
</g>
<g >
<title>palloc0 (1,539,185,187 samples, 0.02%)</title><rect x="927.6" y="405" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="930.63" y="415.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (23,143,335,628 samples, 0.24%)</title><rect x="900.8" y="437" width="2.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="903.83" y="447.5" ></text>
</g>
<g >
<title>AllocSetFree (2,076,986,542 samples, 0.02%)</title><rect x="241.4" y="373" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="244.42" y="383.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,434,114,468 samples, 0.02%)</title><rect x="236.3" y="469" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="239.26" y="479.5" ></text>
</g>
<g >
<title>lappend_oid (3,399,494,699 samples, 0.04%)</title><rect x="962.8" y="373" width="0.5" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="965.85" y="383.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,209,474,426 samples, 0.04%)</title><rect x="882.5" y="389" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="885.47" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,099,251,549 samples, 0.01%)</title><rect x="994.0" y="309" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="996.96" y="319.5" ></text>
</g>
<g >
<title>index_getnext_slot (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="373" width="0.3" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="1161.90" y="383.5" ></text>
</g>
<g >
<title>sched_clock_cpu (959,903,772 samples, 0.01%)</title><rect x="174.2" y="261" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="177.17" y="271.5" ></text>
</g>
<g >
<title>llseek@GLIBC_2.2.5 (13,286,004,692 samples, 0.14%)</title><rect x="936.6" y="229" width="1.6" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="939.59" y="239.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,402,440,460 samples, 0.01%)</title><rect x="1063.8" y="421" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1066.78" y="431.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (3,746,641,229 samples, 0.04%)</title><rect x="270.3" y="309" width="0.5" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="273.29" y="319.5" ></text>
</g>
<g >
<title>bms_add_members (2,553,591,666 samples, 0.03%)</title><rect x="993.8" y="357" width="0.3" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="996.83" y="367.5" ></text>
</g>
<g >
<title>get_rightop (1,271,260,271 samples, 0.01%)</title><rect x="970.9" y="389" width="0.2" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="973.90" y="399.5" ></text>
</g>
<g >
<title>LockBufHdr (2,933,527,407 samples, 0.03%)</title><rect x="325.7" y="213" width="0.4" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="328.73" y="223.5" ></text>
</g>
<g >
<title>relation_open (28,786,113,891 samples, 0.30%)</title><rect x="940.3" y="389" width="3.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="943.25" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,150,986,441 samples, 0.01%)</title><rect x="959.5" y="261" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="962.45" y="271.5" ></text>
</g>
<g >
<title>RelationGetStatExtList (980,109,310 samples, 0.01%)</title><rect x="939.5" y="389" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="942.52" y="399.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,247,691,262 samples, 0.01%)</title><rect x="940.1" y="373" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="943.06" y="383.5" ></text>
</g>
<g >
<title>get_func_retset (6,368,280,256 samples, 0.07%)</title><rect x="836.8" y="373" width="0.8" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="839.80" y="383.5" ></text>
</g>
<g >
<title>enlargeStringInfo (846,147,151 samples, 0.01%)</title><rect x="190.2" y="549" width="0.1" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="193.17" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,172,011,044 samples, 0.01%)</title><rect x="295.8" y="261" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="298.84" y="271.5" ></text>
</g>
<g >
<title>palloc (1,239,889,070 samples, 0.01%)</title><rect x="1047.5" y="469" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1050.46" y="479.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,795,219,794 samples, 0.03%)</title><rect x="223.2" y="501" width="0.4" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="226.23" y="511.5" ></text>
</g>
<g >
<title>check_index_predicates (924,845,440 samples, 0.01%)</title><rect x="1126.1" y="757" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1129.08" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,718,843,377 samples, 0.02%)</title><rect x="298.6" y="245" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="301.57" y="255.5" ></text>
</g>
<g >
<title>skb_release_head_state (8,845,529,491 samples, 0.09%)</title><rect x="183.0" y="357" width="1.1" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="186.00" y="367.5" ></text>
</g>
<g >
<title>SimpleLruReadPage_ReadOnly (5,005,712,871 samples, 0.05%)</title><rect x="1148.2" y="645" width="0.6" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="1151.16" y="655.5" ></text>
</g>
<g >
<title>schedule (107,351,625,879 samples, 1.13%)</title><rect x="161.5" y="373" width="13.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="164.54" y="383.5" ></text>
</g>
<g >
<title>buildRelationAliases (15,788,729,485 samples, 0.17%)</title><rect x="816.5" y="453" width="2.0" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="819.51" y="463.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,847,519,617 samples, 0.03%)</title><rect x="348.2" y="373" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="351.18" y="383.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (845,409,504 samples, 0.01%)</title><rect x="299.7" y="149" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="302.74" y="159.5" ></text>
</g>
<g >
<title>mutex_unlock (1,091,888,881 samples, 0.01%)</title><rect x="185.2" y="373" width="0.2" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="188.24" y="383.5" ></text>
</g>
<g >
<title>hash_search (7,629,274,665 samples, 0.08%)</title><rect x="1022.5" y="341" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1025.49" y="351.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,050,177,499 samples, 0.01%)</title><rect x="1045.3" y="421" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1048.30" y="431.5" ></text>
</g>
<g >
<title>dclist_init (986,132,765 samples, 0.01%)</title><rect x="1131.8" y="757" width="0.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="1134.85" y="767.5" ></text>
</g>
<g >
<title>DatumGetInt32 (2,582,390,080 samples, 0.03%)</title><rect x="114.1" y="741" width="0.3" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="117.10" y="751.5" ></text>
</g>
<g >
<title>rseq_get_rseq_cs.isra.0 (3,874,753,375 samples, 0.04%)</title><rect x="176.4" y="389" width="0.5" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="179.44" y="399.5" ></text>
</g>
<g >
<title>list_length (899,809,396 samples, 0.01%)</title><rect x="457.9" y="453" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="460.91" y="463.5" ></text>
</g>
<g >
<title>newNode (1,900,865,473 samples, 0.02%)</title><rect x="897.0" y="453" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="899.97" y="463.5" ></text>
</g>
<g >
<title>ConditionVariableBroadcast (4,711,950,730 samples, 0.05%)</title><rect x="497.9" y="453" width="0.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="500.94" y="463.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (2,441,914,755 samples, 0.03%)</title><rect x="347.8" y="389" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="350.80" y="399.5" ></text>
</g>
<g >
<title>PortalRun (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="597" width="1.9" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="129.31" y="607.5" ></text>
</g>
<g >
<title>ExecInitNode (1,157,501,317 samples, 0.01%)</title><rect x="44.3" y="757" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="47.29" y="767.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (34,604,313,801 samples, 0.36%)</title><rect x="1002.5" y="197" width="4.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1005.46" y="207.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (944,898,269 samples, 0.01%)</title><rect x="291.5" y="165" width="0.1" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="294.47" y="175.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (11,653,525,725 samples, 0.12%)</title><rect x="147.4" y="533" width="1.5" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="150.41" y="543.5" ></text>
</g>
<g >
<title>update_process_times (2,315,738,193 samples, 0.02%)</title><rect x="795.8" y="421" width="0.3" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="798.77" y="431.5" ></text>
</g>
<g >
<title>palloc0 (2,480,105,967 samples, 0.03%)</title><rect x="432.9" y="341" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="435.88" y="351.5" ></text>
</g>
<g >
<title>tag_hash (4,697,747,317 samples, 0.05%)</title><rect x="831.0" y="341" width="0.6" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="834.03" y="351.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,556,602,322 samples, 0.02%)</title><rect x="1112.9" y="645" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1115.87" y="655.5" ></text>
</g>
<g >
<title>[unknown] (89,339,434,604 samples, 0.94%)</title><rect x="112.6" y="757" width="11.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="115.62" y="767.5" ></text>
</g>
<g >
<title>ExecPushExprSetupSteps (3,307,470,713 samples, 0.03%)</title><rect x="437.8" y="389" width="0.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="440.81" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,263,064,757 samples, 0.01%)</title><rect x="823.5" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="826.52" y="367.5" ></text>
</g>
<g >
<title>ExecCreateExprSetupSteps (9,127,568,800 samples, 0.10%)</title><rect x="437.8" y="405" width="1.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="440.78" y="415.5" ></text>
</g>
<g >
<title>internal_flush_buffer (156,540,332,491 samples, 1.65%)</title><rect x="190.5" y="549" width="19.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="193.49" y="559.5" ></text>
</g>
<g >
<title>relation_close (2,400,035,284 samples, 0.03%)</title><rect x="866.5" y="501" width="0.3" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="869.47" y="511.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (3,306,117,952 samples, 0.03%)</title><rect x="1015.6" y="325" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1018.61" y="335.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (1,460,723,842 samples, 0.02%)</title><rect x="1084.4" y="757" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1087.41" y="767.5" ></text>
</g>
<g >
<title>AtEOXact_Aio (3,502,287,000 samples, 0.04%)</title><rect x="468.7" y="517" width="0.4" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="471.70" y="527.5" ></text>
</g>
<g >
<title>SearchSysCache1 (8,452,558,700 samples, 0.09%)</title><rect x="443.2" y="357" width="1.0" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="446.17" y="367.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVisibility (33,004,078,477 samples, 0.35%)</title><rect x="306.2" y="245" width="4.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="309.18" y="255.5" ></text>
</g>
<g >
<title>heap_attisnull (869,254,699 samples, 0.01%)</title><rect x="931.5" y="389" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="934.51" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCacheList (1,405,300,909 samples, 0.01%)</title><rect x="961.5" y="373" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="964.50" y="383.5" ></text>
</g>
<g >
<title>new_list (1,884,515,894 samples, 0.02%)</title><rect x="955.7" y="293" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="958.72" y="303.5" ></text>
</g>
<g >
<title>make_oper_cache_key (8,962,730,364 samples, 0.09%)</title><rect x="853.1" y="389" width="1.1" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="856.10" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,157,377,129 samples, 0.01%)</title><rect x="513.2" y="389" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="516.20" y="399.5" ></text>
</g>
<g >
<title>fdget (3,251,770,612 samples, 0.03%)</title><rect x="499.9" y="405" width="0.4" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="502.89" y="415.5" ></text>
</g>
<g >
<title>ExecInitRangeTable (2,768,204,637 samples, 0.03%)</title><rect x="459.4" y="485" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="462.41" y="495.5" ></text>
</g>
<g >
<title>ensure_tabstat_xact_level (1,169,732,748 samples, 0.01%)</title><rect x="1133.6" y="757" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1136.64" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,041,251,132 samples, 0.02%)</title><rect x="448.3" y="341" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="451.31" y="351.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (5,734,115,888 samples, 0.06%)</title><rect x="757.9" y="485" width="0.7" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="760.92" y="495.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (971,078,648 samples, 0.01%)</title><rect x="128.4" y="437" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="131.36" y="447.5" ></text>
</g>
<g >
<title>BackendMain (83,672,568,701 samples, 0.88%)</title><rect x="95.7" y="709" width="10.4" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="98.68" y="719.5" ></text>
</g>
<g >
<title>hash_seq_search (6,581,858,005 samples, 0.07%)</title><rect x="473.3" y="501" width="0.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="476.26" y="511.5" ></text>
</g>
<g >
<title>update_irq_load_avg (854,886,332 samples, 0.01%)</title><rect x="174.8" y="325" width="0.1" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="177.76" y="335.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (10,269,144,712 samples, 0.11%)</title><rect x="425.4" y="325" width="1.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="428.38" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,018,707,711 samples, 0.01%)</title><rect x="934.3" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="937.29" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,333,586,559 samples, 0.02%)</title><rect x="119.7" y="741" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="122.71" y="751.5" ></text>
</g>
<g >
<title>colNameToVar (20,653,854,612 samples, 0.22%)</title><rect x="855.8" y="389" width="2.6" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="858.81" y="399.5" ></text>
</g>
<g >
<title>MarkBufferDirty (7,185,343,615 samples, 0.08%)</title><rect x="376.3" y="357" width="0.9" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="379.26" y="367.5" ></text>
</g>
<g >
<title>palloc0 (1,947,510,383 samples, 0.02%)</title><rect x="856.6" y="325" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="859.61" y="335.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple (3,337,899,521 samples, 0.04%)</title><rect x="349.9" y="325" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="352.89" y="335.5" ></text>
</g>
<g >
<title>new_list (1,258,729,468 samples, 0.01%)</title><rect x="849.3" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="852.34" y="399.5" ></text>
</g>
<g >
<title>index_getnext_tid (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="373" width="2.6" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="87.32" y="383.5" ></text>
</g>
<g >
<title>eqsel (53,199,705,465 samples, 0.56%)</title><rect x="1029.5" y="293" width="6.6" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1032.50" y="303.5" ></text>
</g>
<g >
<title>free_attstatsslot (2,351,719,028 samples, 0.02%)</title><rect x="1000.1" y="293" width="0.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1003.12" y="303.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,434,105,134 samples, 0.05%)</title><rect x="397.9" y="341" width="0.6" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="400.91" y="351.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,989,861,894 samples, 0.07%)</title><rect x="1136.6" y="757" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1139.60" y="767.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,845,994,599 samples, 0.02%)</title><rect x="496.8" y="421" width="0.3" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="499.84" y="431.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (1,910,003,973 samples, 0.02%)</title><rect x="1132.4" y="757" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="1135.35" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (12,389,056,324 samples, 0.13%)</title><rect x="954.5" y="421" width="1.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="957.50" y="431.5" ></text>
</g>
<g >
<title>assign_query_collations_walker (47,710,469,842 samples, 0.50%)</title><rect x="806.2" y="453" width="5.9" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="809.16" y="463.5" ></text>
</g>
<g >
<title>RelationGetSmgr (847,401,374 samples, 0.01%)</title><rect x="90.2" y="757" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="93.22" y="767.5" ></text>
</g>
<g >
<title>check_log_duration (1,630,032,952 samples, 0.02%)</title><rect x="464.9" y="581" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="467.88" y="591.5" ></text>
</g>
<g >
<title>check_index_only (1,139,327,858 samples, 0.01%)</title><rect x="1125.9" y="757" width="0.2" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="1128.94" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions (2,257,800,175 samples, 0.02%)</title><rect x="1134.8" y="757" width="0.3" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1137.78" y="767.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (63,132,965,854 samples, 0.66%)</title><rect x="179.1" y="485" width="7.9" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="182.14" y="495.5" ></text>
</g>
<g >
<title>is_orclause (1,290,136,142 samples, 0.01%)</title><rect x="1153.5" y="757" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1156.48" y="767.5" ></text>
</g>
<g >
<title>pfree (1,649,624,927 samples, 0.02%)</title><rect x="403.5" y="357" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="406.49" y="367.5" ></text>
</g>
<g >
<title>lappend (2,253,494,471 samples, 0.02%)</title><rect x="896.7" y="453" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="899.68" y="463.5" ></text>
</g>
<g >
<title>__x64_sys_pwrite64 (57,130,612,815 samples, 0.60%)</title><rect x="499.7" y="421" width="7.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="502.71" y="431.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,258,249,873 samples, 0.03%)</title><rect x="298.8" y="245" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="301.78" y="255.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (13,574,304,006 samples, 0.14%)</title><rect x="830.5" y="405" width="1.7" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="833.47" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (7,256,377,248 samples, 0.08%)</title><rect x="912.5" y="437" width="0.9" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="915.54" y="447.5" ></text>
</g>
<g >
<title>ExecInitModifyTable (1,692,601,893 samples, 0.02%)</title><rect x="44.1" y="757" width="0.2" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="47.08" y="767.5" ></text>
</g>
<g >
<title>palloc (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="597" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1137.90" y="607.5" ></text>
</g>
<g >
<title>bms_copy (1,612,480,030 samples, 0.02%)</title><rect x="966.0" y="357" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="969.04" y="367.5" ></text>
</g>
<g >
<title>ReleaseSysCache (844,112,802 samples, 0.01%)</title><rect x="434.9" y="389" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="437.91" y="399.5" ></text>
</g>
<g >
<title>__smp_call_single_queue (1,157,303,185 samples, 0.01%)</title><rect x="494.7" y="277" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="497.73" y="287.5" ></text>
</g>
<g >
<title>index_getnext_tid (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="389" width="6.8" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="98.68" y="399.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (7,134,108,485 samples, 0.08%)</title><rect x="1004.0" y="165" width="0.9" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="1006.98" y="175.5" ></text>
</g>
<g >
<title>exec_rt_fetch (1,238,494,619 samples, 0.01%)</title><rect x="451.6" y="421" width="0.2" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="454.62" y="431.5" ></text>
</g>
<g >
<title>AtEOXact_PgStat_Database (833,353,343 samples, 0.01%)</title><rect x="32.1" y="757" width="0.1" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="35.13" y="767.5" ></text>
</g>
<g >
<title>heap_page_prune_execute (1,817,068,179 samples, 0.02%)</title><rect x="1146.4" y="741" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1149.36" y="751.5" ></text>
</g>
<g >
<title>ExecModifyTable (1,146,196,340,550 samples, 12.07%)</title><rect x="267.5" y="453" width="142.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="270.53" y="463.5" >ExecModifyTable</text>
</g>
<g >
<title>pull_var_clause (15,317,660,426 samples, 0.16%)</title><rect x="954.1" y="453" width="1.9" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="957.14" y="463.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,497,198,331 samples, 0.02%)</title><rect x="113.0" y="741" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="115.95" y="751.5" ></text>
</g>
<g >
<title>clauselist_selectivity_ext (76,941,918,824 samples, 0.81%)</title><rect x="1027.9" y="373" width="9.5" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="1030.88" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (920,281,834 samples, 0.01%)</title><rect x="102.3" y="149" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="105.25" y="159.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,341,249,667 samples, 0.01%)</title><rect x="388.9" y="229" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="391.90" y="239.5" ></text>
</g>
<g >
<title>PageGetMaxOffsetNumber (1,662,488,278 samples, 0.02%)</title><rect x="82.4" y="757" width="0.2" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="85.36" y="767.5" ></text>
</g>
<g >
<title>ProcArrayGroupClearXid (3,461,918,465 samples, 0.04%)</title><rect x="475.5" y="501" width="0.4" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="478.50" y="511.5" ></text>
</g>
<g >
<title>SearchCatCache (24,569,363,402 samples, 0.26%)</title><rect x="826.2" y="341" width="3.0" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="829.17" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,241,357,311 samples, 0.02%)</title><rect x="1055.0" y="421" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1057.95" y="431.5" ></text>
</g>
<g >
<title>TransactionIdGetStatus (6,580,710,230 samples, 0.07%)</title><rect x="1148.0" y="661" width="0.8" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="1150.96" y="671.5" ></text>
</g>
<g >
<title>BufferAlloc (18,282,572,212 samples, 0.19%)</title><rect x="100.1" y="197" width="2.3" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="103.13" y="207.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (3,471,676,111 samples, 0.04%)</title><rect x="757.2" y="517" width="0.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="760.18" y="527.5" ></text>
</g>
<g >
<title>hash_bytes (6,860,546,208 samples, 0.07%)</title><rect x="867.6" y="405" width="0.8" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="870.58" y="415.5" ></text>
</g>
<g >
<title>nameeqfast (2,407,580,003 samples, 0.03%)</title><rect x="827.5" y="293" width="0.3" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="830.50" y="303.5" ></text>
</g>
<g >
<title>AtEOXact_SMgr (863,248,197 samples, 0.01%)</title><rect x="472.1" y="517" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="475.07" y="527.5" ></text>
</g>
<g >
<title>palloc0 (2,278,239,659 samples, 0.02%)</title><rect x="816.1" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="819.12" y="447.5" ></text>
</g>
<g >
<title>ReadBufferExtended (1,265,816,152 samples, 0.01%)</title><rect x="337.1" y="197" width="0.2" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="340.13" y="207.5" ></text>
</g>
<g >
<title>pfree (5,011,230,182 samples, 0.05%)</title><rect x="525.6" y="437" width="0.6" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="528.60" y="447.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (978,727,143 samples, 0.01%)</title><rect x="186.8" y="453" width="0.1" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="189.83" y="463.5" ></text>
</g>
<g >
<title>ExecAssignExprContext (10,106,669,788 samples, 0.11%)</title><rect x="272.2" y="421" width="1.3" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="275.22" y="431.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (11,334,684,262 samples, 0.12%)</title><rect x="923.2" y="437" width="1.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="926.16" y="447.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (1,673,996,710 samples, 0.02%)</title><rect x="222.6" y="533" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="225.64" y="543.5" ></text>
</g>
<g >
<title>palloc0 (1,443,935,382 samples, 0.02%)</title><rect x="973.6" y="277" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="976.63" y="287.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (1,004,886,995 samples, 0.01%)</title><rect x="94.1" y="741" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="97.13" y="751.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,103,431,194 samples, 0.01%)</title><rect x="409.7" y="277" width="0.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="412.72" y="287.5" ></text>
</g>
<g >
<title>hash_bytes (2,710,266,575 samples, 0.03%)</title><rect x="100.2" y="133" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="103.19" y="143.5" ></text>
</g>
<g >
<title>compare_pathkeys (902,596,146 samples, 0.01%)</title><rect x="1128.5" y="757" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1131.52" y="767.5" ></text>
</g>
<g >
<title>list_make1_impl (4,444,474,796 samples, 0.05%)</title><rect x="1114.1" y="741" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1117.07" y="751.5" ></text>
</g>
<g >
<title>int4eqfast (1,287,095,810 samples, 0.01%)</title><rect x="1152.3" y="757" width="0.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="1155.27" y="767.5" ></text>
</g>
<g >
<title>HeapTupleHasNulls (1,383,265,197 samples, 0.01%)</title><rect x="51.4" y="757" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="54.44" y="767.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,614,880,324 samples, 0.02%)</title><rect x="301.7" y="101" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="304.72" y="111.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="405" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="89.91" y="415.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (810,036,557 samples, 0.01%)</title><rect x="1102.3" y="693" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="1105.35" y="703.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,121,360,983 samples, 0.01%)</title><rect x="848.3" y="373" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="851.31" y="383.5" ></text>
</g>
<g >
<title>ReleaseSysCache (863,676,854 samples, 0.01%)</title><rect x="1055.3" y="421" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1058.33" y="431.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,874,108,414 samples, 0.03%)</title><rect x="406.4" y="373" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="409.37" y="383.5" ></text>
</g>
<g >
<title>table_relation_estimate_size (29,809,742,745 samples, 0.31%)</title><rect x="935.1" y="389" width="3.7" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="938.12" y="399.5" ></text>
</g>
<g >
<title>LockReassignOwner (3,111,228,849 samples, 0.03%)</title><rect x="227.7" y="517" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="230.66" y="527.5" ></text>
</g>
<g >
<title>relation_openrv_extended (104,493,253,962 samples, 1.10%)</title><rect x="820.3" y="437" width="13.0" height="15.0" fill="rgb(231,119,28)" rx="2" ry="2" />
<text  x="823.34" y="447.5" ></text>
</g>
<g >
<title>ScanKeywordLookup (19,767,788,636 samples, 0.21%)</title><rect x="1109.3" y="709" width="2.4" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="1112.28" y="719.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,228,339,378 samples, 0.01%)</title><rect x="1009.1" y="229" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1012.06" y="239.5" ></text>
</g>
<g >
<title>UnpinBuffer (5,362,690,288 samples, 0.06%)</title><rect x="340.0" y="213" width="0.6" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="342.97" y="223.5" ></text>
</g>
<g >
<title>ReadBuffer_common (28,114,993,354 samples, 0.30%)</title><rect x="299.5" y="213" width="3.5" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="302.52" y="223.5" ></text>
</g>
<g >
<title>TrackNewBufferPin (1,542,311,900 samples, 0.02%)</title><rect x="98.6" y="165" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="101.61" y="175.5" ></text>
</g>
<g >
<title>TransactionIdSetPageStatus (25,332,091,317 samples, 0.27%)</title><rect x="477.5" y="469" width="3.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="480.48" y="479.5" ></text>
</g>
<g >
<title>HeapTupleHeaderGetRawXmax (973,575,454 samples, 0.01%)</title><rect x="51.7" y="757" width="0.1" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="54.69" y="767.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (10,237,129,134 samples, 0.11%)</title><rect x="954.8" y="405" width="1.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="957.77" y="415.5" ></text>
</g>
<g >
<title>MakeSingleTupleTableSlot (20,899,212,421 samples, 0.22%)</title><rect x="278.5" y="405" width="2.6" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="281.51" y="415.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,133,729,906 samples, 0.04%)</title><rect x="1010.0" y="261" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1012.99" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,290,723,068 samples, 0.01%)</title><rect x="522.4" y="421" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="525.41" y="431.5" ></text>
</g>
<g >
<title>ReleaseSysCache (891,724,804 samples, 0.01%)</title><rect x="1040.4" y="357" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1043.36" y="367.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (924,145,690 samples, 0.01%)</title><rect x="998.8" y="277" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1001.79" y="287.5" ></text>
</g>
<g >
<title>ExecUpdateAct (410,653,097,727 samples, 4.32%)</title><rect x="344.5" y="421" width="51.0" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="347.53" y="431.5" >ExecU..</text>
</g>
<g >
<title>__x64_sys_futex (56,750,265,250 samples, 0.60%)</title><rect x="483.0" y="389" width="7.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="486.02" y="399.5" ></text>
</g>
<g >
<title>socket_putmessage (2,796,123,811 samples, 0.03%)</title><rect x="189.8" y="565" width="0.3" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="192.78" y="575.5" ></text>
</g>
<g >
<title>coerce_to_target_type (5,490,557,935 samples, 0.06%)</title><rect x="843.7" y="437" width="0.7" height="15.0" fill="rgb(232,124,29)" rx="2" ry="2" />
<text  x="846.72" y="447.5" ></text>
</g>
<g >
<title>transformAExprOp (55,147,723,769 samples, 0.58%)</title><rect x="836.2" y="405" width="6.9" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="839.23" y="415.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (823,924,069 samples, 0.01%)</title><rect x="48.2" y="757" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="51.18" y="767.5" ></text>
</g>
<g >
<title>LockBuffer (2,728,487,764 samples, 0.03%)</title><rect x="341.6" y="213" width="0.3" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="344.58" y="223.5" ></text>
</g>
<g >
<title>list_make1_impl (1,671,603,348 samples, 0.02%)</title><rect x="909.2" y="453" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="912.16" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,796,322,316 samples, 0.03%)</title><rect x="1166.1" y="741" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1169.12" y="751.5" ></text>
</g>
<g >
<title>gup_fast (1,222,575,048 samples, 0.01%)</title><rect x="368.0" y="117" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="370.98" y="127.5" ></text>
</g>
<g >
<title>simplify_function (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="629" width="0.3" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1188.13" y="639.5" ></text>
</g>
<g >
<title>hash_search (3,962,408,181 samples, 0.04%)</title><rect x="814.3" y="421" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="817.34" y="431.5" ></text>
</g>
<g >
<title>lcons (2,094,275,241 samples, 0.02%)</title><rect x="984.3" y="405" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="987.28" y="415.5" ></text>
</g>
<g >
<title>palloc0 (1,136,996,027 samples, 0.01%)</title><rect x="456.2" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="459.21" y="447.5" ></text>
</g>
<g >
<title>pfree (1,585,588,697 samples, 0.02%)</title><rect x="224.3" y="549" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="227.32" y="559.5" ></text>
</g>
<g >
<title>XLogFlush (1,699,640,404 samples, 0.02%)</title><rect x="110.8" y="757" width="0.2" height="15.0" fill="rgb(251,213,50)" rx="2" ry="2" />
<text  x="113.78" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,708,967,712 samples, 0.02%)</title><rect x="408.5" y="277" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="411.53" y="287.5" ></text>
</g>
<g >
<title>bms_free (1,706,040,789 samples, 0.02%)</title><rect x="965.7" y="373" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="968.67" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (1,408,297,201 samples, 0.01%)</title><rect x="217.3" y="533" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="220.33" y="543.5" ></text>
</g>
<g >
<title>namestrcmp (1,172,745,174 samples, 0.01%)</title><rect x="1162.4" y="757" width="0.2" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="1165.41" y="767.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (872,093,346 samples, 0.01%)</title><rect x="356.1" y="245" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="359.06" y="255.5" ></text>
</g>
<g >
<title>__cgroup_account_cputime (1,204,085,282 samples, 0.01%)</title><rect x="487.6" y="181" width="0.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="490.61" y="191.5" ></text>
</g>
<g >
<title>lappend (2,547,864,881 samples, 0.03%)</title><rect x="818.5" y="453" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="821.47" y="463.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="325" width="2.4" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="103.12" y="335.5" ></text>
</g>
<g >
<title>futex_wait_setup (8,716,249,238 samples, 0.09%)</title><rect x="489.0" y="325" width="1.1" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="491.98" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,353,192,251 samples, 0.01%)</title><rect x="216.5" y="533" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="219.48" y="543.5" ></text>
</g>
<g >
<title>ExecInitUpdateProjection (76,821,844,277 samples, 0.81%)</title><rect x="271.9" y="437" width="9.6" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="274.94" y="447.5" ></text>
</g>
<g >
<title>verify_compact_attribute (2,366,267,701 samples, 0.02%)</title><rect x="360.8" y="325" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="363.82" y="335.5" ></text>
</g>
<g >
<title>set_base_rel_pathlists (345,329,383,218 samples, 3.64%)</title><rect x="983.0" y="453" width="42.9" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="986.02" y="463.5" >set_..</text>
</g>
<g >
<title>order_qual_clauses (1,128,706,782 samples, 0.01%)</title><rect x="892.2" y="389" width="0.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="895.23" y="399.5" ></text>
</g>
<g >
<title>_bt_unlockbuf (2,992,733,029 samples, 0.03%)</title><rect x="341.5" y="229" width="0.4" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="344.55" y="239.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (1,083,093,696 samples, 0.01%)</title><rect x="125.8" y="181" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="128.78" y="191.5" ></text>
</g>
<g >
<title>makeRangeVar (811,277,105 samples, 0.01%)</title><rect x="1159.6" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1162.58" y="767.5" ></text>
</g>
<g >
<title>__calc_delta.constprop.0 (1,191,297,511 samples, 0.01%)</title><rect x="171.1" y="261" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="174.11" y="271.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,286,350,683 samples, 0.07%)</title><rect x="1051.1" y="453" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1054.10" y="463.5" ></text>
</g>
<g >
<title>RelationClose (1,486,116,150 samples, 0.02%)</title><rect x="940.0" y="389" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="943.03" y="399.5" ></text>
</g>
<g >
<title>ProcessQuery (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="613" width="2.6" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="87.32" y="623.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64 (932,136,205 samples, 0.01%)</title><rect x="1168.9" y="757" width="0.1" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="1171.86" y="767.5" ></text>
</g>
<g >
<title>ExecScanFetch (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="373" width="1.9" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="129.31" y="383.5" ></text>
</g>
<g >
<title>ReindexIsProcessingIndex (827,454,620 samples, 0.01%)</title><rect x="239.9" y="437" width="0.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="242.88" y="447.5" ></text>
</g>
<g >
<title>AllocSetFree (2,217,002,790 samples, 0.02%)</title><rect x="515.3" y="485" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="518.28" y="495.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,810,501,057 samples, 0.02%)</title><rect x="1021.6" y="229" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1024.58" y="239.5" ></text>
</g>
<g >
<title>_bt_binsrch (57,096,136,799 samples, 0.60%)</title><rect x="316.2" y="261" width="7.1" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="319.22" y="271.5" ></text>
</g>
<g >
<title>__strncmp_avx2 (971,486,633 samples, 0.01%)</title><rect x="834.5" y="437" width="0.1" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="837.48" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (805,552,407 samples, 0.01%)</title><rect x="941.6" y="309" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="944.56" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,057,189,916 samples, 0.01%)</title><rect x="399.0" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="402.02" y="351.5" ></text>
</g>
<g >
<title>fix_indexqual_references (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="421" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="128.92" y="431.5" ></text>
</g>
<g >
<title>ExecInitFunc (14,925,664,177 samples, 0.16%)</title><rect x="427.2" y="341" width="1.9" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="430.22" y="351.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (1,878,268,118 samples, 0.02%)</title><rect x="1046.0" y="405" width="0.3" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1049.04" y="415.5" ></text>
</g>
<g >
<title>intel_thermal_interrupt (1,761,123,484 samples, 0.02%)</title><rect x="758.7" y="485" width="0.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="761.68" y="495.5" ></text>
</g>
<g >
<title>hash_create (42,150,139,439 samples, 0.44%)</title><rect x="1058.8" y="469" width="5.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="1061.78" y="479.5" ></text>
</g>
<g >
<title>palloc (1,011,376,216 samples, 0.01%)</title><rect x="1164.3" y="741" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1167.32" y="751.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (1,644,050,888 samples, 0.02%)</title><rect x="92.2" y="757" width="0.2" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="95.24" y="767.5" ></text>
</g>
<g >
<title>fix_expr_common (1,326,885,538 samples, 0.01%)</title><rect x="902.1" y="357" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="905.11" y="367.5" ></text>
</g>
<g >
<title>bms_copy (2,017,527,390 samples, 0.02%)</title><rect x="993.9" y="341" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="996.86" y="351.5" ></text>
</g>
<g >
<title>AtEOXact_SMgr (1,151,380,634 samples, 0.01%)</title><rect x="32.5" y="757" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="35.52" y="767.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (2,408,943,042 samples, 0.03%)</title><rect x="840.1" y="293" width="0.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="843.12" y="303.5" ></text>
</g>
<g >
<title>standard_planner (2,204,184,953 samples, 0.02%)</title><rect x="128.2" y="549" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="131.20" y="559.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,714,123,723 samples, 0.04%)</title><rect x="917.2" y="357" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="920.17" y="367.5" ></text>
</g>
<g >
<title>__futex_wait (1,015,800,283 samples, 0.01%)</title><rect x="299.0" y="101" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="302.04" y="111.5" ></text>
</g>
<g >
<title>seg_alloc (841,749,556 samples, 0.01%)</title><rect x="1177.9" y="757" width="0.1" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="1180.89" y="767.5" ></text>
</g>
<g >
<title>ExecCheckPermissions (20,527,655,373 samples, 0.22%)</title><rect x="414.2" y="485" width="2.5" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="417.16" y="495.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,471,798,180 samples, 0.02%)</title><rect x="301.7" y="85" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="304.73" y="95.5" ></text>
</g>
<g >
<title>PageGetItemId (837,132,950 samples, 0.01%)</title><rect x="81.6" y="741" width="0.1" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="84.63" y="751.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,116,897,348 samples, 0.01%)</title><rect x="990.2" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="993.17" y="351.5" ></text>
</g>
<g >
<title>__x64_sys_recvfrom (60,741,561,124 samples, 0.64%)</title><rect x="179.3" y="453" width="7.5" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="182.28" y="463.5" ></text>
</g>
<g >
<title>has_unique_index (1,133,702,171 samples, 0.01%)</title><rect x="1035.1" y="229" width="0.1" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="1038.11" y="239.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,144,634,716 samples, 0.01%)</title><rect x="454.3" y="341" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="457.28" y="351.5" ></text>
</g>
<g >
<title>AllocSetFree (4,449,814,132 samples, 0.05%)</title><rect x="359.2" y="341" width="0.5" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="362.18" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,393,454,475 samples, 0.01%)</title><rect x="189.1" y="517" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="192.14" y="527.5" ></text>
</g>
<g >
<title>perf_ctx_enable (1,577,770,976 samples, 0.02%)</title><rect x="484.8" y="245" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="487.80" y="255.5" ></text>
</g>
<g >
<title>__wake_up_common (2,698,611,192 samples, 0.03%)</title><rect x="183.7" y="277" width="0.4" height="15.0" fill="rgb(248,197,47)" rx="2" ry="2" />
<text  x="186.73" y="287.5" ></text>
</g>
<g >
<title>BufTableLookup (1,799,509,755 samples, 0.02%)</title><rect x="408.2" y="277" width="0.3" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="411.23" y="287.5" ></text>
</g>
<g >
<title>restriction_selectivity (68,560,892,689 samples, 0.72%)</title><rect x="1028.8" y="341" width="8.5" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1031.81" y="351.5" ></text>
</g>
<g >
<title>get_typlen (6,391,170,952 samples, 0.07%)</title><rect x="1041.3" y="357" width="0.8" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="1044.31" y="367.5" ></text>
</g>
<g >
<title>TransactionIdIsCurrentTransactionId (1,521,621,873 samples, 0.02%)</title><rect x="309.9" y="213" width="0.2" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="312.87" y="223.5" ></text>
</g>
<g >
<title>is_parallel_safe (20,058,796,641 samples, 0.21%)</title><rect x="915.8" y="485" width="2.5" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="918.78" y="495.5" ></text>
</g>
<g >
<title>IsToastClass (1,362,835,590 samples, 0.01%)</title><rect x="415.0" y="405" width="0.2" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="418.02" y="415.5" ></text>
</g>
<g >
<title>create_seqscan_path (1,066,775,720 samples, 0.01%)</title><rect x="1131.2" y="757" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1134.23" y="767.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (986,854,070 samples, 0.01%)</title><rect x="102.2" y="165" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="105.25" y="175.5" ></text>
</g>
<g >
<title>lappend (2,884,334,492 samples, 0.03%)</title><rect x="870.4" y="533" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="873.40" y="543.5" ></text>
</g>
<g >
<title>make_indexscan (977,505,634 samples, 0.01%)</title><rect x="1160.0" y="757" width="0.2" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="1163.05" y="767.5" ></text>
</g>
<g >
<title>dequeue_task_fair (45,831,540,527 samples, 0.48%)</title><rect x="168.8" y="325" width="5.7" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="171.82" y="335.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (4,047,155,151 samples, 0.04%)</title><rect x="282.0" y="325" width="0.6" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="285.05" y="335.5" ></text>
</g>
<g >
<title>set_sentinel (1,753,020,836 samples, 0.02%)</title><rect x="12.6" y="741" width="0.2" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="15.59" y="751.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,084,770,447 samples, 0.01%)</title><rect x="960.5" y="293" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="963.51" y="303.5" ></text>
</g>
<g >
<title>__libc_start_call_main (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="741" width="2.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="129.31" y="751.5" ></text>
</g>
<g >
<title>hash_bytes (1,562,708,162 samples, 0.02%)</title><rect x="214.7" y="517" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="217.72" y="527.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (4,875,783,945 samples, 0.05%)</title><rect x="258.9" y="421" width="0.6" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="261.94" y="431.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,722,910,336 samples, 0.04%)</title><rect x="397.3" y="325" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="400.28" y="335.5" ></text>
</g>
<g >
<title>ep_send_events (25,587,489,205 samples, 0.27%)</title><rect x="157.9" y="389" width="3.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="160.93" y="399.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (1,675,912,941 samples, 0.02%)</title><rect x="1065.4" y="389" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1068.36" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerReleaseInternal (116,420,661,477 samples, 1.23%)</title><rect x="515.6" y="501" width="14.5" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="518.62" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (983,235,894 samples, 0.01%)</title><rect x="1056.6" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1059.61" y="367.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (2,463,738,124 samples, 0.03%)</title><rect x="84.7" y="325" width="0.3" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="87.68" y="335.5" ></text>
</g>
<g >
<title>PostgresMain (7,653,638,650,116 samples, 80.57%)</title><rect x="132.0" y="613" width="950.7" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="135.01" y="623.5" >PostgresMain</text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (1,743,810,358 samples, 0.02%)</title><rect x="1035.3" y="213" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1038.28" y="223.5" ></text>
</g>
<g >
<title>PushActiveSnapshot (9,478,953,571 samples, 0.10%)</title><rect x="463.5" y="581" width="1.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="466.47" y="591.5" ></text>
</g>
<g >
<title>pg_class_aclcheck (7,377,515,640 samples, 0.08%)</title><rect x="1033.5" y="197" width="0.9" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="1036.48" y="207.5" ></text>
</g>
<g >
<title>palloc (1,365,239,866 samples, 0.01%)</title><rect x="1020.1" y="277" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1023.08" y="287.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,002,957,957 samples, 0.01%)</title><rect x="1045.6" y="373" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1048.64" y="383.5" ></text>
</g>
<g >
<title>XactLockTableInsert (49,502,854,890 samples, 0.52%)</title><rect x="368.6" y="325" width="6.1" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="371.59" y="335.5" ></text>
</g>
<g >
<title>fdget_pos (3,871,489,511 samples, 0.04%)</title><rect x="937.4" y="165" width="0.5" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="940.39" y="175.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,787,203,609 samples, 0.02%)</title><rect x="406.4" y="357" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="409.40" y="367.5" ></text>
</g>
<g >
<title>list_make2_impl (1,631,438,753 samples, 0.02%)</title><rect x="837.6" y="373" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="840.59" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,054,556,976 samples, 0.01%)</title><rect x="878.8" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="881.83" y="447.5" ></text>
</g>
<g >
<title>dequeue_entities (43,870,986,959 samples, 0.46%)</title><rect x="169.1" y="309" width="5.4" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="172.05" y="319.5" ></text>
</g>
<g >
<title>select_task_rq_fair (2,337,926,416 samples, 0.02%)</title><rect x="494.3" y="277" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="497.31" y="287.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_out (9,300,082,515 samples, 0.10%)</title><rect x="165.4" y="325" width="1.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="168.36" y="335.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (3,855,898,749 samples, 0.04%)</title><rect x="1051.4" y="405" width="0.5" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1054.39" y="415.5" ></text>
</g>
<g >
<title>mutex_lock (2,601,659,373 samples, 0.03%)</title><rect x="160.6" y="373" width="0.4" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="163.65" y="383.5" ></text>
</g>
<g >
<title>index_open (29,205,630,568 samples, 0.31%)</title><rect x="940.2" y="405" width="3.6" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="943.22" y="415.5" ></text>
</g>
<g >
<title>_bt_compare (36,655,076,242 samples, 0.39%)</title><rect x="331.7" y="229" width="4.6" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="334.71" y="239.5" ></text>
</g>
<g >
<title>update_entity_lag (1,660,208,645 samples, 0.02%)</title><rect x="487.9" y="213" width="0.2" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="490.86" y="223.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="533" width="1.9" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="129.31" y="543.5" ></text>
</g>
<g >
<title>evaluate_function (985,939,163 samples, 0.01%)</title><rect x="1055.9" y="421" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="1058.93" y="431.5" ></text>
</g>
<g >
<title>CreatePortal (33,582,534,126 samples, 0.35%)</title><rect x="213.3" y="581" width="4.2" height="15.0" fill="rgb(219,66,16)" rx="2" ry="2" />
<text  x="216.35" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,010,380,461 samples, 0.01%)</title><rect x="856.7" y="309" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="859.73" y="319.5" ></text>
</g>
<g >
<title>LockBuffer (6,479,223,268 samples, 0.07%)</title><rect x="406.0" y="389" width="0.8" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="409.04" y="399.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (9,969,189,367 samples, 0.10%)</title><rect x="134.8" y="517" width="1.2" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="137.76" y="527.5" ></text>
</g>
<g >
<title>standard_qp_callback (5,348,051,027 samples, 0.06%)</title><rect x="1044.1" y="469" width="0.7" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="1047.11" y="479.5" ></text>
</g>
<g >
<title>_int_malloc (1,968,617,117 samples, 0.02%)</title><rect x="913.2" y="389" width="0.2" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="916.15" y="399.5" ></text>
</g>
<g >
<title>slot_getsomeattrs (3,704,801,375 samples, 0.04%)</title><rect x="271.4" y="341" width="0.5" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="274.44" y="351.5" ></text>
</g>
<g >
<title>btcanreturn (1,588,004,489 samples, 0.02%)</title><rect x="1123.4" y="757" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1126.38" y="767.5" ></text>
</g>
<g >
<title>choose_bitmap_and (2,678,516,885 samples, 0.03%)</title><rect x="988.2" y="389" width="0.4" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="991.23" y="399.5" ></text>
</g>
<g >
<title>remove_useless_self_joins (1,530,937,520 samples, 0.02%)</title><rect x="1043.2" y="469" width="0.1" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1046.16" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,293,785,533 samples, 0.01%)</title><rect x="394.9" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="397.87" y="303.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (846,552,048 samples, 0.01%)</title><rect x="49.2" y="757" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="52.17" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,411,208,921 samples, 0.01%)</title><rect x="985.0" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="988.05" y="335.5" ></text>
</g>
<g >
<title>get_leftop (1,344,107,161 samples, 0.01%)</title><rect x="970.7" y="389" width="0.2" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="973.73" y="399.5" ></text>
</g>
<g >
<title>newNode (3,393,301,524 samples, 0.04%)</title><rect x="1116.1" y="725" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1119.11" y="735.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,337,524,758 samples, 0.01%)</title><rect x="1167.7" y="757" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="1170.66" y="767.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (4,082,483,712 samples, 0.04%)</title><rect x="795.6" y="517" width="0.5" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="798.62" y="527.5" ></text>
</g>
<g >
<title>genericcostestimate (22,951,476,061 samples, 0.24%)</title><rect x="1010.5" y="309" width="2.9" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1013.51" y="319.5" ></text>
</g>
<g >
<title>resetStringInfo (978,003,555 samples, 0.01%)</title><rect x="1176.8" y="757" width="0.1" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1179.80" y="767.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,579,439,149 samples, 0.02%)</title><rect x="847.1" y="389" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="850.07" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,961,376,712 samples, 0.06%)</title><rect x="902.5" y="373" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="905.51" y="383.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="517" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1161.90" y="527.5" ></text>
</g>
<g >
<title>__futex_wait (1,035,994,061 samples, 0.01%)</title><rect x="388.9" y="149" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="391.93" y="159.5" ></text>
</g>
<g >
<title>hash_initial_lookup (909,180,096 samples, 0.01%)</title><rect x="454.2" y="341" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="457.15" y="351.5" ></text>
</g>
<g >
<title>ExecScanFetch (415,206,824,805 samples, 4.37%)</title><rect x="291.9" y="357" width="51.6" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="294.89" y="367.5" >ExecS..</text>
</g>
<g >
<title>SearchSysCache3 (6,593,556,860 samples, 0.07%)</title><rect x="1040.5" y="357" width="0.8" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1043.47" y="367.5" ></text>
</g>
<g >
<title>palloc0 (1,854,633,212 samples, 0.02%)</title><rect x="841.4" y="325" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="844.40" y="335.5" ></text>
</g>
<g >
<title>futex_do_wait (1,025,875,864 samples, 0.01%)</title><rect x="1148.5" y="469" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="1151.51" y="479.5" ></text>
</g>
<g >
<title>ExecEvalStepOp (16,592,743,976 samples, 0.17%)</title><rect x="286.7" y="277" width="2.0" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="289.68" y="287.5" ></text>
</g>
<g >
<title>IsTidRangeClause (2,070,874,338 samples, 0.02%)</title><rect x="1025.6" y="373" width="0.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1028.56" y="383.5" ></text>
</g>
<g >
<title>unix_stream_sendmsg (117,116,291,102 samples, 1.23%)</title><rect x="194.3" y="421" width="14.6" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="197.35" y="431.5" ></text>
</g>
<g >
<title>LWLockRelease (4,993,411,260 samples, 0.05%)</title><rect x="375.6" y="341" width="0.7" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="378.64" y="351.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,374,229,282 samples, 0.02%)</title><rect x="282.2" y="293" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="285.25" y="303.5" ></text>
</g>
<g >
<title>palloc0 (7,337,570,389 samples, 0.08%)</title><rect x="894.6" y="501" width="0.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="897.59" y="511.5" ></text>
</g>
<g >
<title>create_modifytable_plan (80,036,082,065 samples, 0.84%)</title><rect x="884.3" y="485" width="9.9" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="887.29" y="495.5" ></text>
</g>
<g >
<title>add_base_clause_to_rel (9,023,487,602 samples, 0.09%)</title><rect x="980.0" y="421" width="1.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="983.04" y="431.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (812,720,091 samples, 0.01%)</title><rect x="1119.7" y="677" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1122.67" y="687.5" ></text>
</g>
<g >
<title>verify_compact_attribute (9,549,141,718 samples, 0.10%)</title><rect x="322.0" y="197" width="1.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="324.99" y="207.5" ></text>
</g>
<g >
<title>ExecShutdownNode_walker (7,143,585,460 samples, 0.08%)</title><rect x="410.2" y="469" width="0.9" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="413.21" y="479.5" ></text>
</g>
<g >
<title>table_open (21,436,001,691 samples, 0.23%)</title><rect x="451.8" y="421" width="2.6" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="454.78" y="431.5" ></text>
</g>
<g >
<title>namestrcpy (4,316,897,795 samples, 0.05%)</title><rect x="444.3" y="357" width="0.5" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="447.30" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,160,298,364 samples, 0.01%)</title><rect x="944.1" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="947.08" y="367.5" ></text>
</g>
<g >
<title>ExecIndexBuildScanKeys (29,718,381,834 samples, 0.31%)</title><rect x="433.5" y="421" width="3.7" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="436.49" y="431.5" ></text>
</g>
<g >
<title>LockTagHashCode (3,699,329,414 samples, 0.04%)</title><rect x="823.0" y="373" width="0.5" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="826.02" y="383.5" ></text>
</g>
<g >
<title>tag_hash (4,330,827,561 samples, 0.05%)</title><rect x="868.7" y="437" width="0.6" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="871.74" y="447.5" ></text>
</g>
<g >
<title>relation_close (1,484,932,184 samples, 0.02%)</title><rect x="862.2" y="501" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="865.19" y="511.5" ></text>
</g>
<g >
<title>create_index_path (160,516,776,406 samples, 1.69%)</title><rect x="997.0" y="357" width="19.9" height="15.0" fill="rgb(245,186,44)" rx="2" ry="2" />
<text  x="999.96" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,035,413,988 samples, 0.01%)</title><rect x="406.5" y="341" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="409.47" y="351.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (34,696,550,245 samples, 0.37%)</title><rect x="243.8" y="341" width="4.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="246.83" y="351.5" ></text>
</g>
<g >
<title>generate_base_implied_equalities_const (12,618,590,042 samples, 0.13%)</title><rect x="979.9" y="453" width="1.6" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="982.92" y="463.5" ></text>
</g>
<g >
<title>RewriteQuery (42,240,632,924 samples, 0.44%)</title><rect x="859.1" y="533" width="5.3" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="862.13" y="543.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (959,011,903 samples, 0.01%)</title><rect x="917.4" y="325" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="920.38" y="335.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="677" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1188.13" y="687.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (6,801,267,142 samples, 0.07%)</title><rect x="523.8" y="421" width="0.9" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="526.82" y="431.5" ></text>
</g>
<g >
<title>ExecClearTuple (1,582,968,791 samples, 0.02%)</title><rect x="285.5" y="341" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="288.47" y="351.5" ></text>
</g>
<g >
<title>FunctionCall4Coll (54,540,398,963 samples, 0.57%)</title><rect x="1029.3" y="309" width="6.8" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1032.34" y="319.5" ></text>
</g>
<g >
<title>LWLockReleaseClearVar (2,567,541,611 samples, 0.03%)</title><rect x="513.5" y="437" width="0.3" height="15.0" fill="rgb(253,221,52)" rx="2" ry="2" />
<text  x="516.52" y="447.5" ></text>
</g>
<g >
<title>AllocSetFree (3,601,005,736 samples, 0.04%)</title><rect x="977.6" y="405" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="980.60" y="415.5" ></text>
</g>
<g >
<title>simplify_function (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="469" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="129.16" y="479.5" ></text>
</g>
<g >
<title>switch_fpu_return (1,273,977,984 samples, 0.01%)</title><rect x="490.1" y="373" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="493.10" y="383.5" ></text>
</g>
<g >
<title>int4hashfast (894,362,673 samples, 0.01%)</title><rect x="435.5" y="325" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="438.54" y="335.5" ></text>
</g>
<g >
<title>pg_plan_query (3,069,363,779 samples, 0.03%)</title><rect x="125.9" y="597" width="0.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="128.92" y="607.5" ></text>
</g>
<g >
<title>preprocess_relation_rtes (88,287,630,403 samples, 0.93%)</title><rect x="1057.3" y="501" width="11.0" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1060.29" y="511.5" ></text>
</g>
<g >
<title>cfree@GLIBC_2.2.5 (1,637,083,888 samples, 0.02%)</title><rect x="264.3" y="405" width="0.2" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="267.28" y="415.5" ></text>
</g>
<g >
<title>create_indexscan_plan (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="469" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="89.91" y="479.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (2,369,054,330 samples, 0.02%)</title><rect x="918.0" y="389" width="0.3" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="920.96" y="399.5" ></text>
</g>
<g >
<title>create_indexscan_plan (1,085,985,899 samples, 0.01%)</title><rect x="1130.3" y="757" width="0.1" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="1133.31" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,082,034,745 samples, 0.01%)</title><rect x="812.8" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="815.80" y="463.5" ></text>
</g>
<g >
<title>RelationGetIndexScan (1,132,391,942 samples, 0.01%)</title><rect x="90.0" y="757" width="0.1" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="92.96" y="767.5" ></text>
</g>
<g >
<title>transformFromClause (1,675,883,383 samples, 0.02%)</title><rect x="833.5" y="485" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="836.51" y="495.5" ></text>
</g>
<g >
<title>WALInsertLockRelease (2,867,817,813 samples, 0.03%)</title><rect x="513.5" y="453" width="0.3" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="516.48" y="463.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (3,305,571,574 samples, 0.03%)</title><rect x="178.7" y="485" width="0.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="181.73" y="495.5" ></text>
</g>
<g >
<title>enqueue_task (816,384,101 samples, 0.01%)</title><rect x="494.2" y="293" width="0.1" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="497.17" y="303.5" ></text>
</g>
<g >
<title>palloc0 (2,245,069,556 samples, 0.02%)</title><rect x="990.0" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="993.04" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache1 (6,450,350,345 samples, 0.07%)</title><rect x="848.5" y="389" width="0.8" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="851.45" y="399.5" ></text>
</g>
<g >
<title>hash_bytes (2,489,309,203 samples, 0.03%)</title><rect x="814.5" y="389" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="817.53" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,553,450,341 samples, 0.02%)</title><rect x="365.7" y="261" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="368.73" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (904,500,545 samples, 0.01%)</title><rect x="1077.3" y="469" width="0.2" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="1080.34" y="479.5" ></text>
</g>
<g >
<title>XLogWrite (94,082,570,899 samples, 0.99%)</title><rect x="498.6" y="485" width="11.7" height="15.0" fill="rgb(243,175,42)" rx="2" ry="2" />
<text  x="501.57" y="495.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,492,491,301 samples, 0.02%)</title><rect x="388.9" y="245" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="391.89" y="255.5" ></text>
</g>
<g >
<title>newNode (2,208,520,068 samples, 0.02%)</title><rect x="1117.8" y="725" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1120.76" y="735.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (3,414,164,431 samples, 0.04%)</title><rect x="757.2" y="501" width="0.4" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="760.19" y="511.5" ></text>
</g>
<g >
<title>futex_wait_setup (2,817,368,402 samples, 0.03%)</title><rect x="366.8" y="149" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="369.82" y="159.5" ></text>
</g>
<g >
<title>__memcg_slab_post_alloc_hook (8,467,279,488 samples, 0.09%)</title><rect x="199.3" y="341" width="1.0" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="202.29" y="351.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,133,976,642 samples, 0.01%)</title><rect x="475.6" y="437" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="478.58" y="447.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (909,529,973 samples, 0.01%)</title><rect x="293.9" y="229" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="296.93" y="239.5" ></text>
</g>
<g >
<title>rw_verify_area (4,465,315,219 samples, 0.05%)</title><rect x="500.9" y="389" width="0.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="503.94" y="399.5" ></text>
</g>
<g >
<title>PortalRun (1,873,249,509,240 samples, 19.72%)</title><rect x="229.7" y="581" width="232.7" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="232.72" y="591.5" >PortalRun</text>
</g>
<g >
<title>SysCacheGetAttrNotNull (49,880,510,044 samples, 0.53%)</title><rect x="1000.8" y="277" width="6.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="1003.81" y="287.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,212,663,947 samples, 0.02%)</title><rect x="284.4" y="261" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="287.39" y="271.5" ></text>
</g>
<g >
<title>PreCommit_on_commit_actions (1,160,116,538 samples, 0.01%)</title><rect x="474.1" y="517" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="477.08" y="527.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,330,202,259 samples, 0.02%)</title><rect x="454.0" y="357" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="456.97" y="367.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,843,610,984 samples, 0.02%)</title><rect x="1148.5" y="613" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="1151.49" y="623.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (4,436,003,795 samples, 0.05%)</title><rect x="100.5" y="165" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="103.54" y="175.5" ></text>
</g>
<g >
<title>ExtractReplicaIdentity (2,879,996,474 samples, 0.03%)</title><rect x="47.1" y="757" width="0.4" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="50.10" y="767.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,008,915,234 samples, 0.01%)</title><rect x="357.8" y="341" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="360.82" y="351.5" ></text>
</g>
<g >
<title>new_list (2,047,938,923 samples, 0.02%)</title><rect x="874.3" y="549" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="877.29" y="559.5" ></text>
</g>
<g >
<title>HeapTupleHeaderXminFrozen (3,796,099,653 samples, 0.04%)</title><rect x="304.8" y="229" width="0.5" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="307.84" y="239.5" ></text>
</g>
<g >
<title>AllocSetFree (1,216,757,345 samples, 0.01%)</title><rect x="992.5" y="293" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="995.52" y="303.5" ></text>
</g>
<g >
<title>bms_overlap (894,952,489 samples, 0.01%)</title><rect x="958.5" y="405" width="0.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="961.49" y="415.5" ></text>
</g>
<g >
<title>estimate_expression_value (3,250,942,518 samples, 0.03%)</title><rect x="1030.1" y="245" width="0.4" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="1033.11" y="255.5" ></text>
</g>
<g >
<title>futex_wait (1,078,741,774 samples, 0.01%)</title><rect x="388.9" y="165" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="391.92" y="175.5" ></text>
</g>
<g >
<title>AllocSetAlloc (827,016,935 samples, 0.01%)</title><rect x="897.7" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="900.68" y="431.5" ></text>
</g>
<g >
<title>do_futex (2,527,180,487 samples, 0.03%)</title><rect x="389.3" y="149" width="0.4" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="392.34" y="159.5" ></text>
</g>
<g >
<title>PageGetHeapFreeSpace (5,559,912,318 samples, 0.06%)</title><rect x="377.9" y="357" width="0.7" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="380.92" y="367.5" ></text>
</g>
<g >
<title>planner (1,578,915,045,899 samples, 16.62%)</title><rect x="875.0" y="549" width="196.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="877.95" y="559.5" >planner</text>
</g>
<g >
<title>ReadBuffer (11,333,614,982 samples, 0.12%)</title><rect x="85.0" y="277" width="1.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="87.98" y="287.5" ></text>
</g>
<g >
<title>__sys_recvfrom (59,826,007,822 samples, 0.63%)</title><rect x="179.4" y="437" width="7.4" height="15.0" fill="rgb(247,197,47)" rx="2" ry="2" />
<text  x="182.40" y="447.5" ></text>
</g>
<g >
<title>get_tablespace (853,899,157 samples, 0.01%)</title><rect x="1141.9" y="757" width="0.1" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1144.87" y="767.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (5,616,562,801 samples, 0.06%)</title><rect x="453.7" y="389" width="0.7" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="456.72" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (848,516,569 samples, 0.01%)</title><rect x="265.5" y="453" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="268.54" y="463.5" ></text>
</g>
<g >
<title>GlobalVisUpdate (2,530,131,093 samples, 0.03%)</title><rect x="305.8" y="197" width="0.3" height="15.0" fill="rgb(209,18,4)" rx="2" ry="2" />
<text  x="308.76" y="207.5" ></text>
</g>
<g >
<title>LockBuffer (5,253,586,844 samples, 0.06%)</title><rect x="353.1" y="373" width="0.6" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="356.09" y="383.5" ></text>
</g>
<g >
<title>make_modifytable (12,684,056,409 samples, 0.13%)</title><rect x="892.7" y="469" width="1.5" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="895.65" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,116,994,224 samples, 0.02%)</title><rect x="1043.8" y="437" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1046.84" y="447.5" ></text>
</g>
<g >
<title>makeRangeVar (4,843,466,003 samples, 0.05%)</title><rect x="1117.1" y="741" width="0.6" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1120.11" y="751.5" ></text>
</g>
<g >
<title>palloc0 (2,611,463,699 samples, 0.03%)</title><rect x="446.7" y="373" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="449.71" y="383.5" ></text>
</g>
<g >
<title>LockHeldByMe (11,124,484,184 samples, 0.12%)</title><rect x="923.2" y="421" width="1.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="926.18" y="431.5" ></text>
</g>
<g >
<title>heapam_index_fetch_reset (1,174,990,593 samples, 0.01%)</title><rect x="343.3" y="293" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="346.28" y="303.5" ></text>
</g>
<g >
<title>inline_function (1,946,524,090 samples, 0.02%)</title><rect x="105.8" y="453" width="0.3" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="108.83" y="463.5" ></text>
</g>
<g >
<title>lappend (908,475,268 samples, 0.01%)</title><rect x="976.4" y="437" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="979.45" y="447.5" ></text>
</g>
<g >
<title>bms_add_members (1,182,789,192 samples, 0.01%)</title><rect x="1120.8" y="757" width="0.2" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="1123.80" y="767.5" ></text>
</g>
<g >
<title>ShowTransactionState (927,174,439 samples, 0.01%)</title><rect x="106.2" y="757" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="109.22" y="767.5" ></text>
</g>
<g >
<title>palloc0 (3,235,404,327 samples, 0.03%)</title><rect x="971.7" y="373" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="974.68" y="383.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,279,883,493 samples, 0.01%)</title><rect x="328.4" y="229" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="331.45" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="89.91" y="303.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (27,437,285,221 samples, 0.29%)</title><rect x="102.7" y="485" width="3.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="105.66" y="495.5" ></text>
</g>
<g >
<title>__libc_pwrite (63,494,740,405 samples, 0.67%)</title><rect x="499.2" y="469" width="7.9" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="502.21" y="479.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,036,178,711 samples, 0.01%)</title><rect x="304.3" y="229" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="307.31" y="239.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (4,488,117,583 samples, 0.05%)</title><rect x="85.2" y="197" width="0.6" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="88.24" y="207.5" ></text>
</g>
<g >
<title>ProcArrayEndTransactionInternal (4,675,617,793 samples, 0.05%)</title><rect x="474.9" y="501" width="0.6" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="477.92" y="511.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (945,435,215 samples, 0.01%)</title><rect x="523.0" y="421" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="526.02" y="431.5" ></text>
</g>
<g >
<title>fix_indexqual_operand (5,548,023,151 samples, 0.06%)</title><rect x="888.7" y="357" width="0.7" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="891.66" y="367.5" ></text>
</g>
<g >
<title>XLogInsert (859,926,604 samples, 0.01%)</title><rect x="1146.6" y="725" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="1149.60" y="735.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,227,094,015 samples, 0.01%)</title><rect x="369.9" y="261" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="372.93" y="271.5" ></text>
</g>
<g >
<title>__futex_wait (53,800,205,016 samples, 0.57%)</title><rect x="483.4" y="341" width="6.7" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="486.38" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (919,153,485 samples, 0.01%)</title><rect x="1053.5" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1056.46" y="399.5" ></text>
</g>
<g >
<title>query_planner (1,170,853,582 samples, 0.01%)</title><rect x="1175.2" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1178.15" y="767.5" ></text>
</g>
<g >
<title>reduce_unique_semijoins (867,123,736 samples, 0.01%)</title><rect x="1042.8" y="469" width="0.1" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1045.78" y="479.5" ></text>
</g>
<g >
<title>hash_bytes (2,643,448,698 samples, 0.03%)</title><rect x="924.2" y="373" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="927.24" y="383.5" ></text>
</g>
<g >
<title>LockReleaseAll (107,097,264,098 samples, 1.13%)</title><rect x="516.3" y="469" width="13.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="519.27" y="479.5" ></text>
</g>
<g >
<title>MemoryContextTraverseNext (1,843,066,061 samples, 0.02%)</title><rect x="78.9" y="741" width="0.3" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="81.93" y="751.5" ></text>
</g>
<g >
<title>ExecIndexScan (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="485" width="6.8" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="98.68" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,069,885,192 samples, 0.01%)</title><rect x="896.8" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="899.81" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,017,457,778 samples, 0.01%)</title><rect x="105.6" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="108.63" y="399.5" ></text>
</g>
<g >
<title>BufferGetBlock (2,978,647,672 samples, 0.03%)</title><rect x="34.7" y="757" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="37.73" y="767.5" ></text>
</g>
<g >
<title>generic_write_checks (1,825,780,587 samples, 0.02%)</title><rect x="506.5" y="373" width="0.3" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="509.54" y="383.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (862,923,144 samples, 0.01%)</title><rect x="50.0" y="741" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="52.97" y="751.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,468,033,076 samples, 0.02%)</title><rect x="275.7" y="405" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="278.74" y="415.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (907,884,159 samples, 0.01%)</title><rect x="408.1" y="213" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="411.11" y="223.5" ></text>
</g>
<g >
<title>standard_qp_callback (1,177,396,937 samples, 0.01%)</title><rect x="1184.8" y="757" width="0.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="1187.77" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberTupleDesc (1,819,189,342 samples, 0.02%)</title><rect x="446.5" y="357" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="449.48" y="367.5" ></text>
</g>
<g >
<title>SearchSysCacheList (8,492,432,289 samples, 0.09%)</title><rect x="961.7" y="373" width="1.0" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="964.68" y="383.5" ></text>
</g>
<g >
<title>initStringInfo (2,116,836,907 samples, 0.02%)</title><rect x="189.1" y="565" width="0.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="192.08" y="575.5" ></text>
</g>
<g >
<title>lcons (998,431,369 samples, 0.01%)</title><rect x="347.2" y="341" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="350.21" y="351.5" ></text>
</g>
<g >
<title>AllocSetFreeIndex (964,055,551 samples, 0.01%)</title><rect x="11.4" y="741" width="0.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="14.44" y="751.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,161,028,722 samples, 0.01%)</title><rect x="1038.0" y="309" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1041.02" y="319.5" ></text>
</g>
<g >
<title>make_fn_arguments (815,066,818 samples, 0.01%)</title><rect x="1159.9" y="757" width="0.1" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1162.95" y="767.5" ></text>
</g>
<g >
<title>simplify_function (19,453,139,221 samples, 0.20%)</title><rect x="103.7" y="469" width="2.4" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="106.66" y="479.5" ></text>
</g>
<g >
<title>relation_open (1,304,915,802 samples, 0.01%)</title><rect x="1176.0" y="757" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1179.03" y="767.5" ></text>
</g>
<g >
<title>update_min_vruntime (1,034,857,151 samples, 0.01%)</title><rect x="171.3" y="261" width="0.1" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="174.32" y="271.5" ></text>
</g>
<g >
<title>new_list (2,117,573,101 samples, 0.02%)</title><rect x="963.0" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="966.00" y="367.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (1,233,024,612 samples, 0.01%)</title><rect x="869.4" y="469" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="872.45" y="479.5" ></text>
</g>
<g >
<title>check_log_statement (1,015,903,299 samples, 0.01%)</title><rect x="1126.6" y="757" width="0.1" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="1129.61" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (1,137,083,039 samples, 0.01%)</title><rect x="1167.3" y="741" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1170.32" y="751.5" ></text>
</g>
<g >
<title>palloc (2,639,300,667 samples, 0.03%)</title><rect x="891.1" y="341" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="894.12" y="351.5" ></text>
</g>
<g >
<title>transformTargetEntry (60,908,257,436 samples, 0.64%)</title><rect x="835.5" y="453" width="7.6" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="838.52" y="463.5" ></text>
</g>
<g >
<title>palloc (1,519,445,824 samples, 0.02%)</title><rect x="957.2" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="960.21" y="431.5" ></text>
</g>
<g >
<title>palloc (1,390,185,132 samples, 0.01%)</title><rect x="911.0" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="913.96" y="431.5" ></text>
</g>
<g >
<title>palloc0 (1,594,658,706 samples, 0.02%)</title><rect x="897.0" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="900.01" y="447.5" ></text>
</g>
<g >
<title>verify_compact_attribute (4,088,633,863 samples, 0.04%)</title><rect x="127.3" y="197" width="0.5" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="130.26" y="207.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,534,446,406 samples, 0.02%)</title><rect x="962.4" y="341" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="965.41" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,238,977,313 samples, 0.01%)</title><rect x="460.4" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="463.42" y="495.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (132,172,204,873 samples, 1.39%)</title><rect x="700.6" y="533" width="16.4" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="703.56" y="543.5" ></text>
</g>
<g >
<title>make_rel_from_joinlist (5,511,828,020 samples, 0.06%)</title><rect x="982.3" y="453" width="0.6" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="985.25" y="463.5" ></text>
</g>
<g >
<title>XLogRecordAssemble (15,240,237,962 samples, 0.16%)</title><rect x="389.9" y="325" width="1.9" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="392.90" y="335.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,358,346,833 samples, 0.04%)</title><rect x="1036.9" y="277" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1039.90" y="287.5" ></text>
</g>
<g >
<title>GETSTRUCT (2,314,308,410 samples, 0.02%)</title><rect x="961.1" y="373" width="0.3" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="964.15" y="383.5" ></text>
</g>
<g >
<title>SlruRecentlyUsed (911,229,460 samples, 0.01%)</title><rect x="479.5" y="421" width="0.1" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="482.49" y="431.5" ></text>
</g>
<g >
<title>hash_search (5,352,649,914 samples, 0.06%)</title><rect x="869.6" y="469" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="872.61" y="479.5" ></text>
</g>
<g >
<title>FileSize (15,488,598,636 samples, 0.16%)</title><rect x="936.3" y="245" width="2.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="939.33" y="255.5" ></text>
</g>
<g >
<title>palloc (1,634,081,525 samples, 0.02%)</title><rect x="358.6" y="341" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="361.65" y="351.5" ></text>
</g>
<g >
<title>BTreeTupleGetHeapTID (1,468,277,342 samples, 0.02%)</title><rect x="334.7" y="197" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="337.70" y="207.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (1,397,453,227 samples, 0.01%)</title><rect x="10.0" y="757" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="13.01" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (935,168,571 samples, 0.01%)</title><rect x="976.0" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="978.95" y="383.5" ></text>
</g>
<g >
<title>uint32_hash (1,396,841,920 samples, 0.01%)</title><rect x="1188.9" y="757" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1191.94" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,126,431,117 samples, 0.03%)</title><rect x="972.4" y="357" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="975.39" y="367.5" ></text>
</g>
<g >
<title>issue_xlog_fsync (809,564,764 samples, 0.01%)</title><rect x="1154.2" y="757" width="0.1" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1157.25" y="767.5" ></text>
</g>
<g >
<title>AllocSetReset (41,064,836,577 samples, 0.43%)</title><rect x="260.0" y="421" width="5.1" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="263.02" y="431.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,093,304,854 samples, 0.03%)</title><rect x="301.5" y="133" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="304.53" y="143.5" ></text>
</g>
<g >
<title>TupleDescAttr (997,843,103 samples, 0.01%)</title><rect x="127.4" y="181" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="130.43" y="191.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (12,016,250,271 samples, 0.13%)</title><rect x="889.4" y="341" width="1.5" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="892.44" y="351.5" ></text>
</g>
<g >
<title>AtEOXact_Snapshot (2,093,769,670 samples, 0.02%)</title><rect x="472.3" y="517" width="0.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="475.29" y="527.5" ></text>
</g>
<g >
<title>assign_list_collations (27,828,712,404 samples, 0.29%)</title><rect x="808.6" y="437" width="3.5" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="811.63" y="447.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,688,315,518 samples, 0.03%)</title><rect x="1009.2" y="229" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1012.21" y="239.5" ></text>
</g>
<g >
<title>palloc (1,461,970,926 samples, 0.02%)</title><rect x="880.2" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="883.19" y="447.5" ></text>
</g>
<g >
<title>ExecInterpExprStillValid (48,529,243,690 samples, 0.51%)</title><rect x="285.8" y="309" width="6.0" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="288.81" y="319.5" ></text>
</g>
<g >
<title>palloc0 (3,968,235,665 samples, 0.04%)</title><rect x="886.3" y="389" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="889.28" y="399.5" ></text>
</g>
<g >
<title>_bt_parallel_done (1,513,772,130 samples, 0.02%)</title><rect x="284.0" y="245" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="286.99" y="255.5" ></text>
</g>
<g >
<title>_bt_check_natts (15,726,161,082 samples, 0.17%)</title><rect x="319.5" y="229" width="1.9" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="322.50" y="239.5" ></text>
</g>
<g >
<title>ReleasePredicateLocks (907,624,527 samples, 0.01%)</title><rect x="91.1" y="757" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="94.09" y="767.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,789,879,889 samples, 0.02%)</title><rect x="223.6" y="533" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="226.65" y="543.5" ></text>
</g>
<g >
<title>[unknown] (1,700,398,746 samples, 0.02%)</title><rect x="177.8" y="501" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="180.78" y="511.5" ></text>
</g>
<g >
<title>pgstat_report_query_id (881,834,712 samples, 0.01%)</title><rect x="411.4" y="517" width="0.1" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="414.39" y="527.5" ></text>
</g>
<g >
<title>postmaster_child_launch (83,672,568,701 samples, 0.88%)</title><rect x="95.7" y="725" width="10.4" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="98.68" y="735.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (903,770,393 samples, 0.01%)</title><rect x="1045.3" y="389" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1048.31" y="399.5" ></text>
</g>
<g >
<title>uint32_hash (2,684,764,346 samples, 0.03%)</title><rect x="869.9" y="453" width="0.4" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="872.94" y="463.5" ></text>
</g>
<g >
<title>hash_bytes (2,565,523,029 samples, 0.03%)</title><rect x="941.9" y="293" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="944.91" y="303.5" ></text>
</g>
<g >
<title>pg_qsort (1,336,793,951 samples, 0.01%)</title><rect x="1170.7" y="757" width="0.2" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="1173.72" y="767.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (1,240,223,285 samples, 0.01%)</title><rect x="368.4" y="277" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="371.35" y="287.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,438,784,314 samples, 0.03%)</title><rect x="808.3" y="389" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="811.31" y="399.5" ></text>
</g>
<g >
<title>AllocSetFree (1,154,269,734 samples, 0.01%)</title><rect x="879.2" y="453" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="882.18" y="463.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (1,509,692,979 samples, 0.02%)</title><rect x="825.8" y="309" width="0.2" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="828.81" y="319.5" ></text>
</g>
<g >
<title>ReceiveSharedInvalidMessages (2,697,310,817 samples, 0.03%)</title><rect x="1074.0" y="501" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="1077.01" y="511.5" ></text>
</g>
<g >
<title>ReadBuffer_common (2,484,919,599 samples, 0.03%)</title><rect x="125.2" y="213" width="0.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="128.17" y="223.5" ></text>
</g>
<g >
<title>pull_varnos_walker (1,066,354,840 samples, 0.01%)</title><rect x="1175.0" y="757" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1177.96" y="767.5" ></text>
</g>
<g >
<title>pgstat_report_activity (6,268,678,228 samples, 0.07%)</title><rect x="1078.4" y="597" width="0.8" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1081.41" y="607.5" ></text>
</g>
<g >
<title>castNodeImpl (1,337,413,213 samples, 0.01%)</title><rect x="251.1" y="469" width="0.2" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="254.14" y="479.5" ></text>
</g>
<g >
<title>switch_fpu_return (6,213,103,817 samples, 0.07%)</title><rect x="175.2" y="421" width="0.8" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="178.21" y="431.5" ></text>
</g>
<g >
<title>AtEOXact_SPI (1,209,690,930 samples, 0.01%)</title><rect x="32.7" y="757" width="0.1" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="35.66" y="767.5" ></text>
</g>
<g >
<title>list_insert_nth (2,976,053,639 samples, 0.03%)</title><rect x="984.9" y="389" width="0.4" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="987.88" y="399.5" ></text>
</g>
<g >
<title>clauselist_selectivity (77,153,659,160 samples, 0.81%)</title><rect x="1027.9" y="389" width="9.5" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="1030.85" y="399.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (47,797,741,460 samples, 0.50%)</title><rect x="72.0" y="757" width="6.0" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="75.04" y="767.5" ></text>
</g>
<g >
<title>_bt_parallel_done (1,040,878,581 samples, 0.01%)</title><rect x="130.7" y="757" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="133.73" y="767.5" ></text>
</g>
<g >
<title>sock_def_readable (64,216,385,798 samples, 0.68%)</title><rect x="200.5" y="405" width="8.0" height="15.0" fill="rgb(216,54,13)" rx="2" ry="2" />
<text  x="203.52" y="415.5" ></text>
</g>
<g >
<title>task_tick_fair (1,088,598,025 samples, 0.01%)</title><rect x="795.9" y="389" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="798.92" y="399.5" ></text>
</g>
<g >
<title>canonicalize_ec_expression (2,786,720,186 samples, 0.03%)</title><rect x="970.2" y="389" width="0.4" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="973.21" y="399.5" ></text>
</g>
<g >
<title>futex_wake (3,037,308,997 samples, 0.03%)</title><rect x="367.8" y="165" width="0.4" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="370.83" y="175.5" ></text>
</g>
<g >
<title>downcase_truncate_identifier (10,962,105,939 samples, 0.12%)</title><rect x="1111.9" y="709" width="1.3" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="1114.87" y="719.5" ></text>
</g>
<g >
<title>epoll_wait (175,171,671,513 samples, 1.84%)</title><rect x="156.0" y="485" width="21.8" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="159.02" y="495.5" >e..</text>
</g>
<g >
<title>set_plan_refs (35,069,522,767 samples, 0.37%)</title><rect x="899.8" y="485" width="4.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="902.82" y="495.5" ></text>
</g>
<g >
<title>BlockIdSet (992,133,804 samples, 0.01%)</title><rect x="33.6" y="757" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="36.62" y="767.5" ></text>
</g>
<g >
<title>palloc0 (1,651,690,948 samples, 0.02%)</title><rect x="855.1" y="357" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="858.11" y="367.5" ></text>
</g>
<g >
<title>LockHeldByMe (7,010,079,425 samples, 0.07%)</title><rect x="1066.4" y="421" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1069.36" y="431.5" ></text>
</g>
<g >
<title>ReleaseSysCache (2,332,782,999 samples, 0.02%)</title><rect x="442.9" y="357" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="445.88" y="367.5" ></text>
</g>
<g >
<title>FreeSnapshot (1,868,082,717 samples, 0.02%)</title><rect x="224.3" y="565" width="0.2" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="227.29" y="575.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="501" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="89.91" y="511.5" ></text>
</g>
<g >
<title>tag_hash (11,088,711,533 samples, 0.12%)</title><rect x="851.7" y="357" width="1.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="854.73" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,091,218,610 samples, 0.01%)</title><rect x="965.5" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="968.50" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,034,171,510 samples, 0.01%)</title><rect x="382.6" y="309" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="385.62" y="319.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (9,312,461,801 samples, 0.10%)</title><rect x="954.9" y="389" width="1.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="957.89" y="399.5" ></text>
</g>
<g >
<title>pgstat_report_wait_start (1,043,556,687 samples, 0.01%)</title><rect x="1172.5" y="757" width="0.1" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="1175.51" y="767.5" ></text>
</g>
<g >
<title>pfree (2,906,827,158 samples, 0.03%)</title><rect x="241.4" y="389" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="244.37" y="399.5" ></text>
</g>
<g >
<title>all_rows_selectable (831,077,129 samples, 0.01%)</title><rect x="1031.3" y="229" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1034.35" y="239.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (5,235,356,531 samples, 0.06%)</title><rect x="451.0" y="421" width="0.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="453.97" y="431.5" ></text>
</g>
<g >
<title>prune_freeze_plan (16,485,804,812 samples, 0.17%)</title><rect x="1146.8" y="741" width="2.0" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="1149.75" y="751.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (920,616,971 samples, 0.01%)</title><rect x="91.0" y="757" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="93.98" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,096,182,948 samples, 0.01%)</title><rect x="911.0" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="914.00" y="415.5" ></text>
</g>
<g >
<title>palloc (1,499,396,351 samples, 0.02%)</title><rect x="944.0" y="373" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="947.05" y="383.5" ></text>
</g>
<g >
<title>obj_cgroup_charge_account (1,667,376,342 samples, 0.02%)</title><rect x="198.1" y="309" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="201.14" y="319.5" ></text>
</g>
<g >
<title>get_hash_value (3,631,320,980 samples, 0.04%)</title><rect x="823.0" y="357" width="0.5" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="826.03" y="367.5" ></text>
</g>
<g >
<title>update_curr (6,903,434,026 samples, 0.07%)</title><rect x="487.0" y="213" width="0.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="490.00" y="223.5" ></text>
</g>
<g >
<title>scanRTEForColumn (4,171,297,637 samples, 0.04%)</title><rect x="857.9" y="357" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="860.86" y="367.5" ></text>
</g>
<g >
<title>shmem_write_end (6,097,783,518 samples, 0.06%)</title><rect x="505.8" y="357" width="0.7" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="508.78" y="367.5" ></text>
</g>
<g >
<title>__strcmp_avx2 (2,239,780,073 samples, 0.02%)</title><rect x="421.0" y="437" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="423.96" y="447.5" ></text>
</g>
<g >
<title>ttwu_queue_wakelist (3,106,550,501 samples, 0.03%)</title><rect x="207.8" y="325" width="0.4" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="210.84" y="335.5" ></text>
</g>
<g >
<title>make_oper_cache_key (4,159,437,190 samples, 0.04%)</title><rect x="840.4" y="357" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="843.43" y="367.5" ></text>
</g>
<g >
<title>__update_load_avg_se (2,317,273,833 samples, 0.02%)</title><rect x="173.7" y="261" width="0.3" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="176.67" y="271.5" ></text>
</g>
<g >
<title>hash_search (4,156,029,174 samples, 0.04%)</title><rect x="453.9" y="373" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="456.91" y="383.5" ></text>
</g>
<g >
<title>pg_plan_queries (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="661" width="3.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="105.66" y="671.5" ></text>
</g>
<g >
<title>index_getnext_slot (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="341" width="1.9" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="129.31" y="351.5" ></text>
</g>
<g >
<title>core_yy_switch_to_buffer (5,139,866,925 samples, 0.05%)</title><rect x="871.8" y="517" width="0.7" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="874.85" y="527.5" ></text>
</g>
<g >
<title>xactGetCommittedChildren (959,902,762 samples, 0.01%)</title><rect x="1189.8" y="757" width="0.1" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="1192.78" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerRelease (116,857,528,312 samples, 1.23%)</title><rect x="515.6" y="517" width="14.5" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="518.57" y="527.5" ></text>
</g>
<g >
<title>pfree (1,236,325,592 samples, 0.01%)</title><rect x="993.0" y="357" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="995.96" y="367.5" ></text>
</g>
<g >
<title>new_list (3,409,189,435 samples, 0.04%)</title><rect x="813.5" y="437" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="816.48" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache1 (7,803,417,071 samples, 0.08%)</title><rect x="443.2" y="341" width="1.0" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="446.25" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_read_membarrier_u64 (1,762,478,320 samples, 0.02%)</title><rect x="497.5" y="469" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="500.52" y="479.5" ></text>
</g>
<g >
<title>newNode (4,140,603,022 samples, 0.04%)</title><rect x="273.0" y="373" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="275.95" y="383.5" ></text>
</g>
<g >
<title>bms_is_valid_set (945,918,429 samples, 0.01%)</title><rect x="358.2" y="357" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="361.19" y="367.5" ></text>
</g>
<g >
<title>pfree (1,463,420,590 samples, 0.02%)</title><rect x="235.1" y="533" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="238.14" y="543.5" ></text>
</g>
<g >
<title>ProcArrayEndTransaction (13,810,875,980 samples, 0.15%)</title><rect x="474.2" y="517" width="1.7" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="477.23" y="527.5" ></text>
</g>
<g >
<title>HeapCheckForSerializableConflictOut (1,343,277,671 samples, 0.01%)</title><rect x="304.6" y="245" width="0.2" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="307.59" y="255.5" ></text>
</g>
<g >
<title>SPI_inside_nonatomic_context (1,094,788,421 samples, 0.01%)</title><rect x="93.5" y="757" width="0.2" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="96.53" y="767.5" ></text>
</g>
<g >
<title>init_htab (10,144,661,058 samples, 0.11%)</title><rect x="1062.7" y="453" width="1.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1065.73" y="463.5" ></text>
</g>
<g >
<title>pfree (2,721,107,833 samples, 0.03%)</title><rect x="436.8" y="405" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="439.84" y="415.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (1,007,765,023 samples, 0.01%)</title><rect x="134.6" y="501" width="0.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="137.57" y="511.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (1,506,414,391 samples, 0.02%)</title><rect x="1063.6" y="405" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1066.59" y="415.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="549" width="3.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="105.66" y="559.5" ></text>
</g>
<g >
<title>lappend (2,658,388,166 samples, 0.03%)</title><rect x="893.3" y="453" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="896.29" y="463.5" ></text>
</g>
<g >
<title>create_projection_plan (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="485" width="0.2" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="128.92" y="495.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (1,507,370,874 samples, 0.02%)</title><rect x="250.3" y="437" width="0.1" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="253.26" y="447.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (141,229,299,243 samples, 1.49%)</title><rect x="192.1" y="485" width="17.5" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="195.08" y="495.5" ></text>
</g>
<g >
<title>_int_free_create_chunk (1,244,488,976 samples, 0.01%)</title><rect x="150.7" y="517" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="153.66" y="527.5" ></text>
</g>
<g >
<title>pgstat_get_transactional_drops (1,055,755,459 samples, 0.01%)</title><rect x="1171.9" y="757" width="0.2" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="1174.95" y="767.5" ></text>
</g>
<g >
<title>new_list (1,623,772,323 samples, 0.02%)</title><rect x="818.6" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="821.59" y="447.5" ></text>
</g>
<g >
<title>list_nth_cell (1,118,693,930 samples, 0.01%)</title><rect x="265.3" y="485" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="268.26" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,255,734,203 samples, 0.01%)</title><rect x="915.6" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="918.59" y="447.5" ></text>
</g>
<g >
<title>__schedule (42,150,001,446 samples, 0.44%)</title><rect x="483.7" y="293" width="5.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="486.68" y="303.5" ></text>
</g>
<g >
<title>CreatePortal (1,002,173,655 samples, 0.01%)</title><rect x="40.0" y="757" width="0.1" height="15.0" fill="rgb(219,66,16)" rx="2" ry="2" />
<text  x="43.00" y="767.5" ></text>
</g>
<g >
<title>_bt_checkkeys (2,827,522,797 samples, 0.03%)</title><rect x="329.3" y="229" width="0.3" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="332.27" y="239.5" ></text>
</g>
<g >
<title>ExecPushExprSetupSteps (7,129,104,160 samples, 0.08%)</title><rect x="424.3" y="341" width="0.9" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="427.32" y="351.5" ></text>
</g>
<g >
<title>AtEOXact_GUC (1,752,924,875 samples, 0.02%)</title><rect x="469.7" y="517" width="0.2" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="472.70" y="527.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,681,415,749 samples, 0.02%)</title><rect x="526.9" y="421" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="529.92" y="431.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (2,368,656,008 samples, 0.02%)</title><rect x="330.5" y="245" width="0.3" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="333.51" y="255.5" ></text>
</g>
<g >
<title>ExecGetUpdateNewTuple (23,164,579,337 samples, 0.24%)</title><rect x="269.1" y="437" width="2.8" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="272.07" y="447.5" ></text>
</g>
<g >
<title>__check_object_size.part.0 (4,611,787,322 samples, 0.05%)</title><rect x="186.3" y="309" width="0.5" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="189.25" y="319.5" ></text>
</g>
<g >
<title>futex_wait (1,573,188,973 samples, 0.02%)</title><rect x="496.9" y="341" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="499.87" y="351.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (2,128,379,240 samples, 0.02%)</title><rect x="820.4" y="421" width="0.3" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="823.42" y="431.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (880,330,173 samples, 0.01%)</title><rect x="112.2" y="741" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="115.15" y="751.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,172,323,886 samples, 0.01%)</title><rect x="938.9" y="373" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="941.94" y="383.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (6,270,139,967 samples, 0.07%)</title><rect x="890.1" y="293" width="0.8" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="893.13" y="303.5" ></text>
</g>
<g >
<title>bms_next_member (1,888,487,697 samples, 0.02%)</title><rect x="360.1" y="373" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="363.07" y="383.5" ></text>
</g>
<g >
<title>ReadBuffer_common (1,220,476,765 samples, 0.01%)</title><rect x="125.8" y="213" width="0.1" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="128.76" y="223.5" ></text>
</g>
<g >
<title>uint32_hash (1,219,712,448 samples, 0.01%)</title><rect x="925.0" y="405" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="927.96" y="415.5" ></text>
</g>
<g >
<title>__raw_spin_lock_irqsave (3,325,037,209 samples, 0.04%)</title><rect x="201.4" y="325" width="0.5" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="204.44" y="335.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,126,545,514 samples, 0.01%)</title><rect x="393.6" y="309" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="396.59" y="319.5" ></text>
</g>
<g >
<title>ReadBuffer_common (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="261" width="2.4" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="103.12" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,806,337,406 samples, 0.02%)</title><rect x="1114.4" y="693" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1117.35" y="703.5" ></text>
</g>
<g >
<title>transformAssignedExpr (10,287,216,765 samples, 0.11%)</title><rect x="843.2" y="453" width="1.3" height="15.0" fill="rgb(241,170,40)" rx="2" ry="2" />
<text  x="846.19" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,083,603,956 samples, 0.02%)</title><rect x="477.8" y="405" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="480.78" y="415.5" ></text>
</g>
<g >
<title>RelationClose (1,043,015,356 samples, 0.01%)</title><rect x="862.2" y="485" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="865.24" y="495.5" ></text>
</g>
<g >
<title>PostgresMain (973,110,606 samples, 0.01%)</title><rect x="84.2" y="757" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="87.20" y="767.5" ></text>
</g>
<g >
<title>exec_simple_query (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="661" width="2.8" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="87.32" y="671.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (2,871,476,812 samples, 0.03%)</title><rect x="375.8" y="293" width="0.4" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="378.82" y="303.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,684,832,133 samples, 0.02%)</title><rect x="445.0" y="341" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="447.96" y="351.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (11,266,658,778 samples, 0.12%)</title><rect x="1066.3" y="453" width="1.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="1069.29" y="463.5" ></text>
</g>
<g >
<title>deconstruct_jointree (962,317,846 samples, 0.01%)</title><rect x="1132.0" y="757" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="1135.03" y="767.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,416,697,638 samples, 0.01%)</title><rect x="478.1" y="357" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="481.14" y="367.5" ></text>
</g>
<g >
<title>pq_endmessage (6,284,359,761 samples, 0.07%)</title><rect x="189.3" y="581" width="0.8" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="192.35" y="591.5" ></text>
</g>
<g >
<title>GetBufferDescriptor (2,112,157,447 samples, 0.02%)</title><rect x="48.8" y="757" width="0.2" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="51.77" y="767.5" ></text>
</g>
<g >
<title>inode_needs_update_time.part.0 (2,363,892,230 samples, 0.02%)</title><rect x="501.9" y="357" width="0.3" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="504.90" y="367.5" ></text>
</g>
<g >
<title>verify_compact_attribute (7,045,065,720 samples, 0.07%)</title><rect x="276.1" y="389" width="0.9" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="279.08" y="399.5" ></text>
</g>
<g >
<title>newNode (9,425,121,194 samples, 0.10%)</title><rect x="912.3" y="469" width="1.1" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="915.27" y="479.5" ></text>
</g>
<g >
<title>build_index_tlist (987,193,319 samples, 0.01%)</title><rect x="1124.4" y="757" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1127.37" y="767.5" ></text>
</g>
<g >
<title>avc_lookup (1,118,425,923 samples, 0.01%)</title><rect x="180.8" y="341" width="0.1" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="183.78" y="351.5" ></text>
</g>
<g >
<title>reduce_unique_semijoins (1,061,090,841 samples, 0.01%)</title><rect x="1175.8" y="757" width="0.2" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1178.82" y="767.5" ></text>
</g>
<g >
<title>newNode (5,530,176,173 samples, 0.06%)</title><rect x="449.7" y="421" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="452.74" y="431.5" ></text>
</g>
<g >
<title>distribute_restrictinfo_to_rels (11,222,497,471 samples, 0.12%)</title><rect x="980.0" y="437" width="1.4" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="983.00" y="447.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (4,975,350,095 samples, 0.05%)</title><rect x="105.2" y="453" width="0.6" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="108.21" y="463.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,015,384,591 samples, 0.01%)</title><rect x="50.1" y="757" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="53.08" y="767.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (863,418,846 samples, 0.01%)</title><rect x="818.2" y="373" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="821.19" y="383.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,149,880,446 samples, 0.01%)</title><rect x="379.0" y="341" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="382.00" y="351.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="389" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="89.91" y="399.5" ></text>
</g>
<g >
<title>schedule (890,099,722 samples, 0.01%)</title><rect x="353.6" y="181" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="356.57" y="191.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,663,454,702 samples, 0.02%)</title><rect x="1007.8" y="229" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1010.84" y="239.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="677" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.62" y="687.5" ></text>
</g>
<g >
<title>create_plan (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="581" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="89.91" y="591.5" ></text>
</g>
<g >
<title>hash_bytes (10,258,936,433 samples, 0.11%)</title><rect x="839.1" y="309" width="1.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="842.15" y="319.5" ></text>
</g>
<g >
<title>int4hashfast (1,004,833,000 samples, 0.01%)</title><rect x="963.9" y="309" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="966.94" y="319.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (8,233,433,871 samples, 0.09%)</title><rect x="225.2" y="549" width="1.0" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="228.19" y="559.5" ></text>
</g>
<g >
<title>palloc (1,573,369,985 samples, 0.02%)</title><rect x="861.5" y="469" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="864.45" y="479.5" ></text>
</g>
<g >
<title>recomputeNamespacePath (1,012,472,891 samples, 0.01%)</title><rect x="830.2" y="389" width="0.1" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="833.22" y="399.5" ></text>
</g>
<g >
<title>remove_useless_joins (869,251,336 samples, 0.01%)</title><rect x="1176.4" y="757" width="0.1" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="1179.38" y="767.5" ></text>
</g>
<g >
<title>object_aclcheck (1,473,078,700 samples, 0.02%)</title><rect x="428.3" y="325" width="0.2" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="431.31" y="335.5" ></text>
</g>
<g >
<title>requeue_delayed_entity (1,024,260,723 samples, 0.01%)</title><rect x="202.2" y="309" width="0.1" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="205.17" y="319.5" ></text>
</g>
<g >
<title>exprTypmod (1,766,807,762 samples, 0.02%)</title><rect x="445.7" y="373" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="448.67" y="383.5" ></text>
</g>
<g >
<title>bms_add_members (1,203,143,957 samples, 0.01%)</title><rect x="348.5" y="389" width="0.2" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="351.53" y="399.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,612,833,817 samples, 0.02%)</title><rect x="847.1" y="405" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="850.07" y="415.5" ></text>
</g>
<g >
<title>BufferAlloc (26,553,538,917 samples, 0.28%)</title><rect x="95.8" y="197" width="3.3" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="98.80" y="207.5" ></text>
</g>
<g >
<title>ReadBuffer (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="277" width="0.5" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="89.39" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,317,514,944 samples, 0.01%)</title><rect x="842.3" y="277" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="845.35" y="287.5" ></text>
</g>
<g >
<title>hash_seq_search (12,373,894,749 samples, 0.13%)</title><rect x="528.0" y="453" width="1.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="531.01" y="463.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,008,983,877 samples, 0.01%)</title><rect x="853.8" y="357" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="856.76" y="367.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (1,148,909,795 samples, 0.01%)</title><rect x="382.6" y="325" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="385.61" y="335.5" ></text>
</g>
<g >
<title>palloc0 (15,319,797,040 samples, 0.16%)</title><rect x="279.2" y="373" width="1.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="282.19" y="383.5" ></text>
</g>
<g >
<title>ExecARUpdateTriggers (1,007,016,437 samples, 0.01%)</title><rect x="395.7" y="405" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="398.71" y="415.5" ></text>
</g>
<g >
<title>eval_const_expressions (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="725" width="0.3" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1188.13" y="735.5" ></text>
</g>
<g >
<title>ExecutorStart (397,496,428,256 samples, 4.18%)</title><rect x="411.2" y="533" width="49.4" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="414.20" y="543.5" >Exec..</text>
</g>
<g >
<title>bms_copy (1,963,157,668 samples, 0.02%)</title><rect x="880.1" y="453" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="883.13" y="463.5" ></text>
</g>
<g >
<title>FastPathGrantRelationLock (6,654,423,066 samples, 0.07%)</title><rect x="821.7" y="373" width="0.8" height="15.0" fill="rgb(245,188,44)" rx="2" ry="2" />
<text  x="824.71" y="383.5" ></text>
</g>
<g >
<title>find_relation_notnullatts (3,436,950,386 samples, 0.04%)</title><rect x="938.8" y="405" width="0.5" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="941.82" y="415.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,453,850,893 samples, 0.02%)</title><rect x="408.6" y="261" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="411.56" y="271.5" ></text>
</g>
<g >
<title>ExtendSUBTRANS (806,898,767 samples, 0.01%)</title><rect x="365.3" y="309" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="368.29" y="319.5" ></text>
</g>
<g >
<title>tag_hash (2,533,764,263 samples, 0.03%)</title><rect x="823.2" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="826.17" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,873,283,980 samples, 0.02%)</title><rect x="818.1" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="821.10" y="399.5" ></text>
</g>
<g >
<title>table_relation_size (23,240,463,389 samples, 0.24%)</title><rect x="935.6" y="325" width="2.9" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="938.63" y="335.5" ></text>
</g>
<g >
<title>index_getnext_slot (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="357" width="1.9" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="126.99" y="367.5" ></text>
</g>
<g >
<title>ExecutorRun (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="613" width="6.9" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="98.68" y="623.5" ></text>
</g>
<g >
<title>makeRawStmt (2,522,558,826 samples, 0.03%)</title><rect x="1117.7" y="741" width="0.3" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1120.72" y="751.5" ></text>
</g>
<g >
<title>cost_qual_eval (16,688,257,865 samples, 0.18%)</title><rect x="1037.4" y="389" width="2.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="1040.44" y="399.5" ></text>
</g>
<g >
<title>pg_nextpower2_32 (1,511,907,119 samples, 0.02%)</title><rect x="1164.4" y="741" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1167.45" y="751.5" ></text>
</g>
<g >
<title>make_indexscan (5,459,617,758 samples, 0.06%)</title><rect x="891.6" y="389" width="0.6" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="894.56" y="399.5" ></text>
</g>
<g >
<title>PinBufferForBlock (14,958,118,779 samples, 0.16%)</title><rect x="354.4" y="293" width="1.9" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="357.42" y="303.5" ></text>
</g>
<g >
<title>_bt_metaversion (1,304,417,079 samples, 0.01%)</title><rect x="323.6" y="261" width="0.2" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="326.63" y="271.5" ></text>
</g>
<g >
<title>ReadBufferExtended (29,094,637,079 samples, 0.31%)</title><rect x="299.4" y="229" width="3.6" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="302.42" y="239.5" ></text>
</g>
<g >
<title>MakeTupleTableSlot (4,795,042,600 samples, 0.05%)</title><rect x="432.6" y="357" width="0.6" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="435.60" y="367.5" ></text>
</g>
<g >
<title>match_clauses_to_index (27,909,785,324 samples, 0.29%)</title><rect x="1018.5" y="373" width="3.5" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="1021.49" y="383.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (1,478,032,175 samples, 0.02%)</title><rect x="367.0" y="85" width="0.2" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="369.98" y="95.5" ></text>
</g>
<g >
<title>_equalList (1,060,805,892 samples, 0.01%)</title><rect x="131.7" y="757" width="0.1" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="134.68" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,450,674,986 samples, 0.05%)</title><rect x="807.0" y="373" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="810.02" y="383.5" ></text>
</g>
<g >
<title>ReleaseSysCache (849,618,039 samples, 0.01%)</title><rect x="1036.7" y="309" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1039.72" y="319.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (4,599,176,588 samples, 0.05%)</title><rect x="124.2" y="293" width="0.6" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="127.19" y="303.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,223,169,508 samples, 0.05%)</title><rect x="955.3" y="341" width="0.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="958.32" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,253,935,833 samples, 0.02%)</title><rect x="477.8" y="421" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="480.76" y="431.5" ></text>
</g>
<g >
<title>LockHeldByMe (14,530,147,478 samples, 0.15%)</title><rect x="451.9" y="373" width="1.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="454.92" y="383.5" ></text>
</g>
<g >
<title>new_list (1,654,483,277 samples, 0.02%)</title><rect x="1070.7" y="469" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1073.74" y="479.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,176,617,427 samples, 0.02%)</title><rect x="953.6" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="956.61" y="463.5" ></text>
</g>
<g >
<title>ReadBuffer_common (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="261" width="4.4" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="98.68" y="271.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (2,606,254,336 samples, 0.03%)</title><rect x="334.9" y="197" width="0.3" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="337.88" y="207.5" ></text>
</g>
<g >
<title>tag_hash (2,748,797,471 samples, 0.03%)</title><rect x="523.2" y="421" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="526.20" y="431.5" ></text>
</g>
<g >
<title>fetch_att (988,333,893 samples, 0.01%)</title><rect x="323.2" y="213" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="326.18" y="223.5" ></text>
</g>
<g >
<title>GetMemoryChunkMethodID (886,670,887 samples, 0.01%)</title><rect x="1167.5" y="741" width="0.1" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="1170.46" y="751.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="645" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1137.90" y="655.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (29,784,568,496 samples, 0.31%)</title><rect x="491.4" y="421" width="3.7" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="494.40" y="431.5" ></text>
</g>
<g >
<title>hash_search (4,374,995,272 samples, 0.05%)</title><rect x="451.1" y="389" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="454.07" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,238,132,143 samples, 0.02%)</title><rect x="891.2" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="894.15" y="335.5" ></text>
</g>
<g >
<title>exec_simple_query (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="645" width="0.4" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="1161.90" y="655.5" ></text>
</g>
<g >
<title>wake_up_q (1,677,676,086 samples, 0.02%)</title><rect x="389.4" y="117" width="0.3" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="392.44" y="127.5" ></text>
</g>
<g >
<title>newNode (7,950,256,344 samples, 0.08%)</title><rect x="894.5" y="517" width="1.0" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="897.51" y="527.5" ></text>
</g>
<g >
<title>core_yy_scan_buffer (7,969,000,741 samples, 0.08%)</title><rect x="871.7" y="533" width="1.0" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="874.70" y="543.5" ></text>
</g>
<g >
<title>AllocSetFree (2,775,002,622 samples, 0.03%)</title><rect x="254.6" y="453" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="257.61" y="463.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_in (2,061,136,243 samples, 0.02%)</title><rect x="484.8" y="261" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="487.75" y="271.5" ></text>
</g>
<g >
<title>bms_union (2,873,548,326 samples, 0.03%)</title><rect x="345.6" y="389" width="0.3" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="348.58" y="399.5" ></text>
</g>
<g >
<title>heapam_fetch_row_version (40,664,867,481 samples, 0.43%)</title><rect x="404.8" y="421" width="5.1" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="407.84" y="431.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (1,371,392,756 samples, 0.01%)</title><rect x="240.9" y="373" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="243.86" y="383.5" ></text>
</g>
<g >
<title>verify_compact_attribute (31,352,736,416 samples, 0.33%)</title><rect x="1002.9" y="181" width="3.9" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1005.87" y="191.5" ></text>
</g>
<g >
<title>index_getattr (4,237,052,526 samples, 0.04%)</title><rect x="127.2" y="229" width="0.6" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="130.24" y="239.5" ></text>
</g>
<g >
<title>wipe_mem (840,750,065 samples, 0.01%)</title><rect x="29.2" y="741" width="0.1" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="32.23" y="751.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (966,676,862 samples, 0.01%)</title><rect x="125.5" y="165" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="128.48" y="175.5" ></text>
</g>
<g >
<title>GetSysCacheOid (865,685,696 samples, 0.01%)</title><rect x="50.5" y="757" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="53.47" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (9,610,471,733 samples, 0.10%)</title><rect x="995.8" y="309" width="1.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="998.76" y="319.5" ></text>
</g>
<g >
<title>palloc (1,322,413,108 samples, 0.01%)</title><rect x="422.9" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="425.88" y="351.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (3,262,697,675 samples, 0.03%)</title><rect x="409.5" y="293" width="0.4" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="412.45" y="303.5" ></text>
</g>
<g >
<title>PushCopiedSnapshot (6,586,641,761 samples, 0.07%)</title><rect x="461.4" y="549" width="0.8" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="464.37" y="559.5" ></text>
</g>
<g >
<title>__GI___strlcpy (896,741,392 samples, 0.01%)</title><rect x="840.7" y="341" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="843.69" y="351.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (2,745,421,149 samples, 0.03%)</title><rect x="49.7" y="757" width="0.4" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="52.74" y="767.5" ></text>
</g>
<g >
<title>LWLockConflictsWithVar (3,721,461,057 samples, 0.04%)</title><rect x="496.3" y="453" width="0.4" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="499.26" y="463.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (3,163,372,393 samples, 0.03%)</title><rect x="1059.9" y="453" width="0.4" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="1062.87" y="463.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (870,556,938 samples, 0.01%)</title><rect x="1129.9" y="757" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1132.90" y="767.5" ></text>
</g>
<g >
<title>XLogInsertRecord (17,260,928,731 samples, 0.18%)</title><rect x="511.8" y="469" width="2.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="514.77" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,724,928,116 samples, 0.02%)</title><rect x="1078.1" y="549" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1081.10" y="559.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,471,047,419 samples, 0.02%)</title><rect x="191.9" y="485" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="194.89" y="495.5" ></text>
</g>
<g >
<title>lappend (2,383,950,642 samples, 0.03%)</title><rect x="971.1" y="389" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="974.09" y="399.5" ></text>
</g>
<g >
<title>palloc (1,375,884,751 samples, 0.01%)</title><rect x="930.6" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="933.55" y="383.5" ></text>
</g>
<g >
<title>secure_read (276,232,890,148 samples, 2.91%)</title><rect x="152.9" y="533" width="34.3" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="155.93" y="543.5" >se..</text>
</g>
<g >
<title>index_pages_fetched (3,160,228,045 samples, 0.03%)</title><rect x="1016.0" y="325" width="0.4" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="1019.02" y="335.5" ></text>
</g>
<g >
<title>eval_const_expressions (13,714,206,006 samples, 0.14%)</title><rect x="1051.9" y="485" width="1.7" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1054.89" y="495.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,197,076,664 samples, 0.03%)</title><rect x="214.2" y="533" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="217.16" y="543.5" ></text>
</g>
<g >
<title>int2hashfast (811,327,929 samples, 0.01%)</title><rect x="1032.9" y="149" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1035.89" y="159.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,004,566,512 samples, 0.01%)</title><rect x="234.0" y="469" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="237.01" y="479.5" ></text>
</g>
<g >
<title>pg_comp_crc32c_sse42 (1,253,633,907 samples, 0.01%)</title><rect x="514.2" y="453" width="0.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="517.23" y="463.5" ></text>
</g>
<g >
<title>TransactionLogFetch (6,834,045,459 samples, 0.07%)</title><rect x="1147.9" y="677" width="0.9" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="1150.94" y="687.5" ></text>
</g>
<g >
<title>make_restrictinfo (1,157,992,044 samples, 0.01%)</title><rect x="1161.1" y="757" width="0.2" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="1164.11" y="767.5" ></text>
</g>
<g >
<title>add_base_rels_to_query (1,294,386,331 samples, 0.01%)</title><rect x="1082.9" y="757" width="0.2" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="1085.93" y="767.5" ></text>
</g>
<g >
<title>expression_returns_set (7,920,249,509 samples, 0.08%)</title><rect x="844.8" y="453" width="1.0" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="847.77" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,515,498,775 samples, 0.05%)</title><rect x="426.1" y="261" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="429.08" y="271.5" ></text>
</g>
<g >
<title>palloc (1,663,282,879 samples, 0.02%)</title><rect x="870.5" y="501" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="873.52" y="511.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (971,078,648 samples, 0.01%)</title><rect x="128.4" y="469" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="131.36" y="479.5" ></text>
</g>
<g >
<title>ReadCommand (295,567,846,450 samples, 3.11%)</title><rect x="151.9" y="597" width="36.7" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="154.91" y="607.5" >Rea..</text>
</g>
<g >
<title>fetch_search_path_array (1,405,468,277 samples, 0.01%)</title><rect x="854.0" y="373" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="857.03" y="383.5" ></text>
</g>
<g >
<title>MarkBufferDirtyHint (2,345,747,940 samples, 0.02%)</title><rect x="1147.6" y="677" width="0.3" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="1150.63" y="687.5" ></text>
</g>
<g >
<title>_bt_readpage (4,687,329,473 samples, 0.05%)</title><rect x="126.3" y="261" width="0.6" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="129.31" y="271.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="677" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1137.90" y="687.5" ></text>
</g>
<g >
<title>palloc (1,484,863,677 samples, 0.02%)</title><rect x="965.5" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="968.46" y="351.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetSnapshot (983,012,021 samples, 0.01%)</title><rect x="265.5" y="469" width="0.1" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="268.52" y="479.5" ></text>
</g>
<g >
<title>newNode (2,326,951,692 samples, 0.02%)</title><rect x="856.6" y="341" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="859.57" y="351.5" ></text>
</g>
<g >
<title>ExecutePlan (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="533" width="1.9" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="126.99" y="543.5" ></text>
</g>
<g >
<title>fdget_pos (1,631,801,070 samples, 0.02%)</title><rect x="932.6" y="261" width="0.2" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="935.58" y="271.5" ></text>
</g>
<g >
<title>GetSnapshotData (1,929,345,759 samples, 0.02%)</title><rect x="50.2" y="757" width="0.3" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="53.22" y="767.5" ></text>
</g>
<g >
<title>ExecAllocTableSlot (10,845,568,521 samples, 0.11%)</title><rect x="446.0" y="405" width="1.4" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="449.02" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,003,245,304 samples, 0.01%)</title><rect x="1173.6" y="565" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1176.62" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,168,598,561 samples, 0.01%)</title><rect x="215.4" y="549" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="218.39" y="559.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (42,100,144,036 samples, 0.44%)</title><rect x="259.9" y="437" width="5.3" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="262.92" y="447.5" ></text>
</g>
<g >
<title>sched_balance_softirq (1,097,912,714 samples, 0.01%)</title><rect x="757.8" y="469" width="0.1" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="760.77" y="479.5" ></text>
</g>
<g >
<title>check_stack_depth (1,938,045,058 samples, 0.02%)</title><rect x="1137.0" y="741" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="1140.02" y="751.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,896,455,345 samples, 0.04%)</title><rect x="353.3" y="357" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="356.26" y="367.5" ></text>
</g>
<g >
<title>do_syscall_64 (4,547,918,644 samples, 0.05%)</title><rect x="366.6" y="229" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="369.65" y="239.5" ></text>
</g>
<g >
<title>get_typlen (11,123,220,609 samples, 0.12%)</title><rect x="430.5" y="357" width="1.4" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="433.54" y="367.5" ></text>
</g>
<g >
<title>_copyVar (8,724,881,163 samples, 0.09%)</title><rect x="951.9" y="421" width="1.0" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="954.86" y="431.5" ></text>
</g>
<g >
<title>DynaHashAlloc (1,352,127,764 samples, 0.01%)</title><rect x="1063.0" y="437" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1066.04" y="447.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (1,795,828,442 samples, 0.02%)</title><rect x="124.2" y="229" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="127.19" y="239.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (1,397,105,611 samples, 0.01%)</title><rect x="45.8" y="757" width="0.2" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="48.82" y="767.5" ></text>
</g>
<g >
<title>exprType (2,309,508,867 samples, 0.02%)</title><rect x="847.9" y="405" width="0.3" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="850.94" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,822,297,334 samples, 0.02%)</title><rect x="1168.6" y="757" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1171.63" y="767.5" ></text>
</g>
<g >
<title>WALInsertLockAcquire (3,341,249,431 samples, 0.04%)</title><rect x="513.1" y="453" width="0.4" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="516.07" y="463.5" ></text>
</g>
<g >
<title>list_free (2,400,616,723 samples, 0.03%)</title><rect x="944.3" y="405" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="947.26" y="415.5" ></text>
</g>
<g >
<title>InitResultRelInfo (12,452,295,165 samples, 0.13%)</title><rect x="454.5" y="437" width="1.5" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="457.45" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,075,551,130 samples, 0.02%)</title><rect x="365.9" y="277" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="368.92" y="287.5" ></text>
</g>
<g >
<title>palloc0 (9,197,701,873 samples, 0.10%)</title><rect x="912.3" y="453" width="1.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="915.30" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (912,254,503 samples, 0.01%)</title><rect x="882.9" y="341" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="885.88" y="351.5" ></text>
</g>
<g >
<title>StartReadBuffer (18,035,515,597 samples, 0.19%)</title><rect x="354.0" y="325" width="2.3" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="357.04" y="335.5" ></text>
</g>
<g >
<title>sentinel_ok (26,633,350,962 samples, 0.28%)</title><rect x="25.4" y="741" width="3.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="28.37" y="751.5" ></text>
</g>
<g >
<title>BTreeTupleIsPosting (1,037,625,161 samples, 0.01%)</title><rect x="33.4" y="757" width="0.1" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="36.37" y="767.5" ></text>
</g>
<g >
<title>BackendMain (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="661" width="2.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="126.99" y="671.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (1,291,805,830 samples, 0.01%)</title><rect x="389.5" y="69" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="392.45" y="79.5" ></text>
</g>
<g >
<title>BufferGetBlock (964,359,975 samples, 0.01%)</title><rect x="311.8" y="229" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="314.82" y="239.5" ></text>
</g>
<g >
<title>finalize_plan (46,253,038,359 samples, 0.49%)</title><rect x="877.3" y="501" width="5.8" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="880.31" y="511.5" ></text>
</g>
<g >
<title>ExecEndIndexScan (64,325,378,638 samples, 0.68%)</title><rect x="240.7" y="437" width="7.9" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="243.65" y="447.5" ></text>
</g>
<g >
<title>TrackNewBufferPin (944,682,270 samples, 0.01%)</title><rect x="102.1" y="165" width="0.1" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="105.12" y="175.5" ></text>
</g>
<g >
<title>setup_eager_aggregation (982,529,351 samples, 0.01%)</title><rect x="1043.4" y="469" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="1046.36" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,011,578,261 samples, 0.02%)</title><rect x="1116.8" y="693" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1119.85" y="703.5" ></text>
</g>
<g >
<title>new_list (1,748,619,233 samples, 0.02%)</title><rect x="890.5" y="261" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="893.50" y="271.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,595,576,248 samples, 0.04%)</title><rect x="1033.9" y="133" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1036.93" y="143.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,324,803,868 samples, 0.01%)</title><rect x="271.7" y="261" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="274.71" y="271.5" ></text>
</g>
<g >
<title>ExecScanExtended (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="405" width="1.9" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="126.99" y="415.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,040,829,828 samples, 0.01%)</title><rect x="1122.5" y="757" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1125.52" y="767.5" ></text>
</g>
<g >
<title>restore_fpregs_from_fpstate (5,118,200,385 samples, 0.05%)</title><rect x="175.3" y="405" width="0.7" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="178.35" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,011,666,888 samples, 0.01%)</title><rect x="989.6" y="309" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="992.60" y="319.5" ></text>
</g>
<g >
<title>call_function_single_prep_ipi (973,253,963 samples, 0.01%)</title><rect x="208.0" y="293" width="0.1" height="15.0" fill="rgb(213,41,9)" rx="2" ry="2" />
<text  x="211.00" y="303.5" ></text>
</g>
<g >
<title>pfree (5,852,259,179 samples, 0.06%)</title><rect x="359.1" y="357" width="0.7" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="362.11" y="367.5" ></text>
</g>
<g >
<title>BufferIsLockedByMe (1,147,674,532 samples, 0.01%)</title><rect x="1147.7" y="661" width="0.1" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="1150.67" y="671.5" ></text>
</g>
<g >
<title>AllocSetAlloc (10,389,138,858 samples, 0.11%)</title><rect x="279.8" y="357" width="1.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="282.79" y="367.5" ></text>
</g>
<g >
<title>updateTargetListEntry (1,130,056,068 samples, 0.01%)</title><rect x="1189.2" y="757" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="1192.21" y="767.5" ></text>
</g>
<g >
<title>tag_hash (2,436,389,534 samples, 0.03%)</title><rect x="1186.7" y="757" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1189.71" y="767.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,585,942,032 samples, 0.02%)</title><rect x="298.8" y="229" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="301.81" y="239.5" ></text>
</g>
<g >
<title>main (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="725" width="2.2" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="129.31" y="735.5" ></text>
</g>
<g >
<title>lappend_int (2,625,942,724 samples, 0.03%)</title><rect x="440.7" y="405" width="0.3" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="443.70" y="415.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,401,218,320 samples, 0.04%)</title><rect x="291.3" y="181" width="0.5" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="294.33" y="191.5" ></text>
</g>
<g >
<title>__skb_datagram_iter (9,291,827,722 samples, 0.10%)</title><rect x="185.7" y="341" width="1.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="188.67" y="351.5" ></text>
</g>
<g >
<title>TransactionBlockStatusCode (1,569,639,292 samples, 0.02%)</title><rect x="188.8" y="581" width="0.2" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="191.82" y="591.5" ></text>
</g>
<g >
<title>hash_search (7,627,087,116 samples, 0.08%)</title><rect x="228.6" y="565" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="231.58" y="575.5" ></text>
</g>
<g >
<title>do_futex (1,074,809,871 samples, 0.01%)</title><rect x="475.6" y="373" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="478.58" y="383.5" ></text>
</g>
<g >
<title>heap_fetch (36,370,308,383 samples, 0.38%)</title><rect x="405.4" y="405" width="4.5" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="408.37" y="415.5" ></text>
</g>
<g >
<title>__slab_free (1,306,789,759 samples, 0.01%)</title><rect x="184.9" y="357" width="0.1" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="187.86" y="367.5" ></text>
</g>
<g >
<title>ShutdownExprContext (1,020,731,457 samples, 0.01%)</title><rect x="253.6" y="469" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="256.57" y="479.5" ></text>
</g>
<g >
<title>hash_search (5,972,689,102 samples, 0.06%)</title><rect x="862.6" y="437" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="865.57" y="447.5" ></text>
</g>
<g >
<title>sentinel_ok (1,739,158,090 samples, 0.02%)</title><rect x="259.6" y="421" width="0.2" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="262.58" y="431.5" ></text>
</g>
<g >
<title>sched_balance_newidle (1,027,502,125 samples, 0.01%)</title><rect x="484.3" y="245" width="0.1" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="487.29" y="255.5" ></text>
</g>
<g >
<title>fireASTriggers (1,053,925,048 samples, 0.01%)</title><rect x="404.4" y="437" width="0.1" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="407.37" y="447.5" ></text>
</g>
<g >
<title>select_idle_cpu (14,753,483,129 samples, 0.16%)</title><rect x="203.6" y="277" width="1.8" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="206.59" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,097,643,658 samples, 0.05%)</title><rect x="1022.7" y="325" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1025.69" y="335.5" ></text>
</g>
<g >
<title>_equalList (1,969,861,195 samples, 0.02%)</title><rect x="913.9" y="469" width="0.2" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="916.86" y="479.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (986,139,691 samples, 0.01%)</title><rect x="326.6" y="165" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="329.55" y="175.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,400,634,491 samples, 0.04%)</title><rect x="829.7" y="309" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="832.68" y="319.5" ></text>
</g>
<g >
<title>exprCollation (1,294,705,787 samples, 0.01%)</title><rect x="970.3" y="373" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="973.29" y="383.5" ></text>
</g>
<g >
<title>PinBufferForBlock (4,655,897,203 samples, 0.05%)</title><rect x="85.8" y="197" width="0.6" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="88.81" y="207.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,107,025,354 samples, 0.01%)</title><rect x="527.2" y="421" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="530.18" y="431.5" ></text>
</g>
<g >
<title>func_parallel (5,727,809,226 samples, 0.06%)</title><rect x="916.9" y="389" width="0.7" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="919.92" y="399.5" ></text>
</g>
<g >
<title>new_list (1,939,102,335 samples, 0.02%)</title><rect x="910.9" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="913.92" y="447.5" ></text>
</g>
<g >
<title>_bt_search (97,245,135,828 samples, 1.02%)</title><rect x="330.0" y="261" width="12.1" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="333.04" y="271.5" ></text>
</g>
<g >
<title>deconstruct_recurse (16,244,537,779 samples, 0.17%)</title><rect x="974.4" y="437" width="2.0" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="977.43" y="447.5" ></text>
</g>
<g >
<title>do_futex (3,122,279,661 samples, 0.03%)</title><rect x="367.8" y="181" width="0.4" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="370.82" y="191.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple (7,045,219,313 samples, 0.07%)</title><rect x="290.9" y="229" width="0.9" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="293.92" y="239.5" ></text>
</g>
<g >
<title>check_heap_object (4,906,823,422 samples, 0.05%)</title><rect x="195.7" y="373" width="0.6" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="198.65" y="383.5" ></text>
</g>
<g >
<title>set_ps_display_with_len (3,733,192,016 samples, 0.04%)</title><rect x="1072.4" y="581" width="0.5" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="1075.44" y="591.5" ></text>
</g>
<g >
<title>AllocSetReset (3,614,644,409 samples, 0.04%)</title><rect x="225.6" y="501" width="0.5" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="228.64" y="511.5" ></text>
</g>
<g >
<title>select_task_rq (26,454,569,301 samples, 0.28%)</title><rect x="202.3" y="325" width="3.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="205.33" y="335.5" ></text>
</g>
<g >
<title>do_syscall_64 (62,866,301,836 samples, 0.66%)</title><rect x="179.2" y="469" width="7.8" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="182.17" y="479.5" ></text>
</g>
<g >
<title>RelationBuildPublicationDesc (3,739,553,397 samples, 0.04%)</title><rect x="419.8" y="421" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="422.81" y="431.5" ></text>
</g>
<g >
<title>ExecTypeFromTLInternal (35,394,921,913 samples, 0.37%)</title><rect x="441.5" y="389" width="4.4" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="444.52" y="399.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (999,843,566 samples, 0.01%)</title><rect x="234.5" y="501" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="237.49" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (914,064,862 samples, 0.01%)</title><rect x="816.4" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="819.40" y="431.5" ></text>
</g>
<g >
<title>exec_simple_query (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="629" width="2.3" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="126.99" y="639.5" ></text>
</g>
<g >
<title>BufferAlloc (10,754,855,326 samples, 0.11%)</title><rect x="354.5" y="277" width="1.4" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="357.52" y="287.5" ></text>
</g>
<g >
<title>recv@plt (1,023,309,623 samples, 0.01%)</title><rect x="187.1" y="501" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="190.12" y="511.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,094,299,740 samples, 0.01%)</title><rect x="341.4" y="181" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="344.41" y="191.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,003,780,765 samples, 0.01%)</title><rect x="933.9" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="936.90" y="351.5" ></text>
</g>
<g >
<title>standard_planner (3,069,363,779 samples, 0.03%)</title><rect x="125.9" y="565" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="128.92" y="575.5" ></text>
</g>
<g >
<title>base_yylex (92,080,102,714 samples, 0.97%)</title><rect x="1102.5" y="741" width="11.5" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1105.53" y="751.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,898,673,539 samples, 0.03%)</title><rect x="873.6" y="517" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="876.58" y="527.5" ></text>
</g>
<g >
<title>_bt_getroot (11,333,614,982 samples, 0.12%)</title><rect x="85.0" y="309" width="1.4" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="87.98" y="319.5" ></text>
</g>
<g >
<title>do_futex (2,329,035,961 samples, 0.02%)</title><rect x="375.9" y="213" width="0.3" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="378.87" y="223.5" ></text>
</g>
<g >
<title>update_load_avg (12,288,208,411 samples, 0.13%)</title><rect x="172.8" y="277" width="1.5" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="175.76" y="287.5" ></text>
</g>
<g >
<title>palloc0 (5,010,422,202 samples, 0.05%)</title><rect x="1119.9" y="725" width="0.7" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1122.95" y="735.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (2,178,414,507 samples, 0.02%)</title><rect x="354.1" y="293" width="0.3" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="357.15" y="303.5" ></text>
</g>
<g >
<title>pg_detoast_datum_copy (8,004,707,541 samples, 0.08%)</title><rect x="1007.1" y="277" width="1.0" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1010.06" y="287.5" ></text>
</g>
<g >
<title>ReleaseSysCache (932,154,849 samples, 0.01%)</title><rect x="1029.8" y="261" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1032.81" y="271.5" ></text>
</g>
<g >
<title>security_socket_getpeersec_dgram (2,747,604,259 samples, 0.03%)</title><rect x="195.1" y="405" width="0.3" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="198.11" y="415.5" ></text>
</g>
<g >
<title>create_modifytable_plan (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="549" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="89.91" y="559.5" ></text>
</g>
<g >
<title>make_pathkeys_for_sortclauses (1,364,248,292 samples, 0.01%)</title><rect x="1160.6" y="757" width="0.2" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1163.60" y="767.5" ></text>
</g>
<g >
<title>palloc (3,724,857,367 samples, 0.04%)</title><rect x="873.5" y="533" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="876.48" y="543.5" ></text>
</g>
<g >
<title>create_plan (86,512,391,805 samples, 0.91%)</title><rect x="883.5" y="517" width="10.7" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="886.50" y="527.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,048,741,102 samples, 0.02%)</title><rect x="1054.5" y="421" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1057.45" y="431.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (912,972,750 samples, 0.01%)</title><rect x="948.8" y="325" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="951.83" y="335.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (922,347,502 samples, 0.01%)</title><rect x="223.8" y="501" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="226.75" y="511.5" ></text>
</g>
<g >
<title>palloc (1,332,437,719 samples, 0.01%)</title><rect x="1018.0" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1021.01" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,363,388,156 samples, 0.02%)</title><rect x="914.4" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="917.45" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,117,678,619 samples, 0.01%)</title><rect x="275.2" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="278.21" y="367.5" ></text>
</g>
<g >
<title>table_tuple_fetch_row_version (41,520,936,143 samples, 0.44%)</title><rect x="404.7" y="437" width="5.2" height="15.0" fill="rgb(226,99,23)" rx="2" ry="2" />
<text  x="407.74" y="447.5" ></text>
</g>
<g >
<title>palloc (914,064,862 samples, 0.01%)</title><rect x="816.4" y="437" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="819.40" y="447.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (952,534,523 samples, 0.01%)</title><rect x="831.5" y="309" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="834.50" y="319.5" ></text>
</g>
<g >
<title>InjectionPointRun (4,416,773,910 samples, 0.05%)</title><rect x="352.3" y="373" width="0.5" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="355.27" y="383.5" ></text>
</g>
<g >
<title>PortalRunMulti (1,256,272,098 samples, 0.01%)</title><rect x="83.9" y="757" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="86.95" y="767.5" ></text>
</g>
<g >
<title>bms_add_member (4,149,703,629 samples, 0.04%)</title><rect x="878.1" y="485" width="0.5" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="881.11" y="495.5" ></text>
</g>
<g >
<title>list_nth (909,250,796 samples, 0.01%)</title><rect x="458.0" y="453" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="461.02" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,093,058,766 samples, 0.01%)</title><rect x="416.2" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="419.22" y="431.5" ></text>
</g>
<g >
<title>bms_copy (6,491,298,743 samples, 0.07%)</title><rect x="356.9" y="357" width="0.8" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="359.88" y="367.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,247,901,778 samples, 0.01%)</title><rect x="845.6" y="373" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="848.58" y="383.5" ></text>
</g>
<g >
<title>migrate_task_rq_fair (4,383,251,789 samples, 0.05%)</title><rect x="205.9" y="309" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="208.89" y="319.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,278,711,375 samples, 0.02%)</title><rect x="1030.2" y="213" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1033.23" y="223.5" ></text>
</g>
<g >
<title>scanner_init (1,517,405,894 samples, 0.02%)</title><rect x="1177.4" y="757" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1180.43" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (1,610,295,376 samples, 0.02%)</title><rect x="1072.2" y="549" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1075.19" y="559.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,613,819,239 samples, 0.03%)</title><rect x="369.6" y="261" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="372.59" y="271.5" ></text>
</g>
<g >
<title>AllocSetAllocFromNewBlock (6,531,218,870 samples, 0.07%)</title><rect x="912.6" y="421" width="0.8" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="915.59" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32 (1,309,196,122 samples, 0.01%)</title><rect x="340.5" y="181" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="343.47" y="191.5" ></text>
</g>
<g >
<title>RegisterSnapshot (3,811,666,035 samples, 0.04%)</title><rect x="236.0" y="517" width="0.5" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="239.05" y="527.5" ></text>
</g>
<g >
<title>try_to_block_task.constprop.0.isra.0 (19,891,671,672 samples, 0.21%)</title><rect x="486.3" y="277" width="2.5" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="489.30" y="287.5" ></text>
</g>
<g >
<title>palloc0 (2,082,077,187 samples, 0.02%)</title><rect x="837.9" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="840.90" y="367.5" ></text>
</g>
<g >
<title>PageGetItem (837,127,501 samples, 0.01%)</title><rect x="335.5" y="197" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="338.45" y="207.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (62,401,311,695 samples, 0.66%)</title><rect x="482.9" y="421" width="7.8" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="485.92" y="431.5" ></text>
</g>
<g >
<title>ExecStorePinnedBufferHeapTuple (2,486,842,206 samples, 0.03%)</title><rect x="405.0" y="405" width="0.3" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="408.02" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerReleaseInternal (14,132,798,151 samples, 0.15%)</title><rect x="226.8" y="549" width="1.8" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="229.82" y="559.5" ></text>
</g>
<g >
<title>ExecutePlan (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="549" width="0.3" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1161.90" y="559.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,790,009,287 samples, 0.03%)</title><rect x="309.2" y="149" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="312.25" y="159.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (913,944,548 samples, 0.01%)</title><rect x="1051.7" y="341" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1054.75" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (899,317,629 samples, 0.01%)</title><rect x="984.4" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="987.42" y="367.5" ></text>
</g>
<g >
<title>ExecUpdatePrologue (835,890,574 samples, 0.01%)</title><rect x="46.6" y="757" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="49.59" y="767.5" ></text>
</g>
<g >
<title>LWLockRelease (1,261,559,876 samples, 0.01%)</title><rect x="97.7" y="181" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="100.69" y="191.5" ></text>
</g>
<g >
<title>futex_wait (1,048,376,231 samples, 0.01%)</title><rect x="475.6" y="357" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="478.58" y="367.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (938,152,268 samples, 0.01%)</title><rect x="1019.8" y="293" width="0.1" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="1022.83" y="303.5" ></text>
</g>
<g >
<title>list_make1_impl (1,983,990,759 samples, 0.02%)</title><rect x="1047.4" y="501" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1050.37" y="511.5" ></text>
</g>
<g >
<title>futex_wake (928,684,905 samples, 0.01%)</title><rect x="370.2" y="133" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="373.23" y="143.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,267,853,986 samples, 0.01%)</title><rect x="822.7" y="357" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="825.66" y="367.5" ></text>
</g>
<g >
<title>GrantLockLocal (945,187,931 samples, 0.01%)</title><rect x="822.5" y="373" width="0.2" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="825.53" y="383.5" ></text>
</g>
<g >
<title>new_list (1,856,219,732 samples, 0.02%)</title><rect x="401.3" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="404.35" y="367.5" ></text>
</g>
<g >
<title>check_heap_object (3,618,607,274 samples, 0.04%)</title><rect x="186.4" y="293" width="0.4" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="189.37" y="303.5" ></text>
</g>
<g >
<title>set_task_cpu (6,622,408,428 samples, 0.07%)</title><rect x="205.6" y="325" width="0.8" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="208.61" y="335.5" ></text>
</g>
<g >
<title>palloc0 (1,829,808,486 samples, 0.02%)</title><rect x="416.1" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="419.13" y="447.5" ></text>
</g>
<g >
<title>check_functions_in_node (8,000,543,338 samples, 0.08%)</title><rect x="959.1" y="341" width="1.0" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="962.13" y="351.5" ></text>
</g>
<g >
<title>TransactionGroupUpdateXidStatus (4,292,836,389 samples, 0.05%)</title><rect x="478.6" y="453" width="0.5" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="481.60" y="463.5" ></text>
</g>
<g >
<title>RelationClose (1,159,312,440 samples, 0.01%)</title><rect x="922.9" y="437" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="925.94" y="447.5" ></text>
</g>
<g >
<title>bms_is_valid_set (1,267,169,166 samples, 0.01%)</title><rect x="357.0" y="341" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="360.03" y="351.5" ></text>
</g>
<g >
<title>AllocSetDelete (15,470,201,331 samples, 0.16%)</title><rect x="134.1" y="533" width="1.9" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="137.07" y="543.5" ></text>
</g>
<g >
<title>hash_bytes (2,561,633,041 samples, 0.03%)</title><rect x="863.5" y="421" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="866.50" y="431.5" ></text>
</g>
<g >
<title>exit_to_user_mode_loop (3,185,181,828 samples, 0.03%)</title><rect x="490.3" y="389" width="0.4" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="493.26" y="399.5" ></text>
</g>
<g >
<title>AfterTriggerEndQuery (1,338,355,706 samples, 0.01%)</title><rect x="266.1" y="501" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="269.05" y="511.5" ></text>
</g>
<g >
<title>lcons (6,451,323,691 samples, 0.07%)</title><rect x="1114.7" y="725" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1117.74" y="735.5" ></text>
</g>
<g >
<title>get_hash_value (3,007,301,300 samples, 0.03%)</title><rect x="523.2" y="437" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="526.17" y="447.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,195,214,777 samples, 0.01%)</title><rect x="1006.5" y="149" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1009.53" y="159.5" ></text>
</g>
<g >
<title>add_path (5,802,954,037 samples, 0.06%)</title><rect x="987.5" y="389" width="0.7" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="990.48" y="399.5" ></text>
</g>
<g >
<title>CreateExprContextInternal (10,630,311,151 samples, 0.11%)</title><rect x="422.1" y="389" width="1.3" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="425.11" y="399.5" ></text>
</g>
<g >
<title>create_indexscan_plan (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="437" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="128.92" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,219,066,873 samples, 0.01%)</title><rect x="880.2" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="883.22" y="431.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (4,182,235,203 samples, 0.04%)</title><rect x="1039.0" y="309" width="0.5" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1041.96" y="319.5" ></text>
</g>
<g >
<title>exec_rt_fetch (814,553,903 samples, 0.01%)</title><rect x="1135.5" y="757" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="1138.50" y="767.5" ></text>
</g>
<g >
<title>bsearch (13,131,130,376 samples, 0.14%)</title><rect x="287.0" y="261" width="1.6" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="289.95" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,661,829,016 samples, 0.03%)</title><rect x="352.5" y="325" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="355.49" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,049,189,768 samples, 0.02%)</title><rect x="403.8" y="373" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="406.84" y="383.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,164,410,034 samples, 0.01%)</title><rect x="92.1" y="757" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="95.07" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,302,146,207 samples, 0.01%)</title><rect x="922.1" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="925.06" y="415.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (2,307,472,196 samples, 0.02%)</title><rect x="398.2" y="309" width="0.3" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="401.17" y="319.5" ></text>
</g>
<g >
<title>ExecConstraints (8,853,176,637 samples, 0.09%)</title><rect x="349.2" y="405" width="1.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="352.21" y="415.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="341" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="89.91" y="351.5" ></text>
</g>
<g >
<title>PageGetItem (1,194,029,805 samples, 0.01%)</title><rect x="333.5" y="213" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="336.52" y="223.5" ></text>
</g>
<g >
<title>heap_getattr (48,038,050,941 samples, 0.51%)</title><rect x="1001.0" y="245" width="6.0" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1004.03" y="255.5" ></text>
</g>
<g >
<title>pq_beginmessage (2,301,711,318 samples, 0.02%)</title><rect x="189.1" y="581" width="0.2" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="192.06" y="591.5" ></text>
</g>
<g >
<title>hash_bytes (2,687,570,621 samples, 0.03%)</title><rect x="523.2" y="405" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="526.21" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,413,241,866 samples, 0.01%)</title><rect x="348.3" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="351.35" y="351.5" ></text>
</g>
<g >
<title>PushActiveSnapshot (2,478,864,355 samples, 0.03%)</title><rect x="461.9" y="533" width="0.3" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="464.88" y="543.5" ></text>
</g>
<g >
<title>folio_mark_dirty (1,209,835,924 samples, 0.01%)</title><rect x="505.9" y="341" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="508.89" y="351.5" ></text>
</g>
<g >
<title>get_mergejoin_opfamilies (19,810,676,201 samples, 0.21%)</title><rect x="960.8" y="389" width="2.5" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="963.82" y="399.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (921,568,979 samples, 0.01%)</title><rect x="468.4" y="437" width="0.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="471.38" y="447.5" ></text>
</g>
<g >
<title>hash_search (2,818,225,664 samples, 0.03%)</title><rect x="1015.7" y="293" width="0.3" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1018.67" y="303.5" ></text>
</g>
<g >
<title>PageAddItemExtended (24,931,591,439 samples, 0.26%)</title><rect x="379.3" y="341" width="3.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="382.28" y="351.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,422,981,637 samples, 0.05%)</title><rect x="397.2" y="341" width="0.5" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="400.19" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,106,038,384 samples, 0.02%)</title><rect x="849.8" y="389" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="852.75" y="399.5" ></text>
</g>
<g >
<title>palloc (2,361,363,173 samples, 0.02%)</title><rect x="951.0" y="405" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="954.00" y="415.5" ></text>
</g>
<g >
<title>ExecConstraints (1,409,579,014 samples, 0.01%)</title><rect x="42.5" y="757" width="0.2" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="45.48" y="767.5" ></text>
</g>
<g >
<title>CopySnapshot (4,300,308,435 samples, 0.05%)</title><rect x="463.6" y="549" width="0.6" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="466.62" y="559.5" ></text>
</g>
<g >
<title>RelationClose (1,697,762,949 samples, 0.02%)</title><rect x="240.8" y="405" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="243.82" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,174,301,782 samples, 0.01%)</title><rect x="927.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="930.67" y="399.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (968,027,246 samples, 0.01%)</title><rect x="869.2" y="405" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="872.15" y="415.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,130,840,438 samples, 0.01%)</title><rect x="1033.7" y="117" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1036.65" y="127.5" ></text>
</g>
<g >
<title>TupleDescAttr (1,470,001,716 samples, 0.02%)</title><rect x="117.1" y="741" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="120.13" y="751.5" ></text>
</g>
<g >
<title>palloc (1,209,245,753 samples, 0.01%)</title><rect x="896.8" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="899.79" y="431.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (939,751,623 samples, 0.01%)</title><rect x="440.0" y="341" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="442.99" y="351.5" ></text>
</g>
<g >
<title>palloc0 (3,728,173,317 samples, 0.04%)</title><rect x="1115.6" y="693" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1118.64" y="703.5" ></text>
</g>
<g >
<title>standard_ExecutorStart (395,063,183,486 samples, 4.16%)</title><rect x="411.5" y="517" width="49.1" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="414.50" y="527.5" >stan..</text>
</g>
<g >
<title>transformExprRecurse (56,537,866,421 samples, 0.60%)</title><rect x="836.1" y="421" width="7.0" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="839.06" y="431.5" ></text>
</g>
<g >
<title>ExecScanExtended (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="453" width="6.8" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="98.68" y="463.5" ></text>
</g>
<g >
<title>bsearch@plt (1,065,991,332 samples, 0.01%)</title><rect x="288.6" y="261" width="0.1" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="291.58" y="271.5" ></text>
</g>
<g >
<title>folio_unlock (3,578,217,057 samples, 0.04%)</title><rect x="506.0" y="341" width="0.5" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="509.04" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,612,830,565 samples, 0.03%)</title><rect x="906.5" y="485" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="909.49" y="495.5" ></text>
</g>
<g >
<title>palloc (1,581,726,438 samples, 0.02%)</title><rect x="341.9" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="344.92" y="255.5" ></text>
</g>
<g >
<title>pgstat_report_xact_timestamp (2,038,010,574 samples, 0.02%)</title><rect x="530.3" y="517" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="533.29" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,004,473,658 samples, 0.01%)</title><rect x="816.3" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="819.27" y="431.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,332,299,509 samples, 0.01%)</title><rect x="353.5" y="293" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="356.55" y="303.5" ></text>
</g>
<g >
<title>find_oper_cache_entry (14,064,839,130 samples, 0.15%)</title><rect x="838.7" y="357" width="1.7" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="841.69" y="367.5" ></text>
</g>
<g >
<title>pg_strtoint32_safe (2,417,750,261 samples, 0.03%)</title><rect x="1113.3" y="693" width="0.3" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="1116.33" y="703.5" ></text>
</g>
<g >
<title>rewriteTargetListIU (1,034,834,286 samples, 0.01%)</title><rect x="1177.1" y="757" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="1180.14" y="767.5" ></text>
</g>
<g >
<title>vector8_broadcast (1,934,241,249 samples, 0.02%)</title><rect x="1081.4" y="501" width="0.2" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1084.37" y="511.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,362,674,292 samples, 0.05%)</title><rect x="807.7" y="341" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="810.73" y="351.5" ></text>
</g>
<g >
<title>LWLockRelease (2,909,023,294 samples, 0.03%)</title><rect x="370.1" y="277" width="0.3" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="373.08" y="287.5" ></text>
</g>
<g >
<title>ExecReadyExpr (2,877,380,705 samples, 0.03%)</title><rect x="440.2" y="405" width="0.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="443.24" y="415.5" ></text>
</g>
<g >
<title>LWLockWaitForVar (9,420,820,087 samples, 0.10%)</title><rect x="495.9" y="469" width="1.2" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="498.94" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,279,904,960 samples, 0.01%)</title><rect x="936.8" y="213" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="939.82" y="223.5" ></text>
</g>
<g >
<title>index_getattr (1,111,430,735 samples, 0.01%)</title><rect x="1151.3" y="757" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="1154.34" y="767.5" ></text>
</g>
<g >
<title>VirtualXactLockTableCleanup (4,650,963,938 samples, 0.05%)</title><rect x="526.7" y="453" width="0.6" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="529.75" y="463.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,021,959,820 samples, 0.01%)</title><rect x="822.9" y="357" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="825.89" y="367.5" ></text>
</g>
<g >
<title>make_ands_implicit (3,494,019,306 samples, 0.04%)</title><rect x="1056.8" y="469" width="0.5" height="15.0" fill="rgb(236,145,34)" rx="2" ry="2" />
<text  x="1059.83" y="479.5" ></text>
</g>
<g >
<title>sched_clock_cpu (937,900,485 samples, 0.01%)</title><rect x="174.5" y="325" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="177.52" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (917,446,466 samples, 0.01%)</title><rect x="1047.5" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1050.50" y="463.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (4,513,560,012 samples, 0.05%)</title><rect x="367.7" y="245" width="0.5" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="370.68" y="255.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,721,881,167 samples, 0.03%)</title><rect x="346.8" y="341" width="0.4" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="349.84" y="351.5" ></text>
</g>
<g >
<title>ReleaseBuffer (4,102,407,988 samples, 0.04%)</title><rect x="326.4" y="213" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="329.39" y="223.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,008,357,478 samples, 0.01%)</title><rect x="302.5" y="117" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="305.48" y="127.5" ></text>
</g>
<g >
<title>new_list (1,352,638,368 samples, 0.01%)</title><rect x="919.7" y="453" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="922.66" y="463.5" ></text>
</g>
<g >
<title>printtup_destroy (2,315,609,137 samples, 0.02%)</title><rect x="1072.1" y="581" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="1075.14" y="591.5" ></text>
</g>
<g >
<title>list_nth_cell (926,325,958 samples, 0.01%)</title><rect x="1020.3" y="309" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1023.28" y="319.5" ></text>
</g>
<g >
<title>tag_hash (2,616,226,937 samples, 0.03%)</title><rect x="814.5" y="405" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="817.51" y="415.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,119,855,737 samples, 0.03%)</title><rect x="474.3" y="485" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="477.34" y="495.5" ></text>
</g>
<g >
<title>simplify_function (12,681,761,281 samples, 0.13%)</title><rect x="1055.2" y="437" width="1.6" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1058.25" y="447.5" ></text>
</g>
<g >
<title>futex_wait (3,785,267,112 samples, 0.04%)</title><rect x="366.7" y="181" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="369.70" y="191.5" ></text>
</g>
<g >
<title>AllocSetAlloc (848,948,678 samples, 0.01%)</title><rect x="973.7" y="261" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="976.70" y="271.5" ></text>
</g>
<g >
<title>selinux_socket_sendmsg (6,309,571,448 samples, 0.07%)</title><rect x="193.6" y="405" width="0.7" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="196.57" y="415.5" ></text>
</g>
<g >
<title>bms_copy (2,401,726,467 samples, 0.03%)</title><rect x="345.6" y="373" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="348.60" y="383.5" ></text>
</g>
<g >
<title>ExecutePlan (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="581" width="6.9" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="98.68" y="591.5" ></text>
</g>
<g >
<title>__wake_up_common (61,850,083,631 samples, 0.65%)</title><rect x="200.7" y="373" width="7.7" height="15.0" fill="rgb(248,197,47)" rx="2" ry="2" />
<text  x="203.73" y="383.5" ></text>
</g>
<g >
<title>hash_search (2,632,524,682 samples, 0.03%)</title><rect x="1067.9" y="437" width="0.3" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1070.89" y="447.5" ></text>
</g>
<g >
<title>exprType (1,076,569,160 samples, 0.01%)</title><rect x="277.7" y="405" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="280.66" y="415.5" ></text>
</g>
<g >
<title>list_nth (958,926,209 samples, 0.01%)</title><rect x="1065.9" y="485" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1068.89" y="495.5" ></text>
</g>
<g >
<title>heapam_estimate_rel_size (1,417,558,013 samples, 0.01%)</title><rect x="1150.3" y="757" width="0.2" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1153.31" y="767.5" ></text>
</g>
<g >
<title>printtup_create_DR (4,271,476,462 samples, 0.04%)</title><rect x="212.8" y="565" width="0.5" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="215.81" y="575.5" ></text>
</g>
<g >
<title>tag_hash (2,809,948,076 samples, 0.03%)</title><rect x="300.2" y="101" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="303.17" y="111.5" ></text>
</g>
<g >
<title>CreateQueryDesc (8,327,330,684 samples, 0.09%)</title><rect x="235.8" y="533" width="1.0" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="238.82" y="543.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,107,763,088 samples, 0.03%)</title><rect x="1145.3" y="757" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1148.32" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,746,546,703 samples, 0.02%)</title><rect x="878.4" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="881.39" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache3 (6,082,363,909 samples, 0.06%)</title><rect x="1040.5" y="341" width="0.8" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1043.53" y="351.5" ></text>
</g>
<g >
<title>fix_scan_expr (31,198,664,176 samples, 0.33%)</title><rect x="900.3" y="469" width="3.9" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="903.29" y="479.5" ></text>
</g>
<g >
<title>ExecStoreBufferHeapTuple (7,342,803,594 samples, 0.08%)</title><rect x="297.3" y="261" width="0.9" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="300.27" y="271.5" ></text>
</g>
<g >
<title>bms_copy (2,161,331,631 samples, 0.02%)</title><rect x="957.1" y="437" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="960.13" y="447.5" ></text>
</g>
<g >
<title>SyncRepWaitForLSN (1,192,040,114 samples, 0.01%)</title><rect x="106.9" y="757" width="0.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="109.90" y="767.5" ></text>
</g>
<g >
<title>fmgr_isbuiltin (1,660,227,967 samples, 0.02%)</title><rect x="428.1" y="293" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="431.10" y="303.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,939,871,897 samples, 0.02%)</title><rect x="519.2" y="421" width="0.3" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="522.23" y="431.5" ></text>
</g>
<g >
<title>AtEOXact_Enum (1,240,290,169 samples, 0.01%)</title><rect x="31.0" y="757" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="34.04" y="767.5" ></text>
</g>
<g >
<title>palloc (1,264,827,881 samples, 0.01%)</title><rect x="401.4" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="404.42" y="351.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,200,821,678 samples, 0.04%)</title><rect x="1067.7" y="453" width="0.5" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="1070.69" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,110,760,179 samples, 0.01%)</title><rect x="514.8" y="469" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="517.82" y="479.5" ></text>
</g>
<g >
<title>hash_search (6,335,326,853 samples, 0.07%)</title><rect x="947.3" y="325" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="950.28" y="335.5" ></text>
</g>
<g >
<title>get_hash_value (3,106,881,449 samples, 0.03%)</title><rect x="300.1" y="117" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="303.13" y="127.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,095,078,867 samples, 0.02%)</title><rect x="1034.4" y="213" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1037.43" y="223.5" ></text>
</g>
<g >
<title>list_copy (964,207,431 samples, 0.01%)</title><rect x="1155.5" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="1158.52" y="767.5" ></text>
</g>
<g >
<title>strlen@plt (896,195,308 samples, 0.01%)</title><rect x="853.9" y="357" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="856.91" y="367.5" ></text>
</g>
<g >
<title>_bt_check_natts (15,783,257,821 samples, 0.17%)</title><rect x="333.8" y="213" width="2.0" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="336.81" y="223.5" ></text>
</g>
<g >
<title>coerce_to_boolean (10,002,819,333 samples, 0.11%)</title><rect x="844.5" y="469" width="1.3" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="847.52" y="479.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (4,064,551,160 samples, 0.04%)</title><rect x="827.3" y="309" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="830.30" y="319.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,060,594,112 samples, 0.01%)</title><rect x="1038.0" y="293" width="0.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1041.03" y="303.5" ></text>
</g>
<g >
<title>palloc (2,763,918,956 samples, 0.03%)</title><rect x="873.0" y="501" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="875.98" y="511.5" ></text>
</g>
<g >
<title>makeIndexInfo (9,374,020,907 samples, 0.10%)</title><rect x="400.0" y="373" width="1.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="403.02" y="383.5" ></text>
</g>
<g >
<title>int4pl (1,434,568,464 samples, 0.02%)</title><rect x="290.5" y="277" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="293.48" y="287.5" ></text>
</g>
<g >
<title>distribute_quals_to_rels (946,907,650 samples, 0.01%)</title><rect x="1132.6" y="757" width="0.1" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="1135.62" y="767.5" ></text>
</g>
<g >
<title>btbeginscan (20,683,398,458 samples, 0.22%)</title><rect x="293.0" y="293" width="2.6" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="296.05" y="303.5" ></text>
</g>
<g >
<title>name_matches_visible_ENR (886,800,917 samples, 0.01%)</title><rect x="1162.2" y="757" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="1165.20" y="767.5" ></text>
</g>
<g >
<title>new_list (4,405,067,043 samples, 0.05%)</title><rect x="1164.1" y="757" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1167.09" y="767.5" ></text>
</g>
<g >
<title>rseq_ip_fixup (1,462,878,688 samples, 0.02%)</title><rect x="490.4" y="357" width="0.2" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="493.37" y="367.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (1,575,847,117 samples, 0.02%)</title><rect x="478.1" y="389" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="481.12" y="399.5" ></text>
</g>
<g >
<title>AtEOXact_RelationMap (2,440,207,599 samples, 0.03%)</title><rect x="471.8" y="517" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="474.77" y="527.5" ></text>
</g>
<g >
<title>pgstat_count_io_op_time (7,459,781,144 samples, 0.08%)</title><rect x="509.1" y="469" width="0.9" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="512.06" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,697,802,365 samples, 0.02%)</title><rect x="906.6" y="469" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="909.60" y="479.5" ></text>
</g>
<g >
<title>get_op_opfamily_properties (8,482,297,370 samples, 0.09%)</title><rect x="434.7" y="405" width="1.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="437.73" y="415.5" ></text>
</g>
<g >
<title>__strlen_avx2 (967,114,867 samples, 0.01%)</title><rect x="229.2" y="533" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="232.19" y="543.5" ></text>
</g>
<g >
<title>avc_has_perm (2,162,347,210 samples, 0.02%)</title><rect x="180.7" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="183.65" y="383.5" ></text>
</g>
<g >
<title>_mdnblocks (8,198,018,510 samples, 0.09%)</title><rect x="932.0" y="357" width="1.0" height="15.0" fill="rgb(217,58,14)" rx="2" ry="2" />
<text  x="935.00" y="367.5" ></text>
</g>
<g >
<title>hash_search (8,222,046,896 samples, 0.09%)</title><rect x="823.7" y="373" width="1.0" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="826.70" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (887,513,278 samples, 0.01%)</title><rect x="277.2" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="280.19" y="367.5" ></text>
</g>
<g >
<title>list_free_private (1,916,159,398 samples, 0.02%)</title><rect x="992.5" y="325" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="995.45" y="335.5" ></text>
</g>
<g >
<title>MemoryContextCreate (856,357,065 samples, 0.01%)</title><rect x="347.1" y="325" width="0.1" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="350.07" y="335.5" ></text>
</g>
<g >
<title>_int_malloc (5,603,661,356 samples, 0.06%)</title><rect x="1049.9" y="421" width="0.7" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="1052.86" y="431.5" ></text>
</g>
<g >
<title>expr_setup_walker (5,785,741,054 samples, 0.06%)</title><rect x="425.9" y="277" width="0.7" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="428.92" y="287.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (4,327,253,586 samples, 0.05%)</title><rect x="322.6" y="181" width="0.6" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="325.64" y="191.5" ></text>
</g>
<g >
<title>uint32_hash (1,172,007,513 samples, 0.01%)</title><rect x="943.6" y="341" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="946.65" y="351.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (12,859,390,985 samples, 0.14%)</title><rect x="1074.7" y="501" width="1.6" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="1077.68" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,757,883,085 samples, 0.02%)</title><rect x="813.6" y="405" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="816.64" y="415.5" ></text>
</g>
<g >
<title>ExecInitExprRec (21,070,306,462 samples, 0.22%)</title><rect x="426.7" y="357" width="2.6" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="429.66" y="367.5" ></text>
</g>
<g >
<title>_find_next_bit (822,281,814 samples, 0.01%)</title><rect x="204.0" y="261" width="0.1" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="206.95" y="271.5" ></text>
</g>
<g >
<title>ReadBuffer (809,812,762 samples, 0.01%)</title><rect x="88.4" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="91.37" y="767.5" ></text>
</g>
<g >
<title>PostmasterMain (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="725" width="2.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="126.99" y="735.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,900,169,101 samples, 0.02%)</title><rect x="388.6" y="261" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="391.57" y="271.5" ></text>
</g>
<g >
<title>palloc0 (1,824,960,161 samples, 0.02%)</title><rect x="1117.8" y="709" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1120.80" y="719.5" ></text>
</g>
<g >
<title>ExecIndexScan (1,472,358,513 samples, 0.02%)</title><rect x="43.4" y="757" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="46.44" y="767.5" ></text>
</g>
<g >
<title>sentinel_ok (300,405,911,690 samples, 3.16%)</title><rect x="758.9" y="533" width="37.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="761.92" y="543.5" >sen..</text>
</g>
<g >
<title>pg_plan_query (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="645" width="3.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="105.66" y="655.5" ></text>
</g>
<g >
<title>palloc0 (1,162,330,643 samples, 0.01%)</title><rect x="897.3" y="437" width="0.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="900.29" y="447.5" ></text>
</g>
<g >
<title>exprType (1,426,294,868 samples, 0.02%)</title><rect x="844.6" y="453" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="847.59" y="463.5" ></text>
</g>
<g >
<title>do_futex (3,984,167,261 samples, 0.04%)</title><rect x="366.7" y="197" width="0.5" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="369.67" y="207.5" ></text>
</g>
<g >
<title>ksys_lseek (6,041,512,571 samples, 0.06%)</title><rect x="937.3" y="181" width="0.8" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="940.34" y="191.5" ></text>
</g>
<g >
<title>__wake_up_sync_key (3,265,681,155 samples, 0.03%)</title><rect x="183.7" y="293" width="0.4" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="186.67" y="303.5" ></text>
</g>
<g >
<title>DatumGetInt32 (3,059,403,399 samples, 0.03%)</title><rect x="40.5" y="757" width="0.3" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="43.47" y="767.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (13,306,790,772 samples, 0.14%)</title><rect x="959.0" y="357" width="1.7" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="962.01" y="367.5" ></text>
</g>
<g >
<title>futex_wait (1,050,059,736 samples, 0.01%)</title><rect x="299.0" y="117" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="302.03" y="127.5" ></text>
</g>
<g >
<title>FullTransactionIdNewer (1,033,225,903 samples, 0.01%)</title><rect x="222.4" y="549" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="225.42" y="559.5" ></text>
</g>
<g >
<title>BufferGetPage (1,204,605,462 samples, 0.01%)</title><rect x="338.6" y="229" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="341.57" y="239.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (898,703,936 samples, 0.01%)</title><rect x="128.0" y="229" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="130.99" y="239.5" ></text>
</g>
<g >
<title>do_epoll_wait (146,468,337,264 samples, 1.54%)</title><rect x="156.9" y="421" width="18.2" height="15.0" fill="rgb(211,31,7)" rx="2" ry="2" />
<text  x="159.93" y="431.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,325,157,373 samples, 0.01%)</title><rect x="963.9" y="325" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="966.90" y="335.5" ></text>
</g>
<g >
<title>ExecutorRun (1,166,411,855,152 samples, 12.28%)</title><rect x="266.3" y="533" width="144.9" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="269.31" y="543.5" >ExecutorRun</text>
</g>
<g >
<title>postmaster_child_launch (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="645" width="950.7" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="134.98" y="655.5" >postmaster_child_launch</text>
</g>
<g >
<title>SearchCatCache4 (8,670,698,731 samples, 0.09%)</title><rect x="1008.6" y="261" width="1.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1011.62" y="271.5" ></text>
</g>
<g >
<title>tts_buffer_heap_getsomeattrs (7,418,342,014 samples, 0.08%)</title><rect x="290.9" y="245" width="0.9" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="293.88" y="255.5" ></text>
</g>
<g >
<title>PageIsNew (3,134,470,857 samples, 0.03%)</title><rect x="340.8" y="213" width="0.4" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="343.76" y="223.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,455,345,032 samples, 0.02%)</title><rect x="942.2" y="341" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="945.23" y="351.5" ></text>
</g>
<g >
<title>tts_buffer_heap_materialize (24,528,667,689 samples, 0.26%)</title><rect x="396.1" y="389" width="3.1" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="399.12" y="399.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,194,675,272 samples, 0.02%)</title><rect x="513.1" y="421" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="516.14" y="431.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,492,382,391 samples, 0.02%)</title><rect x="924.1" y="389" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="927.05" y="399.5" ></text>
</g>
<g >
<title>create_plan_recurse (84,160,387,966 samples, 0.89%)</title><rect x="883.8" y="501" width="10.4" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="886.79" y="511.5" ></text>
</g>
<g >
<title>palloc0 (2,431,075,053 samples, 0.03%)</title><rect x="398.9" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="401.86" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (839,199,135 samples, 0.01%)</title><rect x="966.1" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="969.13" y="335.5" ></text>
</g>
<g >
<title>fastgetattr (7,248,522,371 samples, 0.08%)</title><rect x="829.3" y="341" width="0.9" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="832.27" y="351.5" ></text>
</g>
<g >
<title>eval_const_expressions (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="501" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="129.16" y="511.5" ></text>
</g>
<g >
<title>pfree (3,696,271,707 samples, 0.04%)</title><rect x="254.6" y="469" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="257.57" y="479.5" ></text>
</g>
<g >
<title>AllocSetFree (1,064,710,461 samples, 0.01%)</title><rect x="993.0" y="341" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="995.96" y="351.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,766,702,342 samples, 0.02%)</title><rect x="373.6" y="245" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="376.58" y="255.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,832,871,227 samples, 0.02%)</title><rect x="967.4" y="309" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="970.35" y="319.5" ></text>
</g>
<g >
<title>base_yylex (998,853,590 samples, 0.01%)</title><rect x="1085.4" y="757" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1088.40" y="767.5" ></text>
</g>
<g >
<title>__x64_sys_futex (3,188,802,269 samples, 0.03%)</title><rect x="367.8" y="197" width="0.4" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="370.81" y="207.5" ></text>
</g>
<g >
<title>__libc_start_call_main (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="725" width="950.7" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="134.98" y="735.5" >__libc_start_call_main</text>
</g>
<g >
<title>SearchCatCache1 (3,603,320,101 samples, 0.04%)</title><rect x="838.2" y="341" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="841.24" y="351.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (2,487,488,076 samples, 0.03%)</title><rect x="322.3" y="181" width="0.3" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="325.33" y="191.5" ></text>
</g>
<g >
<title>selinux_file_permission (3,530,904,921 samples, 0.04%)</title><rect x="501.1" y="357" width="0.4" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="504.05" y="367.5" ></text>
</g>
<g >
<title>relation_open (15,964,847,091 samples, 0.17%)</title><rect x="1066.3" y="469" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1069.25" y="479.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (3,139,010,565 samples, 0.03%)</title><rect x="262.9" y="389" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="265.88" y="399.5" ></text>
</g>
<g >
<title>pfree (1,547,167,052 samples, 0.02%)</title><rect x="250.8" y="421" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="253.84" y="431.5" ></text>
</g>
<g >
<title>IncrTupleDescRefCount (2,160,529,097 samples, 0.02%)</title><rect x="278.9" y="373" width="0.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="281.91" y="383.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,167,637,663 samples, 0.01%)</title><rect x="897.4" y="469" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="900.44" y="479.5" ></text>
</g>
<g >
<title>updateTargetListEntry (11,099,207,505 samples, 0.12%)</title><rect x="843.1" y="469" width="1.4" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="846.09" y="479.5" ></text>
</g>
<g >
<title>AllocSetReset (126,220,107,985 samples, 1.33%)</title><rect x="136.1" y="565" width="15.7" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="139.12" y="575.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (66,875,600,247 samples, 0.70%)</title><rect x="482.5" y="453" width="8.3" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="485.48" y="463.5" ></text>
</g>
<g >
<title>SimpleLruReadPage_ReadOnly (6,283,460,725 samples, 0.07%)</title><rect x="309.1" y="165" width="0.7" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="312.07" y="175.5" ></text>
</g>
<g >
<title>ExecInitModifyTable (338,942,103,873 samples, 3.57%)</title><rect x="417.2" y="469" width="42.1" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="420.17" y="479.5" >Exe..</text>
</g>
<g >
<title>FreeExecutorState (112,907,411,203 samples, 1.19%)</title><rect x="251.4" y="501" width="14.0" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="254.39" y="511.5" ></text>
</g>
<g >
<title>palloc0 (1,554,340,742 samples, 0.02%)</title><rect x="953.7" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="956.69" y="447.5" ></text>
</g>
<g >
<title>SocketBackend (294,841,434,282 samples, 3.10%)</title><rect x="152.0" y="581" width="36.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="154.99" y="591.5" >Soc..</text>
</g>
<g >
<title>tag_hash (4,432,906,850 samples, 0.05%)</title><rect x="1066.7" y="389" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1069.68" y="399.5" ></text>
</g>
<g >
<title>add_base_rels_to_query (189,116,927,141 samples, 1.99%)</title><rect x="926.4" y="453" width="23.5" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="929.39" y="463.5" >a..</text>
</g>
<g >
<title>CheckVarSlotCompatibility (1,451,288,861 samples, 0.02%)</title><rect x="286.5" y="277" width="0.2" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="289.50" y="287.5" ></text>
</g>
<g >
<title>hash_search (6,329,832,191 samples, 0.07%)</title><rect x="1066.4" y="405" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1069.44" y="415.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,131,799,659 samples, 0.01%)</title><rect x="127.9" y="197" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="130.85" y="207.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,156,795,536 samples, 0.01%)</title><rect x="971.2" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="974.23" y="351.5" ></text>
</g>
<g >
<title>ReserveXLogInsertLocation (6,126,611,530 samples, 0.06%)</title><rect x="387.7" y="309" width="0.8" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="390.69" y="319.5" ></text>
</g>
<g >
<title>[[vdso]] (972,743,365 samples, 0.01%)</title><rect x="1071.5" y="549" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1074.48" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (870,411,187 samples, 0.01%)</title><rect x="974.3" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="977.30" y="399.5" ></text>
</g>
<g >
<title>_bt_search (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="341" width="6.8" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="98.68" y="351.5" ></text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (9,070,562,566 samples, 0.10%)</title><rect x="972.8" y="389" width="1.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="975.81" y="399.5" ></text>
</g>
<g >
<title>ReadBufferExtended (11,333,614,982 samples, 0.12%)</title><rect x="85.0" y="261" width="1.4" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="87.98" y="271.5" ></text>
</g>
<g >
<title>SearchSysCache1 (6,089,029,668 samples, 0.06%)</title><rect x="865.5" y="485" width="0.8" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="868.53" y="495.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,447,792,970 samples, 0.02%)</title><rect x="1082.1" y="565" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1085.09" y="575.5" ></text>
</g>
<g >
<title>bms_copy (1,988,952,163 samples, 0.02%)</title><rect x="883.2" y="501" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="886.21" y="511.5" ></text>
</g>
<g >
<title>pull_up_subqueries_recurse (1,944,658,821 samples, 0.02%)</title><rect x="1070.3" y="469" width="0.2" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1073.27" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,614,057,768 samples, 0.02%)</title><rect x="815.5" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="818.48" y="415.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="373" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="128.92" y="383.5" ></text>
</g>
<g >
<title>BufTableLookup (7,404,723,289 samples, 0.08%)</title><rect x="300.5" y="133" width="0.9" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="303.51" y="143.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,618,061,962 samples, 0.02%)</title><rect x="971.9" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="974.88" y="367.5" ></text>
</g>
<g >
<title>ExecGetRangeTableRelation (1,002,913,354 samples, 0.01%)</title><rect x="447.5" y="405" width="0.1" height="15.0" fill="rgb(241,167,39)" rx="2" ry="2" />
<text  x="450.45" y="415.5" ></text>
</g>
<g >
<title>palloc0 (3,053,153,039 samples, 0.03%)</title><rect x="1116.2" y="709" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1119.15" y="719.5" ></text>
</g>
<g >
<title>palloc0 (3,472,106,790 samples, 0.04%)</title><rect x="812.5" y="469" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="815.50" y="479.5" ></text>
</g>
<g >
<title>list_last_cell (1,037,004,091 samples, 0.01%)</title><rect x="1154.9" y="741" width="0.1" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1157.86" y="751.5" ></text>
</g>
<g >
<title>check_enable_rls (8,587,690,560 samples, 0.09%)</title><rect x="865.2" y="501" width="1.1" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="868.22" y="511.5" ></text>
</g>
<g >
<title>create_modifytable_plan (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="517" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="128.92" y="527.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (1,390,143,234 samples, 0.01%)</title><rect x="1013.1" y="261" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1016.11" y="271.5" ></text>
</g>
<g >
<title>IsSystemClass (1,810,319,429 samples, 0.02%)</title><rect x="415.0" y="421" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="417.99" y="431.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,208,089,634 samples, 0.01%)</title><rect x="241.7" y="405" width="0.2" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="244.73" y="415.5" ></text>
</g>
<g >
<title>CopyIndexAttOptions (2,009,252,521 samples, 0.02%)</title><rect x="930.5" y="389" width="0.2" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="933.47" y="399.5" ></text>
</g>
<g >
<title>assign_collations_walker (1,058,664,766 samples, 0.01%)</title><rect x="1084.6" y="757" width="0.1" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="1087.59" y="767.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (81,716,458,196 samples, 0.86%)</title><rect x="255.1" y="469" width="10.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="258.10" y="479.5" ></text>
</g>
<g >
<title>ExecScan (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="437" width="0.3" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1161.90" y="447.5" ></text>
</g>
<g >
<title>palloc (2,509,121,541 samples, 0.03%)</title><rect x="1118.4" y="693" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1121.43" y="703.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (966,872,314 samples, 0.01%)</title><rect x="716.8" y="501" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="719.81" y="511.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,832,218,866 samples, 0.05%)</title><rect x="811.4" y="325" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="814.37" y="335.5" ></text>
</g>
<g >
<title>LockBuffer (2,646,796,789 samples, 0.03%)</title><rect x="337.8" y="197" width="0.4" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="340.84" y="207.5" ></text>
</g>
<g >
<title>CreateTemplateTupleDesc (2,261,196,558 samples, 0.02%)</title><rect x="441.8" y="373" width="0.3" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="444.82" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,087,369,933 samples, 0.02%)</title><rect x="234.1" y="485" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="237.14" y="495.5" ></text>
</g>
<g >
<title>pick_next_task_fair (3,322,208,524 samples, 0.03%)</title><rect x="484.0" y="261" width="0.4" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="487.03" y="271.5" ></text>
</g>
<g >
<title>pg_leftmost_one_pos32 (1,650,801,750 samples, 0.02%)</title><rect x="120.7" y="741" width="0.2" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="123.67" y="751.5" ></text>
</g>
<g >
<title>newNode (11,627,752,157 samples, 0.12%)</title><rect x="1162.6" y="757" width="1.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1165.62" y="767.5" ></text>
</g>
<g >
<title>palloc (865,702,070 samples, 0.01%)</title><rect x="837.7" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="840.68" y="351.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,209,642,699 samples, 0.01%)</title><rect x="948.1" y="325" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="951.10" y="335.5" ></text>
</g>
<g >
<title>RelationPutHeapTuple (959,210,580 samples, 0.01%)</title><rect x="90.5" y="757" width="0.1" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="93.52" y="767.5" ></text>
</g>
<g >
<title>makeAlias (4,631,557,917 samples, 0.05%)</title><rect x="818.8" y="453" width="0.6" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="821.81" y="463.5" ></text>
</g>
<g >
<title>verify_compact_attribute (2,117,867,440 samples, 0.02%)</title><rect x="347.8" y="373" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="350.84" y="383.5" ></text>
</g>
<g >
<title>murmurhash32 (1,082,425,095 samples, 0.01%)</title><rect x="431.5" y="261" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="434.50" y="271.5" ></text>
</g>
<g >
<title>RelationClose (1,897,239,250 samples, 0.02%)</title><rect x="866.5" y="485" width="0.3" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="869.53" y="495.5" ></text>
</g>
<g >
<title>ExecCloseIndices (15,549,290,634 samples, 0.16%)</title><rect x="238.1" y="469" width="1.9" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="241.05" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (9,225,058,986 samples, 0.10%)</title><rect x="937.0" y="213" width="1.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="939.98" y="223.5" ></text>
</g>
<g >
<title>disable_statement_timeout (1,198,082,511 samples, 0.01%)</title><rect x="802.1" y="565" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="805.11" y="575.5" ></text>
</g>
<g >
<title>palloc0 (6,855,577,264 samples, 0.07%)</title><rect x="458.2" y="437" width="0.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="461.23" y="447.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,897,497,546 samples, 0.03%)</title><rect x="1054.3" y="437" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1057.35" y="447.5" ></text>
</g>
<g >
<title>set_rel_width (20,608,748,073 samples, 0.22%)</title><rect x="1039.5" y="389" width="2.6" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="1042.54" y="399.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (876,239,873 samples, 0.01%)</title><rect x="238.5" y="421" width="0.2" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="241.54" y="431.5" ></text>
</g>
<g >
<title>DynaHashAlloc (1,605,699,438 samples, 0.02%)</title><rect x="1063.6" y="421" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1066.58" y="431.5" ></text>
</g>
<g >
<title>get_hash_value (2,847,980,411 samples, 0.03%)</title><rect x="354.6" y="245" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="357.60" y="255.5" ></text>
</g>
<g >
<title>PageGetItem (917,132,156 samples, 0.01%)</title><rect x="353.8" y="373" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="356.77" y="383.5" ></text>
</g>
<g >
<title>set_rel_pathlist (342,470,312,317 samples, 3.61%)</title><rect x="983.4" y="437" width="42.5" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="986.37" y="447.5" >set_..</text>
</g>
<g >
<title>heap_attisnull (1,001,961,943 samples, 0.01%)</title><rect x="930.8" y="389" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="933.80" y="399.5" ></text>
</g>
<g >
<title>LockBuffer (966,676,862 samples, 0.01%)</title><rect x="125.5" y="229" width="0.1" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="128.48" y="239.5" ></text>
</g>
<g >
<title>makeString (3,823,330,999 samples, 0.04%)</title><rect x="817.5" y="437" width="0.5" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="820.49" y="447.5" ></text>
</g>
<g >
<title>assign_expr_collations (26,612,162,991 samples, 0.28%)</title><rect x="808.8" y="421" width="3.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="811.78" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,689,054,999 samples, 0.02%)</title><rect x="464.4" y="533" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="467.42" y="543.5" ></text>
</g>
<g >
<title>hash_search (5,815,676,938 samples, 0.06%)</title><rect x="923.3" y="389" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="926.31" y="399.5" ></text>
</g>
<g >
<title>scanRTEForColumn (1,724,065,773 samples, 0.02%)</title><rect x="842.8" y="325" width="0.2" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="845.82" y="335.5" ></text>
</g>
<g >
<title>heapam_index_fetch_reset (3,009,410,946 samples, 0.03%)</title><rect x="284.3" y="293" width="0.4" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="287.29" y="303.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (2,207,564,684 samples, 0.02%)</title><rect x="496.8" y="453" width="0.3" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="499.82" y="463.5" ></text>
</g>
<g >
<title>__strcpy_avx2 (1,406,205,318 samples, 0.01%)</title><rect x="1061.0" y="453" width="0.1" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="1063.96" y="463.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (7,798,973,279 samples, 0.08%)</title><rect x="215.7" y="549" width="1.0" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="218.71" y="559.5" ></text>
</g>
<g >
<title>ItemPointerEquals (2,492,146,687 samples, 0.03%)</title><rect x="296.2" y="309" width="0.3" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="299.20" y="319.5" ></text>
</g>
<g >
<title>ResourceOwnerCreate (9,176,549,138 samples, 0.10%)</title><rect x="215.6" y="565" width="1.1" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="218.58" y="575.5" ></text>
</g>
<g >
<title>SIGetDataEntries (2,196,151,528 samples, 0.02%)</title><rect x="1074.1" y="485" width="0.2" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1077.07" y="495.5" ></text>
</g>
<g >
<title>table_block_relation_estimate_size (28,154,698,378 samples, 0.30%)</title><rect x="935.3" y="357" width="3.5" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="938.32" y="367.5" ></text>
</g>
<g >
<title>SetCurrentStatementStartTimestamp (3,292,023,590 samples, 0.03%)</title><rect x="210.1" y="597" width="0.4" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="213.09" y="607.5" ></text>
</g>
<g >
<title>LWLockAcquireOrWait (79,095,828,534 samples, 0.83%)</title><rect x="481.2" y="485" width="9.8" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="484.16" y="495.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,456,324,126 samples, 0.03%)</title><rect x="1185.1" y="581" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1188.13" y="591.5" ></text>
</g>
<g >
<title>ModifyWaitEvent (1,155,267,042 samples, 0.01%)</title><rect x="80.3" y="757" width="0.1" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="83.25" y="767.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (884,781,725 samples, 0.01%)</title><rect x="95.7" y="197" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="98.68" y="207.5" ></text>
</g>
<g >
<title>HeapTupleHasNulls (939,450,766 samples, 0.01%)</title><rect x="1002.3" y="197" width="0.1" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="1005.33" y="207.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="629" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1137.90" y="639.5" ></text>
</g>
<g >
<title>tag_hash (2,580,327,036 samples, 0.03%)</title><rect x="370.6" y="245" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="373.57" y="255.5" ></text>
</g>
<g >
<title>btint4cmp (1,268,256,603 samples, 0.01%)</title><rect x="1123.9" y="757" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1126.92" y="767.5" ></text>
</g>
<g >
<title>murmurhash32 (1,513,528,316 samples, 0.02%)</title><rect x="828.0" y="277" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="831.03" y="287.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (907,517,998 samples, 0.01%)</title><rect x="57.5" y="757" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="60.48" y="767.5" ></text>
</g>
<g >
<title>index_getattr (2,463,738,124 samples, 0.03%)</title><rect x="84.7" y="261" width="0.3" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="87.68" y="271.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="533" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="128.92" y="543.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVisibility (1,825,459,093 samples, 0.02%)</title><rect x="405.7" y="389" width="0.3" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="408.73" y="399.5" ></text>
</g>
<g >
<title>pg_class_aclmask_ext (6,847,734,651 samples, 0.07%)</title><rect x="1033.5" y="165" width="0.9" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="1036.54" y="175.5" ></text>
</g>
<g >
<title>mutex_unlock (1,099,243,932 samples, 0.01%)</title><rect x="161.0" y="373" width="0.1" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="163.97" y="383.5" ></text>
</g>
<g >
<title>heap_page_prune_opt (19,311,270,732 samples, 0.20%)</title><rect x="311.1" y="261" width="2.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="314.07" y="271.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (860,242,846 samples, 0.01%)</title><rect x="819.0" y="389" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="821.95" y="399.5" ></text>
</g>
<g >
<title>BufferAlloc (1,290,999,928 samples, 0.01%)</title><rect x="34.4" y="757" width="0.2" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="37.40" y="767.5" ></text>
</g>
<g >
<title>pq_recvbuf (279,605,486,156 samples, 2.94%)</title><rect x="152.5" y="549" width="34.8" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="155.54" y="559.5" >pq..</text>
</g>
<g >
<title>hash_search (2,593,872,515 samples, 0.03%)</title><rect x="989.6" y="325" width="0.3" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="992.57" y="335.5" ></text>
</g>
<g >
<title>psi_group_change (2,415,877,500 samples, 0.03%)</title><rect x="485.9" y="261" width="0.3" height="15.0" fill="rgb(226,101,24)" rx="2" ry="2" />
<text  x="488.90" y="271.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,922,399,953 samples, 0.05%)</title><rect x="1041.5" y="325" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1044.49" y="335.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (4,876,537,898 samples, 0.05%)</title><rect x="214.9" y="565" width="0.6" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="217.94" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (927,094,221 samples, 0.01%)</title><rect x="872.4" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="875.35" y="463.5" ></text>
</g>
<g >
<title>op_in_opfamily (8,729,901,585 samples, 0.09%)</title><rect x="1020.8" y="309" width="1.1" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="1023.83" y="319.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (1,458,980,256 samples, 0.02%)</title><rect x="1076.1" y="469" width="0.2" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1079.08" y="479.5" ></text>
</g>
<g >
<title>TupleDescAttr (940,498,688 samples, 0.01%)</title><rect x="861.1" y="501" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="864.13" y="511.5" ></text>
</g>
<g >
<title>ReserveXLogInsertLocation (4,239,665,048 samples, 0.04%)</title><rect x="512.5" y="453" width="0.6" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="515.54" y="463.5" ></text>
</g>
<g >
<title>bms_copy (1,590,025,224 samples, 0.02%)</title><rect x="1121.0" y="757" width="0.1" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="1123.95" y="767.5" ></text>
</g>
<g >
<title>RelnameGetRelid (43,193,875,598 samples, 0.45%)</title><rect x="825.0" y="405" width="5.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="827.98" y="415.5" ></text>
</g>
<g >
<title>_bt_getbuf (11,333,614,982 samples, 0.12%)</title><rect x="85.0" y="293" width="1.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="87.98" y="303.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,806,856,863 samples, 0.02%)</title><rect x="923.4" y="373" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="926.36" y="383.5" ></text>
</g>
<g >
<title>planner (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="629" width="3.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="105.66" y="639.5" ></text>
</g>
<g >
<title>newNode (2,370,823,418 samples, 0.02%)</title><rect x="921.1" y="421" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="924.10" y="431.5" ></text>
</g>
<g >
<title>bms_make_singleton (3,952,332,804 samples, 0.04%)</title><rect x="974.7" y="405" width="0.5" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="977.69" y="415.5" ></text>
</g>
<g >
<title>__intel_pmu_enable_all.isra.0 (3,571,723,324 samples, 0.04%)</title><rect x="164.5" y="293" width="0.5" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="167.53" y="303.5" ></text>
</g>
<g >
<title>SPI_inside_nonatomic_context (1,021,133,993 samples, 0.01%)</title><rect x="1076.5" y="533" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1079.46" y="543.5" ></text>
</g>
<g >
<title>scanNameSpaceForENR (967,806,935 samples, 0.01%)</title><rect x="833.4" y="469" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="836.38" y="479.5" ></text>
</g>
<g >
<title>is_redundant_with_indexclauses (1,353,027,827 samples, 0.01%)</title><rect x="1015.4" y="309" width="0.2" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="1018.44" y="319.5" ></text>
</g>
<g >
<title>new_list (4,356,746,797 samples, 0.05%)</title><rect x="1115.0" y="709" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1117.99" y="719.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (915,185,204 samples, 0.01%)</title><rect x="86.7" y="165" width="0.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="89.69" y="175.5" ></text>
</g>
<g >
<title>ExecComputeSlotInfo (2,585,020,680 samples, 0.03%)</title><rect x="424.5" y="325" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="427.51" y="335.5" ></text>
</g>
<g >
<title>set_next_entity (1,085,712,733 samples, 0.01%)</title><rect x="163.4" y="309" width="0.1" height="15.0" fill="rgb(232,125,29)" rx="2" ry="2" />
<text  x="166.40" y="319.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (830,739,932 samples, 0.01%)</title><rect x="409.2" y="245" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="412.18" y="255.5" ></text>
</g>
<g >
<title>newNode (2,235,453,069 samples, 0.02%)</title><rect x="934.2" y="373" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="937.16" y="383.5" ></text>
</g>
<g >
<title>AllocSetCheck (3,644,188,127 samples, 0.04%)</title><rect x="135.1" y="485" width="0.4" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="138.06" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,295,826,693 samples, 0.01%)</title><rect x="996.8" y="229" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="999.79" y="239.5" ></text>
</g>
<g >
<title>bms_is_valid_set (1,013,833,191 samples, 0.01%)</title><rect x="880.4" y="453" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="883.37" y="463.5" ></text>
</g>
<g >
<title>ExecCloseResultRelations (17,893,009,191 samples, 0.19%)</title><rect x="237.8" y="485" width="2.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="240.77" y="495.5" ></text>
</g>
<g >
<title>__update_idle_core (906,467,145 samples, 0.01%)</title><rect x="484.6" y="245" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="487.59" y="255.5" ></text>
</g>
<g >
<title>new_list (3,381,265,959 samples, 0.04%)</title><rect x="1114.2" y="725" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1117.20" y="735.5" ></text>
</g>
<g >
<title>MemoryContextDelete (17,775,127,030 samples, 0.19%)</title><rect x="133.9" y="565" width="2.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="136.89" y="575.5" ></text>
</g>
<g >
<title>list_make1_impl (2,852,159,762 samples, 0.03%)</title><rect x="984.9" y="373" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="987.90" y="383.5" ></text>
</g>
<g >
<title>get_func_retset (8,243,444,765 samples, 0.09%)</title><rect x="848.2" y="405" width="1.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="851.23" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,260,871,668 samples, 0.01%)</title><rect x="861.5" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="864.49" y="463.5" ></text>
</g>
<g >
<title>try_to_wake_up (58,397,294,825 samples, 0.61%)</title><rect x="201.2" y="341" width="7.2" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="204.16" y="351.5" ></text>
</g>
<g >
<title>PointerGetDatum (3,039,486,939 samples, 0.03%)</title><rect x="83.3" y="757" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="86.34" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (981,885,140 samples, 0.01%)</title><rect x="861.9" y="485" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="864.94" y="495.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (1,291,805,830 samples, 0.01%)</title><rect x="389.5" y="37" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="392.45" y="47.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (14,663,255,160 samples, 0.15%)</title><rect x="1037.7" y="357" width="1.8" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1040.69" y="367.5" ></text>
</g>
<g >
<title>AllocSetReset (8,356,357,771 samples, 0.09%)</title><rect x="467.6" y="469" width="1.0" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="470.60" y="479.5" ></text>
</g>
<g >
<title>ExecComputeSlotInfo (1,058,924,974 samples, 0.01%)</title><rect x="437.9" y="373" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="440.88" y="383.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,409,985,659 samples, 0.01%)</title><rect x="1189.5" y="757" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1192.49" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="597" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1188.13" y="607.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,602,885,911 samples, 0.03%)</title><rect x="382.5" y="341" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="385.55" y="351.5" ></text>
</g>
<g >
<title>list_nth_cell (1,091,586,932 samples, 0.01%)</title><rect x="1035.6" y="245" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1038.55" y="255.5" ></text>
</g>
<g >
<title>table_index_fetch_end (3,220,394,746 samples, 0.03%)</title><rect x="248.2" y="405" width="0.4" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="251.23" y="415.5" ></text>
</g>
<g >
<title>heapam_estimate_rel_size (28,595,290,857 samples, 0.30%)</title><rect x="935.3" y="373" width="3.5" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="938.26" y="383.5" ></text>
</g>
<g >
<title>build_index_tlist (13,130,811,371 samples, 0.14%)</title><rect x="933.4" y="405" width="1.7" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="936.42" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,157,971,526 samples, 0.01%)</title><rect x="863.3" y="437" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="866.35" y="447.5" ></text>
</g>
<g >
<title>palloc0 (5,760,879,673 samples, 0.06%)</title><rect x="949.1" y="405" width="0.7" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="952.13" y="415.5" ></text>
</g>
<g >
<title>transformReturningClause (1,128,239,273 samples, 0.01%)</title><rect x="1187.4" y="757" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="1190.42" y="767.5" ></text>
</g>
<g >
<title>lappend_int (3,094,704,084 samples, 0.03%)</title><rect x="457.5" y="453" width="0.4" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="460.53" y="463.5" ></text>
</g>
<g >
<title>LockRelationOid (1,570,319,129 samples, 0.02%)</title><rect x="59.1" y="757" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="62.08" y="767.5" ></text>
</g>
<g >
<title>markVarForSelectPriv (7,471,060,040 samples, 0.08%)</title><rect x="856.9" y="357" width="1.0" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="859.93" y="367.5" ></text>
</g>
<g >
<title>BufferIsValid (944,816,175 samples, 0.01%)</title><rect x="250.1" y="437" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="253.14" y="447.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,481,080,629 samples, 0.04%)</title><rect x="917.2" y="341" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="920.20" y="351.5" ></text>
</g>
<g >
<title>compute_new_xmax_infomask (1,394,884,353 samples, 0.01%)</title><rect x="383.3" y="357" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="386.27" y="367.5" ></text>
</g>
<g >
<title>check_lock_if_inplace_updateable_rel (1,040,069,233 samples, 0.01%)</title><rect x="1126.4" y="757" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1129.39" y="767.5" ></text>
</g>
<g >
<title>CheckExprStillValid (941,754,196 samples, 0.01%)</title><rect x="38.3" y="757" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="41.30" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,212,849,874 samples, 0.01%)</title><rect x="279.0" y="341" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="282.03" y="351.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,236,995,579 samples, 0.01%)</title><rect x="921.6" y="405" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="924.55" y="415.5" ></text>
</g>
<g >
<title>__virt_addr_valid (3,802,862,419 samples, 0.04%)</title><rect x="195.7" y="357" width="0.5" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="198.72" y="367.5" ></text>
</g>
<g >
<title>check_redundant_nullability_qual (924,370,551 samples, 0.01%)</title><rect x="964.2" y="405" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="967.18" y="415.5" ></text>
</g>
<g >
<title>tag_hash (3,618,709,238 samples, 0.04%)</title><rect x="923.6" y="373" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="926.58" y="383.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (8,233,096,366 samples, 0.09%)</title><rect x="1008.7" y="245" width="1.0" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1011.67" y="255.5" ></text>
</g>
<g >
<title>hash_bytes (2,109,215,735 samples, 0.02%)</title><rect x="948.3" y="309" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="951.25" y="319.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,514,327,687 samples, 0.02%)</title><rect x="353.3" y="341" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="356.34" y="351.5" ></text>
</g>
<g >
<title>simplify_function (916,247,721 samples, 0.01%)</title><rect x="1182.9" y="757" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1185.92" y="767.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,296,006,930 samples, 0.01%)</title><rect x="361.0" y="309" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="363.95" y="319.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,583,759,191 samples, 0.02%)</title><rect x="1013.9" y="245" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1016.89" y="255.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,334,472,513 samples, 0.04%)</title><rect x="1038.3" y="293" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1041.26" y="303.5" ></text>
</g>
<g >
<title>pfree (1,481,872,888 samples, 0.02%)</title><rect x="992.5" y="309" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="995.51" y="319.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (960,954,854 samples, 0.01%)</title><rect x="363.7" y="341" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="366.68" y="351.5" ></text>
</g>
<g >
<title>new_list (2,000,222,439 samples, 0.02%)</title><rect x="975.5" y="405" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="978.55" y="415.5" ></text>
</g>
<g >
<title>ExecOpenIndices (1,195,486,297 samples, 0.01%)</title><rect x="45.2" y="757" width="0.1" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="48.19" y="767.5" ></text>
</g>
<g >
<title>BoolGetDatum (841,876,701 samples, 0.01%)</title><rect x="34.2" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="37.16" y="767.5" ></text>
</g>
<g >
<title>LWLockRelease (898,377,181 samples, 0.01%)</title><rect x="406.7" y="373" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="409.73" y="383.5" ></text>
</g>
<g >
<title>list_insert_nth (2,094,708,131 samples, 0.02%)</title><rect x="992.7" y="357" width="0.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="995.69" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,773,667,127 samples, 0.02%)</title><rect x="1119.0" y="677" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1122.02" y="687.5" ></text>
</g>
<g >
<title>pg_class_aclcheck (895,616,830 samples, 0.01%)</title><rect x="1169.2" y="757" width="0.1" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="1172.18" y="767.5" ></text>
</g>
<g >
<title>smgrdestroyall (979,630,493 samples, 0.01%)</title><rect x="1183.5" y="757" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1186.53" y="767.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="549" width="6.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="98.68" y="559.5" ></text>
</g>
<g >
<title>build_simple_rel (188,004,027,073 samples, 1.98%)</title><rect x="926.5" y="437" width="23.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="929.53" y="447.5" >b..</text>
</g>
<g >
<title>yy_get_previous_state (1,113,411,716 samples, 0.01%)</title><rect x="1113.8" y="709" width="0.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1116.80" y="719.5" ></text>
</g>
<g >
<title>new_list (2,165,448,846 samples, 0.02%)</title><rect x="931.2" y="373" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="934.21" y="383.5" ></text>
</g>
<g >
<title>MemoryContextTraverseNext (2,093,795,956 samples, 0.02%)</title><rect x="80.0" y="757" width="0.3" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="82.99" y="767.5" ></text>
</g>
<g >
<title>bms_get_singleton_member (1,501,073,524 samples, 0.02%)</title><rect x="981.2" y="421" width="0.2" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="984.17" y="431.5" ></text>
</g>
<g >
<title>get_cheapest_fractional_path (907,861,694 samples, 0.01%)</title><rect x="1140.7" y="757" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="1143.68" y="767.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,631,444,954 samples, 0.02%)</title><rect x="353.5" y="341" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="356.54" y="351.5" ></text>
</g>
<g >
<title>ExecUpdatePrologue (66,139,083,506 samples, 0.70%)</title><rect x="395.9" y="421" width="8.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="398.91" y="431.5" ></text>
</g>
<g >
<title>[[vdso]] (1,532,396,249 samples, 0.02%)</title><rect x="1078.8" y="565" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1081.82" y="575.5" ></text>
</g>
<g >
<title>__scm_recv_common.isra.0 (1,085,535,715 samples, 0.01%)</title><rect x="185.5" y="357" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="188.47" y="367.5" ></text>
</g>
<g >
<title>ReleaseCatCache (836,646,210 samples, 0.01%)</title><rect x="415.2" y="405" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="418.24" y="415.5" ></text>
</g>
<g >
<title>TupleDescInitEntry (24,441,111,701 samples, 0.26%)</title><rect x="442.1" y="373" width="3.1" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="445.14" y="383.5" ></text>
</g>
<g >
<title>PostgresMain (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="629" width="2.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="129.31" y="639.5" ></text>
</g>
<g >
<title>kmem_cache_alloc_node_noprof (14,464,905,975 samples, 0.15%)</title><rect x="198.6" y="357" width="1.8" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="201.57" y="367.5" ></text>
</g>
<g >
<title>newNode (3,621,333,076 samples, 0.04%)</title><rect x="347.3" y="341" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="350.34" y="351.5" ></text>
</g>
<g >
<title>set_plain_rel_pathlist (978,946,569 samples, 0.01%)</title><rect x="1182.2" y="757" width="0.1" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="1185.16" y="767.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,520,253,429 samples, 0.02%)</title><rect x="109.2" y="741" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="112.20" y="751.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,529,172,839 samples, 0.02%)</title><rect x="1041.0" y="309" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1043.97" y="319.5" ></text>
</g>
<g >
<title>parse_analyze_fixedparams (449,366,471,343 samples, 4.73%)</title><rect x="802.7" y="565" width="55.8" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="805.67" y="575.5" >parse..</text>
</g>
<g >
<title>AllocSetAlloc (1,155,327,914 samples, 0.01%)</title><rect x="441.9" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="444.94" y="351.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,908,953,901 samples, 0.07%)</title><rect x="844.9" y="421" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="847.89" y="431.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,302,566,734 samples, 0.01%)</title><rect x="448.9" y="373" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="451.88" y="383.5" ></text>
</g>
<g >
<title>futex_do_wait (43,302,055,181 samples, 0.46%)</title><rect x="483.5" y="325" width="5.4" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="486.54" y="335.5" ></text>
</g>
<g >
<title>PageGetItem (2,287,170,361 samples, 0.02%)</title><rect x="321.0" y="213" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="323.96" y="223.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (2,823,016,946 samples, 0.03%)</title><rect x="852.7" y="325" width="0.4" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="855.74" y="335.5" ></text>
</g>
<g >
<title>palloc (1,856,417,268 samples, 0.02%)</title><rect x="514.7" y="485" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="517.74" y="495.5" ></text>
</g>
<g >
<title>bms_int_members (1,817,860,378 samples, 0.02%)</title><rect x="359.8" y="373" width="0.3" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="362.84" y="383.5" ></text>
</g>
<g >
<title>palloc0 (2,605,950,152 samples, 0.03%)</title><rect x="815.4" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="818.36" y="431.5" ></text>
</g>
<g >
<title>__rseq_handle_notify_resume (2,204,309,515 samples, 0.02%)</title><rect x="490.4" y="373" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="493.36" y="383.5" ></text>
</g>
<g >
<title>LWLockAcquire (15,072,114,706 samples, 0.16%)</title><rect x="365.5" y="309" width="1.9" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="368.49" y="319.5" ></text>
</g>
<g >
<title>AllocSetCheck (4,751,655,520 samples, 0.05%)</title><rect x="252.6" y="421" width="0.6" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="255.60" y="431.5" ></text>
</g>
<g >
<title>LockAcquireExtended (20,491,319,632 samples, 0.22%)</title><rect x="940.5" y="357" width="2.5" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="943.48" y="367.5" ></text>
</g>
<g >
<title>lappend (960,047,100 samples, 0.01%)</title><rect x="433.2" y="357" width="0.1" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="436.19" y="367.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,829,573,722 samples, 0.04%)</title><rect x="216.8" y="549" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="219.77" y="559.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (2,560,841,102 samples, 0.03%)</title><rect x="389.3" y="197" width="0.4" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="392.33" y="207.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,323,698,452 samples, 0.01%)</title><rect x="812.3" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="815.27" y="447.5" ></text>
</g>
<g >
<title>palloc (1,165,647,944 samples, 0.01%)</title><rect x="872.3" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="875.32" y="479.5" ></text>
</g>
<g >
<title>ExecProcNode (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="485" width="0.3" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="1161.90" y="495.5" ></text>
</g>
<g >
<title>nocachegetattr (5,729,707,640 samples, 0.06%)</title><rect x="829.5" y="325" width="0.7" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="832.46" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,140,055,657 samples, 0.01%)</title><rect x="861.8" y="485" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="864.79" y="495.5" ></text>
</g>
<g >
<title>exprType (1,188,224,543 samples, 0.01%)</title><rect x="960.7" y="389" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="963.67" y="399.5" ></text>
</g>
<g >
<title>make_op (37,523,422,204 samples, 0.39%)</title><rect x="836.3" y="389" width="4.7" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="839.33" y="399.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,322,313,126 samples, 0.01%)</title><rect x="998.8" y="309" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1001.75" y="319.5" ></text>
</g>
<g >
<title>LockHeldByMe (19,216,721,740 samples, 0.20%)</title><rect x="866.9" y="469" width="2.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="869.91" y="479.5" ></text>
</g>
<g >
<title>IsAbortedTransactionBlockState (1,186,336,041 samples, 0.01%)</title><rect x="133.5" y="597" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="136.53" y="607.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (2,002,016,725 samples, 0.02%)</title><rect x="1158.9" y="309" width="0.3" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="1161.90" y="319.5" ></text>
</g>
<g >
<title>check_mergejoinable (44,874,820,285 samples, 0.47%)</title><rect x="958.6" y="405" width="5.6" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="961.61" y="415.5" ></text>
</g>
<g >
<title>[[vdso]] (1,460,790,467 samples, 0.02%)</title><rect x="476.8" y="469" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="479.80" y="479.5" ></text>
</g>
<g >
<title>LWLockQueueSelf (1,292,174,060 samples, 0.01%)</title><rect x="366.4" y="293" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="369.35" y="303.5" ></text>
</g>
<g >
<title>futex_wait (1,523,372,546 samples, 0.02%)</title><rect x="1148.5" y="501" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1151.51" y="511.5" ></text>
</g>
<g >
<title>generic_perform_write (34,975,505,395 samples, 0.37%)</title><rect x="502.2" y="373" width="4.3" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="505.20" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,175,253,227 samples, 0.01%)</title><rect x="278.1" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="281.12" y="383.5" ></text>
</g>
<g >
<title>create_scan_plan (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="485" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="89.91" y="495.5" ></text>
</g>
<g >
<title>LWLockRelease (1,609,635,361 samples, 0.02%)</title><rect x="822.8" y="373" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="825.82" y="383.5" ></text>
</g>
<g >
<title>table_close (2,329,665,994 samples, 0.02%)</title><rect x="803.5" y="533" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="806.46" y="543.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,766,256,405 samples, 0.02%)</title><rect x="496.9" y="405" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="499.85" y="415.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (862,854,465 samples, 0.01%)</title><rect x="443.7" y="309" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="446.68" y="319.5" ></text>
</g>
<g >
<title>list_copy (2,240,455,877 samples, 0.02%)</title><rect x="899.5" y="469" width="0.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="902.54" y="479.5" ></text>
</g>
<g >
<title>heap_page_prune_and_freeze (9,684,059,828 samples, 0.10%)</title><rect x="1148.9" y="741" width="1.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1151.95" y="751.5" ></text>
</g>
<g >
<title>ExecPushExprSetupSteps (7,400,625,927 samples, 0.08%)</title><rect x="274.4" y="405" width="0.9" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="277.43" y="415.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="709" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1188.13" y="719.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,346,480,802 samples, 0.01%)</title><rect x="358.7" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="361.68" y="335.5" ></text>
</g>
<g >
<title>namehashfast (5,405,659,739 samples, 0.06%)</title><rect x="828.2" y="293" width="0.7" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="831.22" y="303.5" ></text>
</g>
<g >
<title>_int_free (972,306,543 samples, 0.01%)</title><rect x="150.3" y="533" width="0.1" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="153.31" y="543.5" ></text>
</g>
<g >
<title>LWLockAcquire (5,159,645,686 samples, 0.05%)</title><rect x="223.0" y="549" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="225.97" y="559.5" ></text>
</g>
<g >
<title>fdget (2,062,666,655 samples, 0.02%)</title><rect x="174.9" y="405" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="177.87" y="415.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (2,108,443,145 samples, 0.02%)</title><rect x="757.3" y="485" width="0.3" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="760.34" y="495.5" ></text>
</g>
<g >
<title>futex_wake (2,306,665,663 samples, 0.02%)</title><rect x="375.9" y="197" width="0.3" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="378.87" y="207.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,603,467,045 samples, 0.02%)</title><rect x="357.7" y="357" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="360.75" y="367.5" ></text>
</g>
<g >
<title>do_syscall_64 (27,750,898,730 samples, 0.29%)</title><rect x="491.6" y="389" width="3.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="494.62" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,226,476,090 samples, 0.04%)</title><rect x="960.1" y="341" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="963.13" y="351.5" ></text>
</g>
<g >
<title>current_time (1,671,497,623 samples, 0.02%)</title><rect x="502.0" y="341" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="504.98" y="351.5" ></text>
</g>
<g >
<title>__update_load_avg_cfs_rq (937,278,864 samples, 0.01%)</title><rect x="488.3" y="197" width="0.1" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="491.28" y="207.5" ></text>
</g>
<g >
<title>wipe_mem (3,609,936,595 samples, 0.04%)</title><rect x="135.5" y="485" width="0.5" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="138.53" y="495.5" ></text>
</g>
<g >
<title>bms_is_valid_set (871,678,597 samples, 0.01%)</title><rect x="951.5" y="421" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="954.49" y="431.5" ></text>
</g>
<g >
<title>markRTEForSelectPriv (1,867,256,937 samples, 0.02%)</title><rect x="842.6" y="309" width="0.2" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="845.59" y="319.5" ></text>
</g>
<g >
<title>simple_copy_to_iter (5,140,514,068 samples, 0.05%)</title><rect x="186.2" y="325" width="0.6" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="189.19" y="335.5" ></text>
</g>
<g >
<title>addNSItemToQuery (7,055,865,646 samples, 0.07%)</title><rect x="813.2" y="469" width="0.9" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="816.21" y="479.5" ></text>
</g>
<g >
<title>ReleaseBuffer (2,679,458,734 samples, 0.03%)</title><rect x="284.3" y="277" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="287.34" y="287.5" ></text>
</g>
<g >
<title>makeString (4,538,387,098 samples, 0.05%)</title><rect x="1115.5" y="725" width="0.6" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="1118.54" y="735.5" ></text>
</g>
<g >
<title>BackendMain (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="645" width="2.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="129.31" y="655.5" ></text>
</g>
<g >
<title>palloc (1,679,105,978 samples, 0.02%)</title><rect x="922.6" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="925.56" y="431.5" ></text>
</g>
<g >
<title>CreateExprContextInternal (9,508,331,263 samples, 0.10%)</title><rect x="346.6" y="357" width="1.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="349.61" y="367.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,514,618,311 samples, 0.03%)</title><rect x="438.6" y="325" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="441.60" y="335.5" ></text>
</g>
<g >
<title>XidInMVCCSnapshot (1,219,947,884 samples, 0.01%)</title><rect x="310.1" y="213" width="0.1" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="313.07" y="223.5" ></text>
</g>
<g >
<title>transformAExprOp (835,705,736 samples, 0.01%)</title><rect x="1187.1" y="757" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1190.09" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,786,828,587 samples, 0.03%)</title><rect x="910.2" y="437" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="913.22" y="447.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,344,510,520 samples, 0.04%)</title><rect x="1034.0" y="117" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1036.96" y="127.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,131,629,320 samples, 0.01%)</title><rect x="1082.3" y="565" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1085.27" y="575.5" ></text>
</g>
<g >
<title>PortalRun (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="645" width="2.6" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="87.32" y="655.5" ></text>
</g>
<g >
<title>hash_search (7,955,164,943 samples, 0.08%)</title><rect x="452.1" y="341" width="1.0" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="455.11" y="351.5" ></text>
</g>
<g >
<title>btcostestimate (137,928,825,857 samples, 1.45%)</title><rect x="997.9" y="325" width="17.2" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="1000.92" y="335.5" ></text>
</g>
<g >
<title>list_delete_ptr (6,986,516,276 samples, 0.07%)</title><rect x="253.7" y="469" width="0.9" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="256.70" y="479.5" ></text>
</g>
<g >
<title>palloc (1,448,418,902 samples, 0.02%)</title><rect x="817.3" y="405" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="820.25" y="415.5" ></text>
</g>
<g >
<title>LockRelease (8,871,709,935 samples, 0.09%)</title><rect x="238.7" y="421" width="1.1" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="241.69" y="431.5" ></text>
</g>
<g >
<title>ExecComputeSlotInfo (4,268,901,613 samples, 0.04%)</title><rect x="274.6" y="389" width="0.5" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="277.55" y="399.5" ></text>
</g>
<g >
<title>__x64_sys_epoll_wait (148,553,898,770 samples, 1.56%)</title><rect x="156.7" y="437" width="18.4" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="159.67" y="447.5" ></text>
</g>
<g >
<title>RelationGetIndexPredicate (1,162,339,197 samples, 0.01%)</title><rect x="89.8" y="757" width="0.2" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="92.82" y="767.5" ></text>
</g>
<g >
<title>do_syscall_64 (4,021,420,011 samples, 0.04%)</title><rect x="932.4" y="293" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="935.45" y="303.5" ></text>
</g>
<g >
<title>ScanKeyEntryInitializeWithInfo (1,518,594,471 samples, 0.02%)</title><rect x="316.0" y="261" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="319.04" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (846,735,987 samples, 0.01%)</title><rect x="963.1" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="966.13" y="335.5" ></text>
</g>
<g >
<title>LockTagHashCode (3,017,910,220 samples, 0.03%)</title><rect x="941.9" y="341" width="0.3" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="944.85" y="351.5" ></text>
</g>
<g >
<title>ExecMaterializeSlot (25,099,600,868 samples, 0.26%)</title><rect x="396.0" y="405" width="3.2" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="399.04" y="415.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="309" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="128.92" y="319.5" ></text>
</g>
<g >
<title>ExecutorRun (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="565" width="1.9" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="126.99" y="575.5" ></text>
</g>
<g >
<title>do_syscall_64 (141,024,159,717 samples, 1.48%)</title><rect x="192.1" y="469" width="17.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="195.10" y="479.5" ></text>
</g>
<g >
<title>__slab_free (1,817,111,718 samples, 0.02%)</title><rect x="182.8" y="325" width="0.2" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="185.75" y="335.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (2,358,161,980 samples, 0.02%)</title><rect x="1078.8" y="581" width="0.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1081.80" y="591.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,962,323,620 samples, 0.02%)</title><rect x="442.9" y="325" width="0.3" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="445.93" y="335.5" ></text>
</g>
<g >
<title>issue_xlog_fsync (13,731,213,425 samples, 0.14%)</title><rect x="507.1" y="469" width="1.7" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="510.10" y="479.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,149,125,232 samples, 0.01%)</title><rect x="309.3" y="117" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="312.34" y="127.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (28,355,260,223 samples, 0.30%)</title><rect x="1053.8" y="501" width="3.5" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1056.77" y="511.5" ></text>
</g>
<g >
<title>LockAcquire (49,006,972,544 samples, 0.52%)</title><rect x="368.7" y="309" width="6.0" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="371.65" y="319.5" ></text>
</g>
<g >
<title>get_hash_value (3,533,914,337 samples, 0.04%)</title><rect x="96.0" y="165" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="98.99" y="175.5" ></text>
</g>
<g >
<title>AtEOXact_LargeObject (1,344,893,110 samples, 0.01%)</title><rect x="470.0" y="517" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="472.97" y="527.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,028,627,101 samples, 0.01%)</title><rect x="848.3" y="357" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="851.32" y="367.5" ></text>
</g>
<g >
<title>do_futex (928,684,905 samples, 0.01%)</title><rect x="370.2" y="149" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="373.23" y="159.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="229" width="4.4" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="98.68" y="239.5" ></text>
</g>
<g >
<title>pg_class_aclcheck_ext (7,225,603,338 samples, 0.08%)</title><rect x="1033.5" y="181" width="0.9" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1036.50" y="191.5" ></text>
</g>
<g >
<title>all (9,499,813,485,584 samples, 100%)</title><rect x="10.0" y="789" width="1180.0" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="13.00" y="799.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (899,002,840 samples, 0.01%)</title><rect x="393.8" y="309" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="396.84" y="319.5" ></text>
</g>
<g >
<title>get_attstatsslot (61,513,794,930 samples, 0.65%)</title><rect x="1000.4" y="293" width="7.7" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="1003.41" y="303.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (14,702,325,729 samples, 0.15%)</title><rect x="954.2" y="437" width="1.8" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="957.22" y="447.5" ></text>
</g>
<g >
<title>_mdnblocks (17,122,087,324 samples, 0.18%)</title><rect x="936.1" y="261" width="2.2" height="15.0" fill="rgb(217,58,14)" rx="2" ry="2" />
<text  x="939.13" y="271.5" ></text>
</g>
<g >
<title>ExecInitIndexScan (232,433,500,729 samples, 2.45%)</title><rect x="421.6" y="437" width="28.9" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="424.61" y="447.5" >Ex..</text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (975,868,388 samples, 0.01%)</title><rect x="430.7" y="293" width="0.2" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="433.73" y="303.5" ></text>
</g>
<g >
<title>bms_copy (2,152,198,280 samples, 0.02%)</title><rect x="965.4" y="357" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="968.38" y="367.5" ></text>
</g>
<g >
<title>AllocSetCheck (4,664,597,562 samples, 0.05%)</title><rect x="134.2" y="517" width="0.5" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="137.15" y="527.5" ></text>
</g>
<g >
<title>RelationGetIndexAttrBitmap (11,299,535,910 samples, 0.12%)</title><rect x="356.3" y="373" width="1.4" height="15.0" fill="rgb(222,79,19)" rx="2" ry="2" />
<text  x="359.29" y="383.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (810,686,037 samples, 0.01%)</title><rect x="406.6" y="357" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="409.63" y="367.5" ></text>
</g>
<g >
<title>LockAcquireExtended (7,544,346,831 samples, 0.08%)</title><rect x="447.9" y="373" width="1.0" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="450.94" y="383.5" ></text>
</g>
<g >
<title>GetPortalByName (7,885,405,325 samples, 0.08%)</title><rect x="214.0" y="565" width="0.9" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="216.96" y="575.5" ></text>
</g>
<g >
<title>fdget (4,598,288,728 samples, 0.05%)</title><rect x="192.9" y="421" width="0.6" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="195.94" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (852,559,956 samples, 0.01%)</title><rect x="955.8" y="261" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="958.82" y="271.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (2,225,632,892 samples, 0.02%)</title><rect x="370.2" y="261" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="373.17" y="271.5" ></text>
</g>
<g >
<title>get_relids_in_jointree (2,695,041,778 samples, 0.03%)</title><rect x="1069.5" y="469" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="1072.45" y="479.5" ></text>
</g>
<g >
<title>HeapTupleNoNulls (1,812,256,018 samples, 0.02%)</title><rect x="52.4" y="757" width="0.2" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="55.41" y="767.5" ></text>
</g>
<g >
<title>__strcmp_avx2 (1,120,368,563 samples, 0.01%)</title><rect x="858.1" y="341" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="861.08" y="351.5" ></text>
</g>
<g >
<title>hash_search (18,065,048,633 samples, 0.19%)</title><rect x="850.9" y="373" width="2.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="853.86" y="383.5" ></text>
</g>
<g >
<title>palloc (1,071,561,686 samples, 0.01%)</title><rect x="119.6" y="741" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="122.57" y="751.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (2,345,129,759 samples, 0.02%)</title><rect x="407.3" y="309" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="410.25" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,096,584,991 samples, 0.01%)</title><rect x="1014.4" y="261" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1017.44" y="271.5" ></text>
</g>
<g >
<title>expr_setup_walker (5,530,496,597 samples, 0.06%)</title><rect x="438.2" y="389" width="0.7" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="441.23" y="399.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (13,223,466,967 samples, 0.14%)</title><rect x="1052.0" y="469" width="1.6" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1054.95" y="479.5" ></text>
</g>
<g >
<title>__errno_location (2,608,005,738 samples, 0.03%)</title><rect x="154.0" y="501" width="0.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="157.04" y="511.5" ></text>
</g>
<g >
<title>palloc (894,248,361 samples, 0.01%)</title><rect x="899.7" y="437" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="902.70" y="447.5" ></text>
</g>
<g >
<title>RecordTransactionCommit (315,764,807,008 samples, 3.32%)</title><rect x="475.9" y="517" width="39.3" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="478.94" y="527.5" >Rec..</text>
</g>
<g >
<title>list_last_cell (1,406,396,302 samples, 0.01%)</title><rect x="1156.0" y="757" width="0.1" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1158.97" y="767.5" ></text>
</g>
<g >
<title>_bt_first (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="357" width="6.8" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="98.68" y="367.5" ></text>
</g>
<g >
<title>buildRelationAliases (1,090,564,487 samples, 0.01%)</title><rect x="1124.2" y="757" width="0.1" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="1127.16" y="767.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (8,887,566,130 samples, 0.09%)</title><rect x="339.5" y="229" width="1.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="342.54" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,250,402,797 samples, 0.01%)</title><rect x="957.2" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="960.24" y="415.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturnSwitchContext (18,792,318,849 samples, 0.20%)</title><rect x="269.6" y="405" width="2.3" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="272.60" y="415.5" ></text>
</g>
<g >
<title>bms_difference (2,733,857,238 samples, 0.03%)</title><rect x="965.3" y="373" width="0.4" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="968.33" y="383.5" ></text>
</g>
<g >
<title>LWLockRelease (35,529,659,288 samples, 0.37%)</title><rect x="491.0" y="485" width="4.4" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="494.00" y="495.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="309" width="0.5" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="89.39" y="319.5" ></text>
</g>
<g >
<title>tas (3,133,316,133 samples, 0.03%)</title><rect x="498.1" y="437" width="0.4" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="501.14" y="447.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="661" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.62" y="671.5" ></text>
</g>
<g >
<title>fill_val (3,184,830,669 samples, 0.03%)</title><rect x="398.5" y="341" width="0.4" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="401.46" y="351.5" ></text>
</g>
<g >
<title>ExecInterpExpr (23,989,096,201 samples, 0.25%)</title><rect x="288.8" y="293" width="3.0" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="291.83" y="303.5" ></text>
</g>
<g >
<title>PageGetItemId (3,396,705,384 samples, 0.04%)</title><rect x="81.8" y="757" width="0.5" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="84.84" y="767.5" ></text>
</g>
<g >
<title>UnlockBufHdr (1,326,513,112 samples, 0.01%)</title><rect x="326.2" y="213" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="329.15" y="223.5" ></text>
</g>
<g >
<title>list_nth (1,377,623,644 samples, 0.01%)</title><rect x="860.3" y="517" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="863.29" y="527.5" ></text>
</g>
<g >
<title>VirtualXactLockTableInsert (5,860,117,599 samples, 0.06%)</title><rect x="1076.7" y="533" width="0.8" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="1079.74" y="543.5" ></text>
</g>
<g >
<title>planstate_tree_walker_impl (850,143,726 samples, 0.01%)</title><rect x="1173.0" y="757" width="0.1" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="1175.99" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,043,330,139 samples, 0.01%)</title><rect x="948.7" y="341" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="951.70" y="351.5" ></text>
</g>
<g >
<title>relation_close (1,373,242,578 samples, 0.01%)</title><rect x="946.9" y="389" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="949.93" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,183,898,347 samples, 0.01%)</title><rect x="459.1" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="462.12" y="447.5" ></text>
</g>
<g >
<title>palloc (1,121,877,899 samples, 0.01%)</title><rect x="984.4" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="987.40" y="383.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (928,126,987 samples, 0.01%)</title><rect x="396.8" y="357" width="0.1" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="399.77" y="367.5" ></text>
</g>
<g >
<title>palloc0 (1,983,553,596 samples, 0.02%)</title><rect x="812.2" y="453" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="815.20" y="463.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (1,045,770,493 samples, 0.01%)</title><rect x="368.0" y="101" width="0.1" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="371.00" y="111.5" ></text>
</g>
<g >
<title>ServerLoop (83,672,568,701 samples, 0.88%)</title><rect x="95.7" y="757" width="10.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="98.68" y="767.5" ></text>
</g>
<g >
<title>update_curr (15,436,267,163 samples, 0.16%)</title><rect x="170.4" y="277" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="173.40" y="287.5" ></text>
</g>
<g >
<title>transformTopLevelStmt (433,682,255,277 samples, 4.57%)</title><rect x="804.6" y="549" width="53.9" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="807.62" y="559.5" >trans..</text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (3,164,203,444 samples, 0.03%)</title><rect x="795.7" y="485" width="0.4" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="798.73" y="495.5" ></text>
</g>
<g >
<title>LWLockRelease (887,955,707 samples, 0.01%)</title><rect x="301.9" y="133" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="304.92" y="143.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (15,028,927,322 samples, 0.16%)</title><rect x="916.4" y="453" width="1.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="919.40" y="463.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,995,818,510 samples, 0.04%)</title><rect x="959.6" y="293" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="962.60" y="303.5" ></text>
</g>
<g >
<title>get_relname_relid (38,808,284,830 samples, 0.41%)</title><rect x="825.4" y="389" width="4.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="828.40" y="399.5" ></text>
</g>
<g >
<title>_bt_mark_scankey_required (1,809,479,512 samples, 0.02%)</title><rect x="324.3" y="245" width="0.2" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="327.32" y="255.5" ></text>
</g>
<g >
<title>get_tablespace (8,272,176,246 samples, 0.09%)</title><rect x="1022.4" y="357" width="1.0" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1025.41" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,632,955,722 samples, 0.03%)</title><rect x="1017.4" y="309" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1020.41" y="319.5" ></text>
</g>
<g >
<title>palloc (4,245,287,253 samples, 0.04%)</title><rect x="1112.6" y="677" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1115.59" y="687.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,676,045,534 samples, 0.05%)</title><rect x="811.4" y="309" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="814.39" y="319.5" ></text>
</g>
<g >
<title>newNode (4,746,523,660 samples, 0.05%)</title><rect x="969.6" y="357" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="972.61" y="367.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (5,797,923,851 samples, 0.06%)</title><rect x="429.3" y="341" width="0.7" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="432.33" y="351.5" ></text>
</g>
<g >
<title>get_restriction_variable (46,310,726,580 samples, 0.49%)</title><rect x="1029.9" y="261" width="5.8" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="1032.94" y="271.5" ></text>
</g>
<g >
<title>pull_varnos (1,986,833,910 samples, 0.02%)</title><rect x="1035.2" y="229" width="0.3" height="15.0" fill="rgb(229,112,26)" rx="2" ry="2" />
<text  x="1038.25" y="239.5" ></text>
</g>
<g >
<title>pgstat_clear_snapshot (3,042,244,167 samples, 0.03%)</title><rect x="471.2" y="501" width="0.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="474.19" y="511.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_out (2,691,113,864 samples, 0.03%)</title><rect x="485.1" y="261" width="0.3" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="488.11" y="271.5" ></text>
</g>
<g >
<title>parserOpenTable (811,643,400 samples, 0.01%)</title><rect x="1166.9" y="757" width="0.1" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="1169.94" y="767.5" ></text>
</g>
<g >
<title>palloc (1,582,309,571 samples, 0.02%)</title><rect x="457.7" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="460.70" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,064,515,365 samples, 0.01%)</title><rect x="1069.6" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1072.65" y="431.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (902,080,660 samples, 0.01%)</title><rect x="302.9" y="133" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="305.89" y="143.5" ></text>
</g>
<g >
<title>new_list (3,882,521,532 samples, 0.04%)</title><rect x="1118.3" y="709" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1121.31" y="719.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,278,555,757 samples, 0.01%)</title><rect x="890.8" y="277" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="893.75" y="287.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (4,885,545,640 samples, 0.05%)</title><rect x="1052.7" y="421" width="0.6" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1055.71" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,231,646,390 samples, 0.01%)</title><rect x="849.9" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="852.86" y="383.5" ></text>
</g>
<g >
<title>ExecModifyTable (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="501" width="0.3" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="1161.90" y="511.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="565" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="89.91" y="575.5" ></text>
</g>
<g >
<title>newNode (2,658,144,043 samples, 0.03%)</title><rect x="842.2" y="309" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="845.19" y="319.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (7,790,469,852 samples, 0.08%)</title><rect x="431.0" y="309" width="0.9" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="433.95" y="319.5" ></text>
</g>
<g >
<title>hash_bytes (2,580,327,036 samples, 0.03%)</title><rect x="370.6" y="229" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="373.57" y="239.5" ></text>
</g>
<g >
<title>XLogBeginInsert (1,831,686,203 samples, 0.02%)</title><rect x="384.9" y="341" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="387.87" y="351.5" ></text>
</g>
<g >
<title>fdget (2,723,440,563 samples, 0.03%)</title><rect x="179.8" y="421" width="0.4" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="182.83" y="431.5" ></text>
</g>
<g >
<title>heapam_index_fetch_begin (2,356,991,461 samples, 0.02%)</title><rect x="295.7" y="293" width="0.3" height="15.0" fill="rgb(243,174,41)" rx="2" ry="2" />
<text  x="298.70" y="303.5" ></text>
</g>
<g >
<title>GETSTRUCT (2,443,598,647 samples, 0.03%)</title><rect x="48.4" y="757" width="0.3" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="51.37" y="767.5" ></text>
</g>
<g >
<title>__wake_up_sync_key (62,411,451,018 samples, 0.66%)</title><rect x="200.7" y="389" width="7.7" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="203.66" y="399.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (5,279,897,731 samples, 0.06%)</title><rect x="757.9" y="469" width="0.7" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="760.95" y="479.5" ></text>
</g>
<g >
<title>pfree (2,095,818,668 samples, 0.02%)</title><rect x="987.9" y="373" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="990.94" y="383.5" ></text>
</g>
<g >
<title>new_list (1,758,865,787 samples, 0.02%)</title><rect x="971.2" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="974.17" y="383.5" ></text>
</g>
<g >
<title>ReleaseBuffer (2,913,844,670 samples, 0.03%)</title><rect x="250.4" y="437" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="253.45" y="447.5" ></text>
</g>
<g >
<title>LockTagHashCode (3,240,231,810 samples, 0.03%)</title><rect x="523.1" y="453" width="0.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="526.14" y="463.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (4,224,794,037 samples, 0.04%)</title><rect x="86.4" y="293" width="0.5" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="89.39" y="303.5" ></text>
</g>
<g >
<title>palloc (1,160,912,755 samples, 0.01%)</title><rect x="1019.1" y="309" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1022.06" y="319.5" ></text>
</g>
<g >
<title>IncrTupleDescRefCount (2,437,231,840 samples, 0.03%)</title><rect x="446.4" y="373" width="0.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="449.41" y="383.5" ></text>
</g>
<g >
<title>preprocess_targetlist (38,489,371,642 samples, 0.41%)</title><rect x="920.4" y="485" width="4.7" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="923.35" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (997,464,857 samples, 0.01%)</title><rect x="815.2" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="818.17" y="399.5" ></text>
</g>
<g >
<title>BufferIsLockedByMeInMode (1,692,154,102 samples, 0.02%)</title><rect x="393.4" y="309" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="396.35" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,139,686,251 samples, 0.01%)</title><rect x="975.6" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="978.63" y="383.5" ></text>
</g>
<g >
<title>ProcessQuery (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="629" width="6.9" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="98.68" y="639.5" ></text>
</g>
<g >
<title>relation_excluded_by_constraints (2,171,561,684 samples, 0.02%)</title><rect x="1026.8" y="421" width="0.2" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="1029.76" y="431.5" ></text>
</g>
<g >
<title>palloc (1,212,886,658 samples, 0.01%)</title><rect x="874.4" y="533" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="877.38" y="543.5" ></text>
</g>
<g >
<title>fetch_att (874,816,361 samples, 0.01%)</title><rect x="336.2" y="197" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="339.15" y="207.5" ></text>
</g>
<g >
<title>palloc0 (2,727,165,008 samples, 0.03%)</title><rect x="423.1" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="426.09" y="367.5" ></text>
</g>
<g >
<title>StartReadBuffer (21,917,682,525 samples, 0.23%)</title><rect x="407.1" y="341" width="2.8" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="410.14" y="351.5" ></text>
</g>
<g >
<title>btint4cmp (3,472,786,044 samples, 0.04%)</title><rect x="318.8" y="213" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="321.79" y="223.5" ></text>
</g>
<g >
<title>heap_hot_search_buffer (64,597,736,793 samples, 0.68%)</title><rect x="303.0" y="261" width="8.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="306.04" y="271.5" ></text>
</g>
<g >
<title>down_write (814,601,374 samples, 0.01%)</title><rect x="501.6" y="373" width="0.1" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="504.55" y="383.5" ></text>
</g>
<g >
<title>sock_wfree (8,196,218,826 samples, 0.09%)</title><rect x="183.1" y="325" width="1.0" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="186.08" y="335.5" ></text>
</g>
<g >
<title>btcost_correlation (79,380,228,677 samples, 0.84%)</title><rect x="999.9" y="309" width="9.8" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="1002.86" y="319.5" ></text>
</g>
<g >
<title>MemoryContextCheck (4,312,347,325 samples, 0.05%)</title><rect x="78.6" y="757" width="0.6" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="81.63" y="767.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,008,083,031 samples, 0.01%)</title><rect x="972.2" y="373" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="975.21" y="383.5" ></text>
</g>
<g >
<title>new_list (4,458,984,007 samples, 0.05%)</title><rect x="1017.2" y="341" width="0.6" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1020.24" y="351.5" ></text>
</g>
<g >
<title>EvalPlanQualEnd (943,255,014 samples, 0.01%)</title><rect x="240.5" y="453" width="0.1" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="243.46" y="463.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,200,227,702 samples, 0.01%)</title><rect x="828.9" y="309" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="831.90" y="319.5" ></text>
</g>
<g >
<title>palloc0 (1,678,323,907 samples, 0.02%)</title><rect x="975.3" y="405" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="978.26" y="415.5" ></text>
</g>
<g >
<title>CheckExprStillValid (23,265,093,794 samples, 0.24%)</title><rect x="285.9" y="293" width="2.9" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="288.86" y="303.5" ></text>
</g>
<g >
<title>relation_close (1,711,933,364 samples, 0.02%)</title><rect x="1066.0" y="469" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="1069.03" y="479.5" ></text>
</g>
<g >
<title>palloc (1,009,733,911 samples, 0.01%)</title><rect x="971.5" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="974.47" y="367.5" ></text>
</g>
<g >
<title>GetSnapshotData (26,463,605,531 samples, 0.28%)</title><rect x="231.4" y="533" width="3.3" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="234.39" y="543.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (1,548,717,501 samples, 0.02%)</title><rect x="201.7" y="309" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="204.66" y="319.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,188,806,025 samples, 0.01%)</title><rect x="925.0" y="389" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="927.97" y="399.5" ></text>
</g>
<g >
<title>XLogRecPtrToBytePos (1,109,790,295 samples, 0.01%)</title><rect x="388.0" y="293" width="0.2" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="391.04" y="303.5" ></text>
</g>
<g >
<title>lcons (3,310,137,003 samples, 0.03%)</title><rect x="943.9" y="405" width="0.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="946.85" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberBuffer (945,101,750 samples, 0.01%)</title><rect x="98.7" y="149" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="101.69" y="159.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="613" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.62" y="623.5" ></text>
</g>
<g >
<title>_bt_lockbuf (2,931,804,980 samples, 0.03%)</title><rect x="337.8" y="213" width="0.4" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="340.81" y="223.5" ></text>
</g>
<g >
<title>ExecProcNode (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="533" width="0.3" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="1161.90" y="543.5" ></text>
</g>
<g >
<title>tick_nohz_handler (2,470,464,007 samples, 0.03%)</title><rect x="795.8" y="437" width="0.3" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="798.77" y="447.5" ></text>
</g>
<g >
<title>intel_thermal_interrupt (897,509,062 samples, 0.01%)</title><rect x="757.6" y="469" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="760.62" y="479.5" ></text>
</g>
<g >
<title>planner (1,837,816,106 samples, 0.02%)</title><rect x="86.9" y="613" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="89.91" y="623.5" ></text>
</g>
<g >
<title>bms_del_member (2,550,043,439 samples, 0.03%)</title><rect x="994.2" y="357" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="997.22" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,494,511,907 samples, 0.02%)</title><rect x="819.9" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="822.85" y="431.5" ></text>
</g>
<g >
<title>GlobalVisTestFor (943,984,697 samples, 0.01%)</title><rect x="312.0" y="245" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="314.96" y="255.5" ></text>
</g>
<g >
<title>AtEOXact_Buffers (2,524,408,039 samples, 0.03%)</title><rect x="469.2" y="517" width="0.3" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="472.19" y="527.5" ></text>
</g>
<g >
<title>MemoryContextDelete (82,279,702,288 samples, 0.87%)</title><rect x="255.0" y="485" width="10.3" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="258.03" y="495.5" ></text>
</g>
<g >
<title>lappend (5,997,678,530 samples, 0.06%)</title><rect x="1017.1" y="357" width="0.7" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1020.06" y="367.5" ></text>
</g>
<g >
<title>list_free (2,333,142,510 samples, 0.02%)</title><rect x="403.4" y="389" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="406.41" y="399.5" ></text>
</g>
<g >
<title>gup_fast (3,744,977,719 samples, 0.04%)</title><rect x="489.6" y="277" width="0.5" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="492.60" y="287.5" ></text>
</g>
<g >
<title>RelationGetIndexAttOptions (2,580,931,345 samples, 0.03%)</title><rect x="930.4" y="405" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="933.40" y="415.5" ></text>
</g>
<g >
<title>SearchSysCache3 (879,107,096 samples, 0.01%)</title><rect x="95.1" y="757" width="0.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="98.14" y="767.5" ></text>
</g>
<g >
<title>fetch_upper_rel (1,239,476,320 samples, 0.01%)</title><rect x="894.2" y="517" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="897.24" y="527.5" ></text>
</g>
<g >
<title>ExecScanFetch (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="389" width="1.9" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="126.99" y="399.5" ></text>
</g>
<g >
<title>transformUpdateStmt (429,201,329,475 samples, 4.52%)</title><rect x="805.2" y="501" width="53.3" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="808.17" y="511.5" >trans..</text>
</g>
<g >
<title>pg_rotate_left32 (1,774,753,089 samples, 0.02%)</title><rect x="1144.3" y="741" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1147.31" y="751.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (4,053,923,908 samples, 0.04%)</title><rect x="276.5" y="373" width="0.5" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="279.45" y="383.5" ></text>
</g>
<g >
<title>tag_hash (2,814,909,912 samples, 0.03%)</title><rect x="100.2" y="149" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="103.17" y="159.5" ></text>
</g>
<g >
<title>RelationClose (899,389,415 samples, 0.01%)</title><rect x="237.6" y="437" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="240.65" y="447.5" ></text>
</g>
<g >
<title>ExecScanExtended (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="437" width="2.6" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="87.32" y="447.5" ></text>
</g>
<g >
<title>_bt_compare (52,604,939,067 samples, 0.55%)</title><rect x="316.8" y="245" width="6.5" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="319.77" y="255.5" ></text>
</g>
<g >
<title>replace_nestloop_params (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="421" width="0.2" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="89.91" y="431.5" ></text>
</g>
<g >
<title>bms_del_member (2,978,723,760 samples, 0.03%)</title><rect x="1068.6" y="485" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1071.59" y="495.5" ></text>
</g>
<g >
<title>pfree (2,471,686,914 samples, 0.03%)</title><rect x="515.3" y="501" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="518.26" y="511.5" ></text>
</g>
<g >
<title>LWLockWaitListLock (808,587,696 samples, 0.01%)</title><rect x="482.1" y="453" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="485.10" y="463.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,760,531,233 samples, 0.03%)</title><rect x="1030.2" y="229" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1033.17" y="239.5" ></text>
</g>
<g >
<title>assign_collations_walker (6,096,974,260 samples, 0.06%)</title><rect x="810.2" y="325" width="0.8" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="813.24" y="335.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,655,518,961 samples, 0.02%)</title><rect x="409.5" y="277" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="412.52" y="287.5" ></text>
</g>
<g >
<title>hash_initial_lookup (881,652,927 samples, 0.01%)</title><rect x="239.3" y="373" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="242.30" y="383.5" ></text>
</g>
<g >
<title>assign_special_exec_param (3,445,391,208 samples, 0.04%)</title><rect x="911.2" y="485" width="0.4" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="914.18" y="495.5" ></text>
</g>
<g >
<title>dlist_move_head (866,198,342 samples, 0.01%)</title><rect x="1133.2" y="757" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1136.23" y="767.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (9,498,815,836 samples, 0.10%)</title><rect x="11.6" y="757" width="1.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="14.63" y="767.5" ></text>
</g>
<g >
<title>ExecTypeFromTL (3,568,378,946 samples, 0.04%)</title><rect x="456.4" y="437" width="0.5" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="459.44" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,394,779,831 samples, 0.01%)</title><rect x="928.0" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="930.95" y="383.5" ></text>
</g>
<g >
<title>ExecInitNode (342,799,807,292 samples, 3.61%)</title><rect x="416.8" y="485" width="42.6" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="419.83" y="495.5" >Exec..</text>
</g>
<g >
<title>get_hash_value (3,800,301,036 samples, 0.04%)</title><rect x="407.8" y="261" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="410.75" y="271.5" ></text>
</g>
<g >
<title>PageGetItemId (2,452,993,619 samples, 0.03%)</title><rect x="116.1" y="741" width="0.3" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="119.13" y="751.5" ></text>
</g>
<g >
<title>newNode (5,065,359,064 samples, 0.05%)</title><rect x="400.5" y="357" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="403.55" y="367.5" ></text>
</g>
<g >
<title>BufferIsValid (9,769,845,352 samples, 0.10%)</title><rect x="36.1" y="757" width="1.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="39.11" y="767.5" ></text>
</g>
<g >
<title>pfree (1,379,095,530 samples, 0.01%)</title><rect x="879.2" y="469" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="882.17" y="479.5" ></text>
</g>
<g >
<title>BufferIsValid (1,400,424,238 samples, 0.01%)</title><rect x="113.8" y="741" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="116.80" y="751.5" ></text>
</g>
<g >
<title>index_close (11,066,765,995 samples, 0.12%)</title><rect x="238.4" y="453" width="1.4" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="241.42" y="463.5" ></text>
</g>
<g >
<title>GetXLogBuffer (3,822,093,160 samples, 0.04%)</title><rect x="386.9" y="293" width="0.5" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="389.95" y="303.5" ></text>
</g>
<g >
<title>_bt_getrootheight (1,156,099,782 samples, 0.01%)</title><rect x="130.3" y="757" width="0.1" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="133.27" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,155,404,518 samples, 0.02%)</title><rect x="889.1" y="293" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="892.06" y="303.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (58,932,292,539 samples, 0.62%)</title><rect x="499.6" y="453" width="7.4" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="502.65" y="463.5" ></text>
</g>
<g >
<title>hash_bytes (3,921,248,835 samples, 0.04%)</title><rect x="947.6" y="293" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="950.57" y="303.5" ></text>
</g>
<g >
<title>get_futex_key (1,894,397,668 samples, 0.02%)</title><rect x="366.9" y="133" width="0.3" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="369.93" y="143.5" ></text>
</g>
<g >
<title>get_opmethod_canorder (875,634,043 samples, 0.01%)</title><rect x="962.7" y="373" width="0.1" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="965.74" y="383.5" ></text>
</g>
<g >
<title>_raw_spin_lock (1,291,805,830 samples, 0.01%)</title><rect x="389.5" y="53" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="392.45" y="63.5" ></text>
</g>
<g >
<title>ExecutorFinish (4,449,432,917 samples, 0.05%)</title><rect x="265.8" y="533" width="0.5" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="268.76" y="543.5" ></text>
</g>
<g >
<title>set_opfuncid (953,687,427 samples, 0.01%)</title><rect x="1182.0" y="757" width="0.2" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="1185.03" y="767.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,233,442,853 samples, 0.01%)</title><rect x="1120.3" y="693" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1123.35" y="703.5" ></text>
</g>
<g >
<title>SearchSysCacheList (1,257,797,087 samples, 0.01%)</title><rect x="95.4" y="757" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="98.37" y="767.5" ></text>
</g>
<g >
<title>uint32_hash (1,372,303,028 samples, 0.01%)</title><rect x="1015.8" y="277" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1018.85" y="287.5" ></text>
</g>
<g >
<title>XLogRegisterData (1,280,229,877 samples, 0.01%)</title><rect x="394.0" y="341" width="0.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="396.97" y="351.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,177,648,321 samples, 0.02%)</title><rect x="1144.9" y="741" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1147.95" y="751.5" ></text>
</g>
<g >
<title>get_hash_entry (855,250,005 samples, 0.01%)</title><rect x="942.6" y="309" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="945.61" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,298,517,155 samples, 0.02%)</title><rect x="1119.5" y="693" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1122.53" y="703.5" ></text>
</g>
<g >
<title>AllocSetFree (1,276,455,129 samples, 0.01%)</title><rect x="461.1" y="501" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="464.05" y="511.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (991,205,549 samples, 0.01%)</title><rect x="98.8" y="165" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="101.81" y="175.5" ></text>
</g>
<g >
<title>ExecProcNode (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="517" width="1.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="126.99" y="527.5" ></text>
</g>
<g >
<title>exprTypmod (1,343,408,235 samples, 0.01%)</title><rect x="1034.9" y="229" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="1037.91" y="239.5" ></text>
</g>
<g >
<title>bms_add_members (2,305,584,635 samples, 0.02%)</title><rect x="968.6" y="373" width="0.3" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="971.58" y="383.5" ></text>
</g>
<g >
<title>lappend (3,681,894,359 samples, 0.04%)</title><rect x="953.1" y="437" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="956.13" y="447.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (3,529,119,117 samples, 0.04%)</title><rect x="394.6" y="309" width="0.4" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="397.59" y="319.5" ></text>
</g>
<g >
<title>fmgr_info (2,013,372,161 samples, 0.02%)</title><rect x="439.3" y="373" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="442.27" y="383.5" ></text>
</g>
<g >
<title>_bt_getroot (35,738,350,268 samples, 0.38%)</title><rect x="95.7" y="325" width="4.4" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="98.68" y="335.5" ></text>
</g>
<g >
<title>handle_softirqs (1,160,247,817 samples, 0.01%)</title><rect x="757.2" y="469" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="760.20" y="479.5" ></text>
</g>
<g >
<title>kmalloc_size_roundup (1,193,202,543 samples, 0.01%)</title><rect x="198.4" y="341" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="201.43" y="351.5" ></text>
</g>
<g >
<title>do_syscall_64 (58,754,055,376 samples, 0.62%)</title><rect x="499.7" y="437" width="7.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="502.67" y="447.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (934,496,892 samples, 0.01%)</title><rect x="241.8" y="389" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="244.76" y="399.5" ></text>
</g>
<g >
<title>palloc (1,274,572,324 samples, 0.01%)</title><rect x="966.1" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="969.08" y="351.5" ></text>
</g>
<g >
<title>bms_add_member (3,315,178,360 samples, 0.03%)</title><rect x="348.1" y="389" width="0.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="351.12" y="399.5" ></text>
</g>
<g >
<title>palloc0 (2,311,164,640 samples, 0.02%)</title><rect x="348.2" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="351.24" y="367.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,012,858,039 samples, 0.01%)</title><rect x="339.8" y="213" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="342.75" y="223.5" ></text>
</g>
<g >
<title>create_modifytable_path (13,887,000,742 samples, 0.15%)</title><rect x="911.7" y="485" width="1.7" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="914.72" y="495.5" ></text>
</g>
<g >
<title>gup_fast (2,742,246,924 samples, 0.03%)</title><rect x="492.2" y="293" width="0.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="495.22" y="303.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (872,995,785 samples, 0.01%)</title><rect x="29.0" y="741" width="0.2" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="32.05" y="751.5" ></text>
</g>
<g >
<title>subquery_planner (3,060,492,147 samples, 0.03%)</title><rect x="1185.1" y="757" width="0.4" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="1188.09" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,470,586,992 samples, 0.03%)</title><rect x="293.8" y="245" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="296.81" y="255.5" ></text>
</g>
<g >
<title>create_index_paths (295,340,285,105 samples, 3.11%)</title><rect x="985.3" y="405" width="36.7" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="988.27" y="415.5" >cre..</text>
</g>
<g >
<title>ExecClearTuple (12,695,250,563 samples, 0.13%)</title><rect x="249.6" y="469" width="1.5" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="252.56" y="479.5" ></text>
</g>
<g >
<title>page_verify_redirects (1,351,587,093 samples, 0.01%)</title><rect x="1149.1" y="709" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1152.10" y="719.5" ></text>
</g>
<g >
<title>fireRules (1,098,765,408 samples, 0.01%)</title><rect x="1139.3" y="757" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="1142.32" y="767.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,238,095,590 samples, 0.04%)</title><rect x="837.1" y="325" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="840.06" y="335.5" ></text>
</g>
<g >
<title>sched_tick (3,091,854,062 samples, 0.03%)</title><rect x="758.1" y="421" width="0.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="761.15" y="431.5" ></text>
</g>
<g >
<title>ExecutorEnd (232,748,182,138 samples, 2.45%)</title><rect x="236.8" y="533" width="29.0" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="239.85" y="543.5" >Ex..</text>
</g>
<g >
<title>ExecProcNodeFirst (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="501" width="1.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="126.99" y="511.5" ></text>
</g>
<g >
<title>ExecInterpExpr (964,500,540 samples, 0.01%)</title><rect x="44.8" y="757" width="0.1" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="47.76" y="767.5" ></text>
</g>
<g >
<title>native_write_msr (3,571,723,324 samples, 0.04%)</title><rect x="164.5" y="277" width="0.5" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="167.53" y="287.5" ></text>
</g>
<g >
<title>lcons (1,727,676,689 samples, 0.02%)</title><rect x="272.7" y="373" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="275.74" y="383.5" ></text>
</g>
<g >
<title>ExecScan (991,204,959 samples, 0.01%)</title><rect x="343.7" y="405" width="0.1" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="346.72" y="415.5" ></text>
</g>
<g >
<title>_bt_readpage (945,281,554 samples, 0.01%)</title><rect x="131.1" y="757" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="134.12" y="767.5" ></text>
</g>
<g >
<title>palloc0 (5,191,405,261 samples, 0.05%)</title><rect x="449.8" y="405" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="452.78" y="415.5" ></text>
</g>
<g >
<title>heap_getattr (881,646,598 samples, 0.01%)</title><rect x="1146.1" y="757" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1149.09" y="767.5" ></text>
</g>
<g >
<title>RecoveryInProgress (1,572,269,623 samples, 0.02%)</title><rect x="88.9" y="757" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="91.86" y="767.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (2,201,059,197 samples, 0.02%)</title><rect x="818.1" y="405" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="821.06" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,254,377,106 samples, 0.02%)</title><rect x="496.4" y="437" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="499.42" y="447.5" ></text>
</g>
<g >
<title>finalize_plan (27,894,257,973 samples, 0.29%)</title><rect x="879.5" y="485" width="3.5" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="882.54" y="495.5" ></text>
</g>
<g >
<title>new_list (3,469,891,824 samples, 0.04%)</title><rect x="990.8" y="341" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="993.82" y="351.5" ></text>
</g>
<g >
<title>schedule (1,025,875,864 samples, 0.01%)</title><rect x="1148.5" y="453" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1151.51" y="463.5" ></text>
</g>
<g >
<title>MemoryContextCreate (1,207,559,631 samples, 0.01%)</title><rect x="1060.1" y="437" width="0.2" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="1063.11" y="447.5" ></text>
</g>
<g >
<title>psi_group_change (6,221,037,678 samples, 0.07%)</title><rect x="167.6" y="325" width="0.7" height="15.0" fill="rgb(226,101,24)" rx="2" ry="2" />
<text  x="170.56" y="335.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (6,343,855,476 samples, 0.07%)</title><rect x="323.8" y="261" width="0.8" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="326.80" y="271.5" ></text>
</g>
<g >
<title>ComputeXidHorizons (1,553,442,582 samples, 0.02%)</title><rect x="312.4" y="197" width="0.2" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="315.37" y="207.5" ></text>
</g>
<g >
<title>slot_getsomeattrs (9,124,155,013 samples, 0.10%)</title><rect x="290.7" y="277" width="1.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="293.67" y="287.5" ></text>
</g>
<g >
<title>PinBufferForBlock (19,141,434,356 samples, 0.20%)</title><rect x="100.1" y="213" width="2.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="103.12" y="223.5" ></text>
</g>
<g >
<title>make_op (60,431,431,192 samples, 0.64%)</title><rect x="846.8" y="421" width="7.5" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="849.79" y="431.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,387,464,438 samples, 0.01%)</title><rect x="989.7" y="293" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="992.72" y="303.5" ></text>
</g>
<g >
<title>newNode (4,382,063,455 samples, 0.05%)</title><rect x="886.2" y="405" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="889.22" y="415.5" ></text>
</g>
<g >
<title>enforce_generic_type_consistency (5,388,483,649 samples, 0.06%)</title><rect x="847.3" y="405" width="0.6" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="850.27" y="415.5" ></text>
</g>
<g >
<title>check_lock_if_inplace_updateable_rel (1,338,515,829 samples, 0.01%)</title><rect x="360.4" y="373" width="0.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="363.39" y="383.5" ></text>
</g>
<g >
<title>table_block_relation_size (22,804,555,470 samples, 0.24%)</title><rect x="935.7" y="309" width="2.8" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="938.68" y="319.5" ></text>
</g>
<g >
<title>hash_search (6,835,709,819 samples, 0.07%)</title><rect x="832.4" y="389" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="835.41" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,049,547,257 samples, 0.01%)</title><rect x="522.4" y="405" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="525.44" y="415.5" ></text>
</g>
<g >
<title>newNode (4,580,540,116 samples, 0.05%)</title><rect x="893.7" y="453" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="896.65" y="463.5" ></text>
</g>
<g >
<title>tts_virtual_clear (1,139,349,603 samples, 0.01%)</title><rect x="285.5" y="325" width="0.2" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="288.52" y="335.5" ></text>
</g>
<g >
<title>mdnblocks (9,059,726,210 samples, 0.10%)</title><rect x="932.0" y="373" width="1.1" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="934.95" y="383.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,217,499,738 samples, 0.01%)</title><rect x="952.7" y="357" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="955.71" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,111,760,549 samples, 0.01%)</title><rect x="1065.4" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1068.43" y="383.5" ></text>
</g>
<g >
<title>ExecResetTupleTable (21,224,328,790 samples, 0.22%)</title><rect x="248.7" y="485" width="2.6" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="251.71" y="495.5" ></text>
</g>
<g >
<title>SysCacheGetAttr (49,089,648,764 samples, 0.52%)</title><rect x="1000.9" y="261" width="6.1" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1003.90" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,815,080,476 samples, 0.02%)</title><rect x="474.5" y="453" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="477.50" y="463.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (2,429,826,462 samples, 0.03%)</title><rect x="375.9" y="261" width="0.3" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="378.86" y="271.5" ></text>
</g>
<g >
<title>find_lateral_references (878,511,525 samples, 0.01%)</title><rect x="1138.8" y="757" width="0.2" height="15.0" fill="rgb(221,73,17)" rx="2" ry="2" />
<text  x="1141.85" y="767.5" ></text>
</g>
<g >
<title>makeTargetEntry (2,952,428,921 samples, 0.03%)</title><rect x="921.0" y="437" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="924.03" y="447.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,872,432,694 samples, 0.02%)</title><rect x="509.8" y="437" width="0.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="512.76" y="447.5" ></text>
</g>
<g >
<title>ParallelContextActive (887,882,677 samples, 0.01%)</title><rect x="83.1" y="757" width="0.1" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="86.08" y="767.5" ></text>
</g>
<g >
<title>heap_freetuple (1,723,911,010 samples, 0.02%)</title><rect x="250.8" y="437" width="0.2" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="253.82" y="447.5" ></text>
</g>
<g >
<title>_bt_checkpage (4,088,943,389 samples, 0.04%)</title><rect x="340.6" y="229" width="0.6" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="343.65" y="239.5" ></text>
</g>
<g >
<title>xas_load (4,525,555,751 samples, 0.05%)</title><rect x="505.1" y="309" width="0.6" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="508.13" y="319.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (984,537,577 samples, 0.01%)</title><rect x="1055.7" y="373" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1058.70" y="383.5" ></text>
</g>
<g >
<title>AllocSetFree (1,177,028,935 samples, 0.01%)</title><rect x="250.9" y="405" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="253.86" y="415.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,815,402,983 samples, 0.05%)</title><rect x="943.2" y="373" width="0.6" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="946.20" y="383.5" ></text>
</g>
<g >
<title>PinBuffer (2,218,922,817 samples, 0.02%)</title><rect x="355.5" y="261" width="0.3" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="358.54" y="271.5" ></text>
</g>
<g >
<title>new_list (1,658,488,557 samples, 0.02%)</title><rect x="835.3" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="838.31" y="447.5" ></text>
</g>
<g >
<title>WaitXLogInsertionsToFinish (18,940,554,903 samples, 0.20%)</title><rect x="495.5" y="485" width="2.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="498.47" y="495.5" ></text>
</g>
<g >
<title>query_tree_walker_impl (51,971,787,236 samples, 0.55%)</title><rect x="805.6" y="469" width="6.5" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="808.63" y="479.5" ></text>
</g>
<g >
<title>copy_generic_path_info (1,057,003,120 samples, 0.01%)</title><rect x="887.9" y="389" width="0.2" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="890.94" y="399.5" ></text>
</g>
<g >
<title>start_xact_command (39,073,250,501 samples, 0.41%)</title><rect x="1072.9" y="581" width="4.9" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="1075.92" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,051,584,591 samples, 0.01%)</title><rect x="883.3" y="469" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="886.32" y="479.5" ></text>
</g>
<g >
<title>list_free (1,970,918,686 samples, 0.02%)</title><rect x="992.4" y="341" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="995.45" y="351.5" ></text>
</g>
<g >
<title>heap_prune_satisfies_vacuum (3,981,835,124 samples, 0.04%)</title><rect x="1149.7" y="709" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1152.65" y="719.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,686,524,791 samples, 0.02%)</title><rect x="496.9" y="373" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="499.86" y="383.5" ></text>
</g>
<g >
<title>generic_write_check_limits (1,342,857,493 samples, 0.01%)</title><rect x="506.6" y="357" width="0.2" height="15.0" fill="rgb(206,9,2)" rx="2" ry="2" />
<text  x="509.60" y="367.5" ></text>
</g>
<g >
<title>hash_bytes (3,552,828,548 samples, 0.04%)</title><rect x="923.6" y="357" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="926.59" y="367.5" ></text>
</g>
<g >
<title>palloc0 (4,164,683,411 samples, 0.04%)</title><rect x="969.7" y="341" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="972.69" y="351.5" ></text>
</g>
<g >
<title>TransactionIdDidCommit (10,110,082,844 samples, 0.11%)</title><rect x="308.6" y="213" width="1.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="311.60" y="223.5" ></text>
</g>
<g >
<title>tag_hash (2,622,726,118 samples, 0.03%)</title><rect x="354.6" y="229" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="357.63" y="239.5" ></text>
</g>
<g >
<title>dequeue_task_fair (19,100,737,722 samples, 0.20%)</title><rect x="486.4" y="261" width="2.4" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="489.40" y="271.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,274,491,837 samples, 0.02%)</title><rect x="975.2" y="421" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="978.18" y="431.5" ></text>
</g>
<g >
<title>try_to_wake_up (1,677,676,086 samples, 0.02%)</title><rect x="389.4" y="101" width="0.3" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="392.44" y="111.5" ></text>
</g>
<g >
<title>transformExprRecurse (16,757,652,677 samples, 0.18%)</title><rect x="841.0" y="389" width="2.1" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="844.00" y="399.5" ></text>
</g>
<g >
<title>HeapTupleHeaderXminCommitted (1,525,040,061 samples, 0.02%)</title><rect x="52.1" y="757" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="55.11" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,259,913,548 samples, 0.01%)</title><rect x="446.9" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="449.88" y="367.5" ></text>
</g>
<g >
<title>hash_search (4,791,866,496 samples, 0.05%)</title><rect x="942.4" y="341" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="945.43" y="351.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (831,614,962 samples, 0.01%)</title><rect x="35.1" y="757" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="38.10" y="767.5" ></text>
</g>
<g >
<title>pull_up_subqueries (5,740,404,837 samples, 0.06%)</title><rect x="1069.8" y="501" width="0.7" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1072.80" y="511.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="485" width="2.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="87.32" y="495.5" ></text>
</g>
<g >
<title>palloc (1,899,005,744 samples, 0.02%)</title><rect x="425.0" y="309" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="427.97" y="319.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,746,733,794 samples, 0.07%)</title><rect x="947.2" y="341" width="0.9" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="950.23" y="351.5" ></text>
</g>
<g >
<title>ReleasePredicateLocks (2,123,664,785 samples, 0.02%)</title><rect x="529.6" y="485" width="0.3" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="532.61" y="495.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,280,905,085 samples, 0.01%)</title><rect x="374.2" y="245" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="377.22" y="255.5" ></text>
</g>
<g >
<title>__libc_start_main@@GLIBC_2.34 (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="741" width="950.7" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="134.98" y="751.5" >__libc_start_main@@GLIBC_2.34</text>
</g>
<g >
<title>CatalogCacheCompareTuple (911,755,927 samples, 0.01%)</title><rect x="1010.1" y="245" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1013.11" y="255.5" ></text>
</g>
<g >
<title>palloc0 (2,864,267,922 samples, 0.03%)</title><rect x="977.0" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="979.99" y="447.5" ></text>
</g>
<g >
<title>lappend (2,407,939,000 samples, 0.03%)</title><rect x="815.0" y="437" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="818.01" y="447.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (7,373,745,227 samples, 0.08%)</title><rect x="955.1" y="373" width="0.9" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="958.13" y="383.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (956,410,952 samples, 0.01%)</title><rect x="33.2" y="757" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="36.25" y="767.5" ></text>
</g>
<g >
<title>AtStart_Cache (3,694,074,083 samples, 0.04%)</title><rect x="1073.9" y="533" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1076.88" y="543.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="453" width="1.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="126.99" y="463.5" ></text>
</g>
<g >
<title>XLogRegisterBuffer (12,747,522,322 samples, 0.13%)</title><rect x="392.4" y="341" width="1.6" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="395.39" y="351.5" ></text>
</g>
<g >
<title>XLogBytePosToRecPtr (1,213,074,245 samples, 0.01%)</title><rect x="387.9" y="293" width="0.1" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="390.89" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,173,228,450 samples, 0.01%)</title><rect x="976.7" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="979.75" y="383.5" ></text>
</g>
<g >
<title>datumIsEqual (1,982,492,994 samples, 0.02%)</title><rect x="1131.5" y="757" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1134.51" y="767.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (11,303,820,152 samples, 0.12%)</title><rect x="85.0" y="213" width="1.4" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="87.99" y="223.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,257,540,797 samples, 0.01%)</title><rect x="959.4" y="293" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="962.44" y="303.5" ></text>
</g>
<g >
<title>AdvanceXLInsertBuffer (847,641,860 samples, 0.01%)</title><rect x="387.3" y="277" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="390.27" y="287.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (1,006,114,033 samples, 0.01%)</title><rect x="940.1" y="357" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="943.09" y="367.5" ></text>
</g>
<g >
<title>FreeExprContext (27,974,233,542 samples, 0.29%)</title><rect x="251.6" y="485" width="3.4" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="254.56" y="495.5" ></text>
</g>
<g >
<title>castNodeImpl (1,047,588,174 samples, 0.01%)</title><rect x="906.8" y="501" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="909.82" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,128,746,448 samples, 0.01%)</title><rect x="914.6" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="917.60" y="431.5" ></text>
</g>
<g >
<title>string_hash (2,166,069,554 samples, 0.02%)</title><rect x="217.2" y="549" width="0.3" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="220.25" y="559.5" ></text>
</g>
<g >
<title>LockHeldByMe (13,208,571,679 samples, 0.14%)</title><rect x="830.5" y="389" width="1.7" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="833.51" y="399.5" ></text>
</g>
<g >
<title>CacheInvalidateHeapTuple (1,199,489,458 samples, 0.01%)</title><rect x="363.9" y="357" width="0.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="366.86" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (2,697,557,875 samples, 0.03%)</title><rect x="263.3" y="389" width="0.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="266.27" y="399.5" ></text>
</g>
<g >
<title>x64_sys_call (1,941,111,757 samples, 0.02%)</title><rect x="177.2" y="437" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="180.23" y="447.5" ></text>
</g>
<g >
<title>assign_collations_walker (1,173,662,217 samples, 0.01%)</title><rect x="808.5" y="373" width="0.1" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="811.46" y="383.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (4,660,528,515 samples, 0.05%)</title><rect x="1056.2" y="421" width="0.6" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1059.18" y="431.5" ></text>
</g>
<g >
<title>slab_update_freelist.isra.0 (1,037,946,447 samples, 0.01%)</title><rect x="182.8" y="309" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="185.85" y="319.5" ></text>
</g>
<g >
<title>ReadyForQuery (172,066,123,498 samples, 1.81%)</title><rect x="188.6" y="597" width="21.4" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="191.62" y="607.5" >R..</text>
</g>
<g >
<title>bms_union (2,360,770,743 samples, 0.02%)</title><rect x="966.0" y="373" width="0.3" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="969.00" y="383.5" ></text>
</g>
<g >
<title>update_ps_display_precheck (1,298,849,205 samples, 0.01%)</title><rect x="1082.5" y="565" width="0.2" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1085.52" y="575.5" ></text>
</g>
<g >
<title>copy_folio_from_iter_atomic (13,041,680,307 samples, 0.14%)</title><rect x="502.8" y="357" width="1.6" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="505.78" y="367.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (924,700,544 samples, 0.01%)</title><rect x="297.8" y="213" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="300.84" y="223.5" ></text>
</g>
<g >
<title>exprCollation (1,175,629,326 samples, 0.01%)</title><rect x="810.5" y="309" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="813.48" y="319.5" ></text>
</g>
<g >
<title>CreateCommandTag (3,158,677,723 samples, 0.03%)</title><rect x="212.3" y="581" width="0.4" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="215.29" y="591.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (7,410,173,818 samples, 0.08%)</title><rect x="757.7" y="533" width="1.0" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="760.74" y="543.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,849,967,068 samples, 0.02%)</title><rect x="924.7" y="405" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="927.73" y="415.5" ></text>
</g>
<g >
<title>sysvec_thermal (860,527,632 samples, 0.01%)</title><rect x="796.1" y="501" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="799.13" y="511.5" ></text>
</g>
<g >
<title>pgstat_count_slru_blocks_hit (1,226,248,395 samples, 0.01%)</title><rect x="480.0" y="421" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="482.99" y="431.5" ></text>
</g>
<g >
<title>exec_simple_query (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="613" width="2.2" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="129.31" y="623.5" ></text>
</g>
<g >
<title>transformExprRecurse (33,578,355,930 samples, 0.35%)</title><rect x="854.3" y="421" width="4.2" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="857.30" y="431.5" ></text>
</g>
<g >
<title>smgrnblocks_cached (1,248,231,399 samples, 0.01%)</title><rect x="938.4" y="277" width="0.1" height="15.0" fill="rgb(243,175,41)" rx="2" ry="2" />
<text  x="941.36" y="287.5" ></text>
</g>
<g >
<title>get_typavgwidth (6,544,022,737 samples, 0.07%)</title><rect x="1041.3" y="373" width="0.8" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="1044.29" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,017,359,513 samples, 0.01%)</title><rect x="450.3" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="453.29" y="399.5" ></text>
</g>
<g >
<title>AllocSetFree (1,587,809,962 samples, 0.02%)</title><rect x="988.0" y="357" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="990.97" y="367.5" ></text>
</g>
<g >
<title>core_yylex_init (6,266,221,298 samples, 0.07%)</title><rect x="872.7" y="533" width="0.8" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="875.69" y="543.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,097,269,834 samples, 0.01%)</title><rect x="902.0" y="357" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="904.97" y="367.5" ></text>
</g>
<g >
<title>estimate_rel_size (30,356,564,124 samples, 0.32%)</title><rect x="935.1" y="405" width="3.7" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="938.05" y="415.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (3,235,204,720 samples, 0.03%)</title><rect x="489.7" y="261" width="0.4" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="492.66" y="271.5" ></text>
</g>
<g >
<title>index_getattr (1,304,462,264 samples, 0.01%)</title><rect x="125.6" y="245" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="128.60" y="255.5" ></text>
</g>
<g >
<title>hash_bytes (2,456,804,720 samples, 0.03%)</title><rect x="354.6" y="213" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="357.65" y="223.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (1,481,688,423 samples, 0.02%)</title><rect x="282.3" y="277" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="285.26" y="287.5" ></text>
</g>
<g >
<title>_bt_moveright (1,304,462,264 samples, 0.01%)</title><rect x="125.6" y="277" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="128.60" y="287.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (923,554,004 samples, 0.01%)</title><rect x="436.3" y="373" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="439.32" y="383.5" ></text>
</g>
<g >
<title>attnumTypeId (950,058,379 samples, 0.01%)</title><rect x="843.6" y="437" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="846.59" y="447.5" ></text>
</g>
<g >
<title>relation_open (21,036,878,955 samples, 0.22%)</title><rect x="451.8" y="405" width="2.6" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="454.82" y="415.5" ></text>
</g>
<g >
<title>palloc (976,018,637 samples, 0.01%)</title><rect x="909.2" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="912.24" y="431.5" ></text>
</g>
<g >
<title>mutex_lock (1,755,714,939 samples, 0.02%)</title><rect x="185.0" y="373" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="188.02" y="383.5" ></text>
</g>
<g >
<title>string_hash (3,038,797,296 samples, 0.03%)</title><rect x="214.6" y="533" width="0.3" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="217.56" y="543.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (909,374,906 samples, 0.01%)</title><rect x="259.8" y="437" width="0.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="262.81" y="447.5" ></text>
</g>
<g >
<title>ExecScan (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="453" width="2.6" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="87.32" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,317,387,065 samples, 0.02%)</title><rect x="830.7" y="341" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="833.74" y="351.5" ></text>
</g>
<g >
<title>sched_balance_newidle (3,687,411,645 samples, 0.04%)</title><rect x="162.9" y="309" width="0.5" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="165.94" y="319.5" ></text>
</g>
<g >
<title>ExecInitExprRec (1,621,877,376 samples, 0.02%)</title><rect x="427.7" y="325" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="430.71" y="335.5" ></text>
</g>
<g >
<title>eqsel (1,798,925,613 samples, 0.02%)</title><rect x="1134.4" y="757" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1137.36" y="767.5" ></text>
</g>
<g >
<title>PageGetItem (835,427,416 samples, 0.01%)</title><rect x="382.4" y="341" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="385.38" y="351.5" ></text>
</g>
<g >
<title>assign_expr_collations (17,559,728,273 samples, 0.18%)</title><rect x="806.4" y="437" width="2.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="809.44" y="447.5" ></text>
</g>
<g >
<title>PinBuffer (9,050,426,698 samples, 0.10%)</title><rect x="97.9" y="181" width="1.1" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="100.85" y="191.5" ></text>
</g>
<g >
<title>ExecScanExtended (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="421" width="0.3" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1161.90" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (966,676,862 samples, 0.01%)</title><rect x="125.5" y="181" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="128.48" y="191.5" ></text>
</g>
<g >
<title>QueryRewrite (1,135,813,297 samples, 0.01%)</title><rect x="88.1" y="757" width="0.2" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="91.13" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,028,776,699 samples, 0.01%)</title><rect x="126.2" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="129.17" y="399.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (10,991,958,811 samples, 0.12%)</title><rect x="947.2" y="373" width="1.3" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="950.16" y="383.5" ></text>
</g>
<g >
<title>make_const (3,858,307,067 samples, 0.04%)</title><rect x="841.2" y="373" width="0.4" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="844.15" y="383.5" ></text>
</g>
<g >
<title>gup_fast (1,594,836,564 samples, 0.02%)</title><rect x="367.0" y="101" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="369.97" y="111.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,749,918,609 samples, 0.02%)</title><rect x="366.0" y="261" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="368.96" y="271.5" ></text>
</g>
<g >
<title>BufferGetBlock (931,566,225 samples, 0.01%)</title><rect x="328.6" y="213" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="331.63" y="223.5" ></text>
</g>
<g >
<title>palloc0 (4,103,869,676 samples, 0.04%)</title><rect x="966.6" y="357" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="969.62" y="367.5" ></text>
</g>
<g >
<title>_bt_readnextpage (4,291,360,124 samples, 0.05%)</title><rect x="283.6" y="261" width="0.6" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="286.64" y="271.5" ></text>
</g>
<g >
<title>btgettuple (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="325" width="1.9" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="126.99" y="335.5" ></text>
</g>
<g >
<title>StartReadBuffer (27,698,801,440 samples, 0.29%)</title><rect x="299.6" y="197" width="3.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="302.56" y="207.5" ></text>
</g>
<g >
<title>prepare_task_switch (2,861,230,978 samples, 0.03%)</title><rect x="485.1" y="277" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="488.09" y="287.5" ></text>
</g>
<g >
<title>match_clause_to_index (26,180,917,385 samples, 0.28%)</title><rect x="1018.7" y="357" width="3.3" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1021.70" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,651,794,084 samples, 0.05%)</title><rect x="104.1" y="453" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="107.11" y="463.5" ></text>
</g>
<g >
<title>futex_wake_mark (942,008,586 samples, 0.01%)</title><rect x="492.0" y="325" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="495.00" y="335.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableXid (3,922,568,998 samples, 0.04%)</title><rect x="312.1" y="245" width="0.5" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="315.08" y="255.5" ></text>
</g>
<g >
<title>futex_do_wait (1,131,286,277 samples, 0.01%)</title><rect x="496.9" y="309" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="499.88" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,710,008,929 samples, 0.02%)</title><rect x="413.4" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="416.36" y="463.5" ></text>
</g>
<g >
<title>ExecClearTuple (4,983,464,416 samples, 0.05%)</title><rect x="281.9" y="341" width="0.7" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="284.93" y="351.5" ></text>
</g>
<g >
<title>palloc0 (1,712,444,720 samples, 0.02%)</title><rect x="818.9" y="421" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="821.86" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,442,668,614 samples, 0.02%)</title><rect x="430.7" y="325" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="433.68" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,049,279,697 samples, 0.01%)</title><rect x="914.8" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="917.84" y="431.5" ></text>
</g>
<g >
<title>__strncmp_avx2 (1,551,431,799 samples, 0.02%)</title><rect x="827.6" y="277" width="0.2" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="830.59" y="287.5" ></text>
</g>
<g >
<title>new_list (3,343,204,781 samples, 0.04%)</title><rect x="891.0" y="357" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="894.04" y="367.5" ></text>
</g>
<g >
<title>reconsider_outer_join_clauses (3,317,749,848 samples, 0.03%)</title><rect x="1042.4" y="469" width="0.4" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="1045.37" y="479.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (933,752,216 samples, 0.01%)</title><rect x="522.6" y="405" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="525.62" y="415.5" ></text>
</g>
<g >
<title>_bt_search (9,265,348,929 samples, 0.10%)</title><rect x="124.8" y="293" width="1.1" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="127.77" y="303.5" ></text>
</g>
<g >
<title>DeconstructQualifiedName (1,772,113,138 samples, 0.02%)</title><rect x="853.4" y="373" width="0.3" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="856.44" y="383.5" ></text>
</g>
<g >
<title>__ieee754_log_fma (2,969,471,947 samples, 0.03%)</title><rect x="998.9" y="309" width="0.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="1001.92" y="319.5" ></text>
</g>
<g >
<title>internal_putbytes (2,004,423,578 samples, 0.02%)</title><rect x="189.9" y="549" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="192.88" y="559.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,035,936,813 samples, 0.01%)</title><rect x="430.1" y="357" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="433.13" y="367.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (4,191,098,032 samples, 0.04%)</title><rect x="507.7" y="421" width="0.5" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="510.67" y="431.5" ></text>
</g>
<g >
<title>unix_maybe_add_creds (1,944,111,981 samples, 0.02%)</title><rect x="208.5" y="405" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="211.49" y="415.5" ></text>
</g>
<g >
<title>palloc0 (4,699,083,864 samples, 0.05%)</title><rect x="400.6" y="341" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="403.59" y="351.5" ></text>
</g>
<g >
<title>relation_open (23,855,348,349 samples, 0.25%)</title><rect x="830.4" y="421" width="2.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="833.36" y="431.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,832,554,424 samples, 0.02%)</title><rect x="1145.5" y="741" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1148.48" y="751.5" ></text>
</g>
<g >
<title>rseq_update_cpu_node_id (1,067,821,846 samples, 0.01%)</title><rect x="176.9" y="405" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="179.92" y="415.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (2,777,553,854 samples, 0.03%)</title><rect x="389.3" y="213" width="0.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="392.31" y="223.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (983,169,158 samples, 0.01%)</title><rect x="369.6" y="245" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="372.64" y="255.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (4,865,099,078 samples, 0.05%)</title><rect x="1051.3" y="437" width="0.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1054.27" y="447.5" ></text>
</g>
<g >
<title>__put_user_nocheck_4 (2,241,941,927 samples, 0.02%)</title><rect x="158.3" y="373" width="0.3" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="161.28" y="383.5" ></text>
</g>
<g >
<title>preprocess_expression (2,600,633,993 samples, 0.03%)</title><rect x="1185.1" y="741" width="0.3" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1188.12" y="751.5" ></text>
</g>
<g >
<title>AllocSetFree (1,177,480,530 samples, 0.01%)</title><rect x="323.4" y="229" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="326.43" y="239.5" ></text>
</g>
<g >
<title>tag_hash (4,614,334,974 samples, 0.05%)</title><rect x="452.5" y="325" width="0.6" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="455.53" y="335.5" ></text>
</g>
<g >
<title>set_plain_rel_size (121,262,703,836 samples, 1.28%)</title><rect x="1027.0" y="421" width="15.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="1030.04" y="431.5" ></text>
</g>
<g >
<title>standard_planner (1,575,179,795,911 samples, 16.58%)</title><rect x="875.4" y="533" width="195.7" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="878.41" y="543.5" >standard_planner</text>
</g>
<g >
<title>ExecShutdownNode_walker (3,525,774,931 samples, 0.04%)</title><rect x="410.5" y="437" width="0.5" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="413.54" y="447.5" ></text>
</g>
<g >
<title>pq_getbytes (4,042,898,642 samples, 0.04%)</title><rect x="188.0" y="549" width="0.5" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="190.96" y="559.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (971,078,648 samples, 0.01%)</title><rect x="128.4" y="421" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="131.36" y="431.5" ></text>
</g>
<g >
<title>ExecAssignExprContext (10,853,675,765 samples, 0.11%)</title><rect x="422.1" y="421" width="1.3" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="425.09" y="431.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (2,290,634,572 samples, 0.02%)</title><rect x="509.5" y="437" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="512.46" y="447.5" ></text>
</g>
<g >
<title>FileSize (7,909,773,044 samples, 0.08%)</title><rect x="932.0" y="341" width="1.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="935.04" y="351.5" ></text>
</g>
<g >
<title>fdatasync (7,614,223,250 samples, 0.08%)</title><rect x="507.3" y="437" width="0.9" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="510.29" y="447.5" ></text>
</g>
<g >
<title>shmem_get_folio_gfp (10,184,421,025 samples, 0.11%)</title><rect x="504.5" y="341" width="1.3" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="507.52" y="351.5" ></text>
</g>
<g >
<title>uint32_hash (1,596,056,009 samples, 0.02%)</title><rect x="1065.6" y="453" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1068.64" y="463.5" ></text>
</g>
<g >
<title>tag_hash (2,588,232,114 samples, 0.03%)</title><rect x="451.3" y="373" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="454.29" y="383.5" ></text>
</g>
<g >
<title>get_attavgwidth (8,765,545,611 samples, 0.09%)</title><rect x="1040.2" y="373" width="1.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="1043.20" y="383.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,375,195,855 samples, 0.01%)</title><rect x="822.7" y="373" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="825.65" y="383.5" ></text>
</g>
<g >
<title>list_free_private (1,594,111,789 samples, 0.02%)</title><rect x="953.9" y="437" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="956.92" y="447.5" ></text>
</g>
<g >
<title>create_lateral_join_info (1,249,679,691 samples, 0.01%)</title><rect x="956.0" y="469" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="959.04" y="479.5" ></text>
</g>
<g >
<title>IndexNext (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="373" width="1.9" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="126.99" y="383.5" ></text>
</g>
<g >
<title>palloc (1,750,682,771 samples, 0.02%)</title><rect x="985.0" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="988.01" y="351.5" ></text>
</g>
<g >
<title>relation_open (16,045,980,953 samples, 0.17%)</title><rect x="923.1" y="453" width="2.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="926.13" y="463.5" ></text>
</g>
<g >
<title>IndexNext (22,572,743,756 samples, 0.24%)</title><rect x="281.9" y="357" width="2.8" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="284.87" y="367.5" ></text>
</g>
<g >
<title>ExecAssignProjectionInfo (70,891,170,959 samples, 0.75%)</title><rect x="423.6" y="389" width="8.8" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="426.60" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,405,089,711 samples, 0.01%)</title><rect x="835.8" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="838.83" y="399.5" ></text>
</g>
<g >
<title>hash_initial_lookup (807,343,724 samples, 0.01%)</title><rect x="869.8" y="437" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="872.84" y="447.5" ></text>
</g>
<g >
<title>palloc (3,215,963,744 samples, 0.03%)</title><rect x="1017.3" y="325" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1020.34" y="335.5" ></text>
</g>
<g >
<title>_bt_moveright (10,088,207,313 samples, 0.11%)</title><rect x="338.2" y="245" width="1.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="341.19" y="255.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,817,023,752 samples, 0.02%)</title><rect x="1136.4" y="757" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1139.37" y="767.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,064,060,014 samples, 0.01%)</title><rect x="864.2" y="437" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="867.22" y="447.5" ></text>
</g>
<g >
<title>palloc (943,425,543 samples, 0.01%)</title><rect x="992.8" y="309" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="995.82" y="319.5" ></text>
</g>
<g >
<title>decimalLength64 (2,751,657,249 samples, 0.03%)</title><rect x="218.5" y="533" width="0.3" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="221.49" y="543.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,120,285,893 samples, 0.01%)</title><rect x="102.0" y="165" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="104.97" y="175.5" ></text>
</g>
<g >
<title>palloc (1,208,629,233 samples, 0.01%)</title><rect x="459.1" y="453" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="462.12" y="463.5" ></text>
</g>
<g >
<title>heap_fill_tuple (8,908,565,962 samples, 0.09%)</title><rect x="397.7" y="357" width="1.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="400.75" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,161,100,655 samples, 0.01%)</title><rect x="889.2" y="277" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="892.19" y="287.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u64_impl (873,931,247 samples, 0.01%)</title><rect x="1167.9" y="757" width="0.1" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="1170.94" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,141,753,524 samples, 0.05%)</title><rect x="415.3" y="421" width="0.7" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="418.35" y="431.5" ></text>
</g>
<g >
<title>pg_plan_queries (1,837,816,106 samples, 0.02%)</title><rect x="86.9" y="645" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="89.91" y="655.5" ></text>
</g>
<g >
<title>palloc (1,595,252,831 samples, 0.02%)</title><rect x="993.9" y="325" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="996.92" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,233,829,423 samples, 0.01%)</title><rect x="282.3" y="261" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="285.29" y="271.5" ></text>
</g>
<g >
<title>ExecProcNode (1,047,626,423 samples, 0.01%)</title><rect x="266.9" y="501" width="0.2" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="269.93" y="511.5" ></text>
</g>
<g >
<title>ResourceOwnerRelease (14,629,585,902 samples, 0.15%)</title><rect x="226.8" y="565" width="1.8" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="229.77" y="575.5" ></text>
</g>
<g >
<title>pfree (1,185,464,010 samples, 0.01%)</title><rect x="254.4" y="389" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="257.38" y="399.5" ></text>
</g>
<g >
<title>ReadBuffer (18,909,736,002 samples, 0.20%)</title><rect x="353.9" y="373" width="2.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="356.94" y="383.5" ></text>
</g>
<g >
<title>newNode (3,961,567,553 samples, 0.04%)</title><rect x="1116.6" y="725" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1119.62" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,656,680,946 samples, 0.03%)</title><rect x="112.6" y="741" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="115.62" y="751.5" ></text>
</g>
<g >
<title>palloc0 (1,775,498,101 samples, 0.02%)</title><rect x="979.4" y="421" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="982.39" y="431.5" ></text>
</g>
<g >
<title>bms_free (2,839,920,246 samples, 0.03%)</title><rect x="995.2" y="341" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="998.16" y="351.5" ></text>
</g>
<g >
<title>AllocSetFree (2,082,255,245 samples, 0.02%)</title><rect x="803.2" y="517" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="806.16" y="527.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,393,212,510 samples, 0.04%)</title><rect x="522.3" y="437" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="525.32" y="447.5" ></text>
</g>
<g >
<title>ExecProcNode (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="501" width="2.6" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="87.32" y="511.5" ></text>
</g>
<g >
<title>transformUpdateStmt (1,156,482,497 samples, 0.01%)</title><rect x="1187.7" y="757" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1190.74" y="767.5" ></text>
</g>
<g >
<title>MultiXactIdSetOldestMember (6,219,971,333 samples, 0.07%)</title><rect x="377.2" y="357" width="0.7" height="15.0" fill="rgb(220,73,17)" rx="2" ry="2" />
<text  x="380.15" y="367.5" ></text>
</g>
<g >
<title>fastgetattr (4,028,483,796 samples, 0.04%)</title><rect x="360.7" y="357" width="0.5" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="363.67" y="367.5" ></text>
</g>
<g >
<title>MaintainLatestCompletedXid (2,524,355,100 samples, 0.03%)</title><rect x="475.2" y="485" width="0.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="478.19" y="495.5" ></text>
</g>
<g >
<title>bms_union (3,263,029,955 samples, 0.03%)</title><rect x="957.1" y="453" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="960.05" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32_impl (1,099,248,456 samples, 0.01%)</title><rect x="368.4" y="245" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="371.37" y="255.5" ></text>
</g>
<g >
<title>BackendStartup (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="725" width="2.8" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="87.32" y="735.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (943,148,909 samples, 0.01%)</title><rect x="218.2" y="549" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="221.18" y="559.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,502,745,256 samples, 0.07%)</title><rect x="923.2" y="405" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="926.23" y="415.5" ></text>
</g>
<g >
<title>addRangeTableEntryForRelation (48,012,681,730 samples, 0.51%)</title><rect x="814.1" y="469" width="5.9" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="817.08" y="479.5" ></text>
</g>
<g >
<title>_bt_compare (3,262,946,556 samples, 0.03%)</title><rect x="124.8" y="261" width="0.4" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="127.77" y="271.5" ></text>
</g>
<g >
<title>GetCommandTagNameAndLen (1,892,544,427 samples, 0.02%)</title><rect x="217.9" y="549" width="0.3" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="220.95" y="559.5" ></text>
</g>
<g >
<title>preprocess_expression (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="581" width="3.4" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="105.66" y="591.5" ></text>
</g>
<g >
<title>palloc (1,133,717,239 samples, 0.01%)</title><rect x="897.7" y="437" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="900.65" y="447.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,304,462,264 samples, 0.01%)</title><rect x="125.6" y="229" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="128.60" y="239.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (836,304,340 samples, 0.01%)</title><rect x="1032.6" y="165" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1035.60" y="175.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,535,033,158 samples, 0.02%)</title><rect x="829.9" y="277" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="832.91" y="287.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,325,301,701 samples, 0.01%)</title><rect x="928.2" y="421" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="931.20" y="431.5" ></text>
</g>
<g >
<title>cost_qual_eval_node (1,800,746,918 samples, 0.02%)</title><rect x="1013.1" y="277" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1016.06" y="287.5" ></text>
</g>
<g >
<title>ItemPointerGetOffsetNumber (1,152,855,994 samples, 0.01%)</title><rect x="310.4" y="245" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="313.45" y="255.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (892,186,450 samples, 0.01%)</title><rect x="896.5" y="469" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="899.48" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (4,613,528,055 samples, 0.05%)</title><rect x="366.6" y="245" width="0.6" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="369.64" y="255.5" ></text>
</g>
<g >
<title>index_close (1,715,169,845 samples, 0.02%)</title><rect x="940.0" y="405" width="0.2" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="943.01" y="415.5" ></text>
</g>
<g >
<title>palloc0 (3,162,858,751 samples, 0.03%)</title><rect x="974.8" y="389" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="977.79" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,105,434,880 samples, 0.01%)</title><rect x="447.2" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="450.20" y="351.5" ></text>
</g>
<g >
<title>create_modifytable_path (1,144,141,636 samples, 0.01%)</title><rect x="1130.6" y="757" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="1133.59" y="767.5" ></text>
</g>
<g >
<title>ExecIndexScan (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="421" width="1.9" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="129.31" y="431.5" ></text>
</g>
<g >
<title>filemap_get_entry (8,249,714,709 samples, 0.09%)</title><rect x="504.7" y="325" width="1.0" height="15.0" fill="rgb(246,193,46)" rx="2" ry="2" />
<text  x="507.66" y="335.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (950,700,672 samples, 0.01%)</title><rect x="1021.5" y="229" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1024.47" y="239.5" ></text>
</g>
<g >
<title>pull_varnos_walker (8,165,899,171 samples, 0.09%)</title><rect x="972.9" y="373" width="1.0" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="975.92" y="383.5" ></text>
</g>
<g >
<title>element_alloc (9,579,042,900 samples, 0.10%)</title><rect x="1064.4" y="421" width="1.2" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="1067.39" y="431.5" ></text>
</g>
<g >
<title>LWLockWakeup (3,223,314,787 samples, 0.03%)</title><rect x="389.3" y="245" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="392.27" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,099,383,352 samples, 0.01%)</title><rect x="860.1" y="469" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="863.06" y="479.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,106,979,430 samples, 0.01%)</title><rect x="939.1" y="357" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="942.11" y="367.5" ></text>
</g>
<g >
<title>set_plain_rel_pathlist (332,823,965,509 samples, 3.50%)</title><rect x="984.6" y="421" width="41.3" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="987.57" y="431.5" >set..</text>
</g>
<g >
<title>LockBuffer (1,853,327,073 samples, 0.02%)</title><rect x="127.8" y="213" width="0.2" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="130.76" y="223.5" ></text>
</g>
<g >
<title>WALInsertLockAcquire (5,136,821,178 samples, 0.05%)</title><rect x="388.5" y="309" width="0.6" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="391.46" y="319.5" ></text>
</g>
<g >
<title>nocachegetattr (45,713,740,521 samples, 0.48%)</title><rect x="1001.3" y="213" width="5.7" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="1004.32" y="223.5" ></text>
</g>
<g >
<title>socket_putmessage (5,231,324,521 samples, 0.06%)</title><rect x="218.9" y="565" width="0.6" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="221.88" y="575.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,703,100,369 samples, 0.02%)</title><rect x="897.2" y="453" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="900.22" y="463.5" ></text>
</g>
<g >
<title>subquery_planner (2,204,184,953 samples, 0.02%)</title><rect x="128.2" y="533" width="0.3" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="131.20" y="543.5" ></text>
</g>
<g >
<title>replace_nestloop_params (12,162,864,594 samples, 0.13%)</title><rect x="889.4" y="357" width="1.5" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="892.42" y="367.5" ></text>
</g>
<g >
<title>ReadCommand (841,019,835 samples, 0.01%)</title><rect x="88.5" y="757" width="0.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="91.54" y="767.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="373" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="89.91" y="383.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,156,928,373 samples, 0.01%)</title><rect x="348.0" y="357" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="350.96" y="367.5" ></text>
</g>
<g >
<title>__log_finite@GLIBC_2.15@plt (4,472,084,137 samples, 0.05%)</title><rect x="999.3" y="309" width="0.5" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="1002.29" y="319.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (1,548,598,726 samples, 0.02%)</title><rect x="490.1" y="389" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="493.07" y="399.5" ></text>
</g>
<g >
<title>get_row_security_policies (10,817,185,799 samples, 0.11%)</title><rect x="865.0" y="517" width="1.4" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="868.04" y="527.5" ></text>
</g>
<g >
<title>unix_stream_recvmsg (47,538,421,121 samples, 0.50%)</title><rect x="180.9" y="405" width="5.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="183.92" y="415.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (1,412,080,331 samples, 0.01%)</title><rect x="305.6" y="213" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="308.57" y="223.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,239,468,530 samples, 0.03%)</title><rect x="101.2" y="181" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="104.20" y="191.5" ></text>
</g>
<g >
<title>AllocSetAlloc (16,648,628,768 samples, 0.18%)</title><rect x="1048.6" y="469" width="2.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1051.64" y="479.5" ></text>
</g>
<g >
<title>transformExpr (102,336,941,746 samples, 1.08%)</title><rect x="845.8" y="469" width="12.7" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="848.76" y="479.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,537,959,094 samples, 0.02%)</title><rect x="304.1" y="245" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="307.11" y="255.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (2,717,677,953 samples, 0.03%)</title><rect x="476.8" y="485" width="0.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="479.76" y="495.5" ></text>
</g>
<g >
<title>newNode (3,273,427,976 samples, 0.03%)</title><rect x="934.6" y="373" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="937.64" y="383.5" ></text>
</g>
<g >
<title>SimpleLruReadPage (6,919,952,927 samples, 0.07%)</title><rect x="479.3" y="437" width="0.8" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="482.28" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,520,164,632 samples, 0.02%)</title><rect x="873.1" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="876.13" y="495.5" ></text>
</g>
<g >
<title>eval_const_expressions (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="565" width="3.4" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="105.66" y="575.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (14,824,560,978 samples, 0.16%)</title><rect x="451.9" y="389" width="1.8" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="454.88" y="399.5" ></text>
</g>
<g >
<title>assign_query_collations (52,222,188,760 samples, 0.55%)</title><rect x="805.6" y="485" width="6.5" height="15.0" fill="rgb(236,145,34)" rx="2" ry="2" />
<text  x="808.60" y="495.5" ></text>
</g>
<g >
<title>__sys_sendto (132,797,281,655 samples, 1.40%)</title><rect x="192.4" y="437" width="16.5" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="195.40" y="447.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (902,269,803 samples, 0.01%)</title><rect x="376.7" y="325" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="379.66" y="335.5" ></text>
</g>
<g >
<title>shmem_file_llseek (1,142,119,908 samples, 0.01%)</title><rect x="932.8" y="261" width="0.1" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="935.80" y="271.5" ></text>
</g>
<g >
<title>LockBuffer (3,012,852,234 samples, 0.03%)</title><rect x="341.2" y="213" width="0.3" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="344.17" y="223.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (2,707,317,520 samples, 0.03%)</title><rect x="825.7" y="325" width="0.3" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="828.66" y="335.5" ></text>
</g>
<g >
<title>ExtendCommitTs (2,574,577,980 samples, 0.03%)</title><rect x="365.0" y="309" width="0.3" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="367.97" y="319.5" ></text>
</g>
<g >
<title>register_seq_scan (852,136,044 samples, 0.01%)</title><rect x="473.2" y="485" width="0.1" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="476.15" y="495.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (64,664,044,868 samples, 0.68%)</title><rect x="482.7" y="437" width="8.0" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="485.72" y="447.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="533" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="89.91" y="543.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,248,726,048 samples, 0.02%)</title><rect x="869.7" y="453" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="872.66" y="463.5" ></text>
</g>
<g >
<title>CleanUpLock (9,613,407,826 samples, 0.10%)</title><rect x="518.5" y="453" width="1.2" height="15.0" fill="rgb(232,124,29)" rx="2" ry="2" />
<text  x="521.54" y="463.5" ></text>
</g>
<g >
<title>_bt_checkkeys (2,002,016,725 samples, 0.02%)</title><rect x="1158.9" y="277" width="0.3" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="1161.90" y="287.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,036,349,575 samples, 0.01%)</title><rect x="298.2" y="261" width="0.2" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="301.23" y="271.5" ></text>
</g>
<g >
<title>__schedule (1,099,952,697 samples, 0.01%)</title><rect x="496.9" y="277" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="499.88" y="287.5" ></text>
</g>
<g >
<title>palloc0 (3,581,012,093 samples, 0.04%)</title><rect x="1116.7" y="709" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1119.66" y="719.5" ></text>
</g>
<g >
<title>hash_search (6,910,862,501 samples, 0.07%)</title><rect x="373.9" y="277" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="376.87" y="287.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,175,301,248 samples, 0.01%)</title><rect x="1111.6" y="693" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1114.57" y="703.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (4,308,763,817 samples, 0.05%)</title><rect x="375.7" y="325" width="0.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="378.72" y="335.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (2,699,016,652 samples, 0.03%)</title><rect x="126.9" y="229" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="129.89" y="239.5" ></text>
</g>
<g >
<title>AllocSetCheck (1,252,055,832 samples, 0.01%)</title><rect x="225.9" y="485" width="0.1" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="228.89" y="495.5" ></text>
</g>
<g >
<title>CommitTransaction (1,707,912,138 samples, 0.02%)</title><rect x="38.8" y="757" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="41.84" y="767.5" ></text>
</g>
<g >
<title>pull_varattnos_walker (1,635,519,883 samples, 0.02%)</title><rect x="996.3" y="277" width="0.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="999.32" y="287.5" ></text>
</g>
<g >
<title>BuildUpdateIndexInfo (7,366,556,829 samples, 0.08%)</title><rect x="345.0" y="405" width="0.9" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="348.02" y="415.5" ></text>
</g>
<g >
<title>AllocSetCheck (106,507,653,084 samples, 1.12%)</title><rect x="136.5" y="549" width="13.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="139.47" y="559.5" ></text>
</g>
<g >
<title>alloc_skb_with_frags (29,928,058,260 samples, 0.32%)</title><rect x="196.7" y="389" width="3.7" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="199.66" y="399.5" ></text>
</g>
<g >
<title>LWLockAcquire (966,676,862 samples, 0.01%)</title><rect x="125.5" y="213" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="128.48" y="223.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (860,361,082 samples, 0.01%)</title><rect x="355.3" y="245" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="358.31" y="255.5" ></text>
</g>
<g >
<title>bms_copy (1,871,027,893 samples, 0.02%)</title><rect x="358.3" y="373" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="361.31" y="383.5" ></text>
</g>
<g >
<title>_bt_getroot (840,928,765 samples, 0.01%)</title><rect x="130.2" y="757" width="0.1" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="133.16" y="767.5" ></text>
</g>
<g >
<title>index_getnext_tid (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="341" width="1.9" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="126.99" y="351.5" ></text>
</g>
<g >
<title>_raw_spin_lock (1,192,291,137 samples, 0.01%)</title><rect x="165.5" y="293" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="168.51" y="303.5" ></text>
</g>
<g >
<title>relation_open (14,836,972,792 samples, 0.16%)</title><rect x="947.1" y="389" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="950.13" y="399.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,004,261,240 samples, 0.01%)</title><rect x="341.3" y="197" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="344.26" y="207.5" ></text>
</g>
<g >
<title>expand_function_arguments (1,026,391,396 samples, 0.01%)</title><rect x="1056.1" y="421" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1059.05" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (872,871,344 samples, 0.01%)</title><rect x="835.4" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="838.40" y="415.5" ></text>
</g>
<g >
<title>StartReadBuffer (1,220,476,765 samples, 0.01%)</title><rect x="125.8" y="197" width="0.1" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="128.76" y="207.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (13,454,344,990 samples, 0.14%)</title><rect x="916.6" y="437" width="1.7" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="919.60" y="447.5" ></text>
</g>
<g >
<title>AllocSetFree (1,346,959,812 samples, 0.01%)</title><rect x="1000.2" y="261" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1003.22" y="271.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,398,732,473 samples, 0.01%)</title><rect x="831.7" y="357" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="834.67" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (4,182,860,317 samples, 0.04%)</title><rect x="24.9" y="741" width="0.5" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="27.85" y="751.5" ></text>
</g>
<g >
<title>pfree (1,750,361,070 samples, 0.02%)</title><rect x="1000.2" y="277" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1003.20" y="287.5" ></text>
</g>
<g >
<title>enqueue_entity (2,221,106,605 samples, 0.02%)</title><rect x="206.6" y="277" width="0.3" height="15.0" fill="rgb(218,62,15)" rx="2" ry="2" />
<text  x="209.59" y="287.5" ></text>
</g>
<g >
<title>__schedule (105,810,841,434 samples, 1.11%)</title><rect x="161.7" y="357" width="13.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="164.73" y="367.5" ></text>
</g>
<g >
<title>SS_finalize_plan (47,533,412,020 samples, 0.50%)</title><rect x="877.2" y="517" width="5.9" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="880.22" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,119,189,571 samples, 0.01%)</title><rect x="841.5" y="309" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="844.48" y="319.5" ></text>
</g>
<g >
<title>lappend (2,549,438,143 samples, 0.03%)</title><rect x="447.0" y="389" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="450.04" y="399.5" ></text>
</g>
<g >
<title>secure_write (835,161,170 samples, 0.01%)</title><rect x="1177.8" y="757" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1180.78" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,165,371,183 samples, 0.01%)</title><rect x="457.7" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="460.74" y="415.5" ></text>
</g>
<g >
<title>do_syscall_64 (169,088,446,031 samples, 1.78%)</title><rect x="156.5" y="453" width="21.0" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="159.47" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,774,903,522 samples, 0.02%)</title><rect x="947.3" y="309" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="950.34" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (886,416,193 samples, 0.01%)</title><rect x="440.9" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="443.89" y="367.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (3,384,950,319 samples, 0.04%)</title><rect x="901.9" y="373" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="904.85" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,927,590,917 samples, 0.02%)</title><rect x="880.9" y="421" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="883.92" y="431.5" ></text>
</g>
<g >
<title>_bt_first (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="309" width="1.9" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="126.99" y="319.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (1,094,972,911 samples, 0.01%)</title><rect x="700.4" y="485" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="703.40" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,890,853,492 samples, 0.03%)</title><rect x="886.4" y="373" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="889.41" y="383.5" ></text>
</g>
<g >
<title>ExecInitResultTypeTL (36,294,385,815 samples, 0.38%)</title><rect x="441.4" y="421" width="4.6" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="444.44" y="431.5" ></text>
</g>
<g >
<title>newNode (3,496,490,167 samples, 0.04%)</title><rect x="431.9" y="357" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="434.92" y="367.5" ></text>
</g>
<g >
<title>BufferDescriptorGetBuffer (1,310,856,530 samples, 0.01%)</title><rect x="302.1" y="117" width="0.2" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="305.09" y="127.5" ></text>
</g>
<g >
<title>CreateExprContext (10,051,582,799 samples, 0.11%)</title><rect x="272.2" y="405" width="1.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="275.22" y="415.5" ></text>
</g>
<g >
<title>list_delete_nth_cell (3,900,739,408 samples, 0.04%)</title><rect x="254.1" y="437" width="0.5" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="257.08" y="447.5" ></text>
</g>
<g >
<title>tick_nohz_handler (1,480,325,505 samples, 0.02%)</title><rect x="757.4" y="437" width="0.2" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="760.38" y="447.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (10,262,152,788 samples, 0.11%)</title><rect x="146.1" y="533" width="1.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="149.13" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="581" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1137.90" y="591.5" ></text>
</g>
<g >
<title>newNode (24,025,311,080 samples, 0.25%)</title><rect x="1047.7" y="501" width="3.0" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1050.74" y="511.5" ></text>
</g>
<g >
<title>FastPathUnGrantRelationLock (19,978,046,467 samples, 0.21%)</title><rect x="519.7" y="453" width="2.5" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="522.73" y="463.5" ></text>
</g>
<g >
<title>IndexNext (414,550,658,632 samples, 4.36%)</title><rect x="291.9" y="341" width="51.5" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="294.94" y="351.5" >Index..</text>
</g>
<g >
<title>_int_malloc (1,845,629,268 samples, 0.02%)</title><rect x="280.8" y="309" width="0.2" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="283.76" y="319.5" ></text>
</g>
<g >
<title>att_isnull (1,107,956,378 samples, 0.01%)</title><rect x="1085.2" y="757" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="1088.20" y="767.5" ></text>
</g>
<g >
<title>ExecProcNode (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="565" width="6.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="98.68" y="575.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (3,904,739,205 samples, 0.04%)</title><rect x="1012.2" y="293" width="0.5" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1015.24" y="303.5" ></text>
</g>
<g >
<title>palloc0 (3,411,183,006 samples, 0.04%)</title><rect x="212.9" y="549" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="215.92" y="559.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (860,475,275 samples, 0.01%)</title><rect x="280.0" y="341" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="283.01" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,850,685,598 samples, 0.03%)</title><rect x="976.1" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="979.09" y="431.5" ></text>
</g>
<g >
<title>gup_fast_pte_range (1,017,358,232 samples, 0.01%)</title><rect x="367.0" y="69" width="0.2" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="370.04" y="79.5" ></text>
</g>
<g >
<title>xas_start (2,109,747,953 samples, 0.02%)</title><rect x="505.4" y="293" width="0.3" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="508.43" y="303.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,234,207,871 samples, 0.01%)</title><rect x="90.8" y="757" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="93.81" y="767.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,425,280,107 samples, 0.02%)</title><rect x="866.6" y="469" width="0.2" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="869.58" y="479.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="549" width="1.9" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="126.99" y="559.5" ></text>
</g>
<g >
<title>IsInParallelMode (1,061,870,314 samples, 0.01%)</title><rect x="54.8" y="757" width="0.2" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="57.82" y="767.5" ></text>
</g>
<g >
<title>restriction_is_always_false (1,344,629,198 samples, 0.01%)</title><rect x="980.9" y="405" width="0.1" height="15.0" fill="rgb(238,151,36)" rx="2" ry="2" />
<text  x="983.86" y="415.5" ></text>
</g>
<g >
<title>postmaster_child_launch (22,701,609,115 samples, 0.24%)</title><rect x="84.3" y="709" width="2.8" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="87.32" y="719.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,643,910,278 samples, 0.02%)</title><rect x="446.5" y="341" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="449.50" y="351.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,542,720,607 samples, 0.02%)</title><rect x="1066.5" y="389" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1069.49" y="399.5" ></text>
</g>
<g >
<title>tick_nohz_handler (870,876,914 samples, 0.01%)</title><rect x="700.4" y="437" width="0.1" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="703.41" y="447.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,582,288,439 samples, 0.02%)</title><rect x="941.5" y="325" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="944.53" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="421" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="129.16" y="431.5" ></text>
</g>
<g >
<title>canonicalize_qual (1,332,785,224 samples, 0.01%)</title><rect x="1054.1" y="469" width="0.2" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="1057.12" y="479.5" ></text>
</g>
<g >
<title>palloc (2,267,575,081 samples, 0.02%)</title><rect x="880.9" y="437" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="883.88" y="447.5" ></text>
</g>
<g >
<title>transformColumnRef (11,705,737,090 samples, 0.12%)</title><rect x="841.6" y="373" width="1.5" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="844.63" y="383.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,085,768,088 samples, 0.01%)</title><rect x="369.9" y="197" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="372.95" y="207.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,332,870,859 samples, 0.01%)</title><rect x="459.9" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="462.92" y="463.5" ></text>
</g>
<g >
<title>ExecScan (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="405" width="1.9" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="129.31" y="415.5" ></text>
</g>
<g >
<title>tick_nohz_handler (4,451,406,526 samples, 0.05%)</title><rect x="758.0" y="453" width="0.6" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="761.00" y="463.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,302,341,976 samples, 0.01%)</title><rect x="1134.9" y="725" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1137.90" y="735.5" ></text>
</g>
<g >
<title>pg_comp_crc32c_sse42 (4,556,563,730 samples, 0.05%)</title><rect x="391.2" y="309" width="0.6" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="394.23" y="319.5" ></text>
</g>
<g >
<title>XLogFlush (242,445,079,296 samples, 2.55%)</title><rect x="480.8" y="501" width="30.1" height="15.0" fill="rgb(251,213,50)" rx="2" ry="2" />
<text  x="483.78" y="511.5" >XL..</text>
</g>
<g >
<title>check_list_invariants (823,689,930 samples, 0.01%)</title><rect x="1154.8" y="741" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1157.75" y="751.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (30,053,645,816 samples, 0.32%)</title><rect x="491.4" y="437" width="3.7" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="494.37" y="447.5" ></text>
</g>
<g >
<title>get_futex_key (1,468,602,696 samples, 0.02%)</title><rect x="367.9" y="149" width="0.2" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="370.95" y="159.5" ></text>
</g>
<g >
<title>AllocSetFreeIndex (1,616,880,660 samples, 0.02%)</title><rect x="113.2" y="741" width="0.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="116.17" y="751.5" ></text>
</g>
<g >
<title>DatumGetObjectId (1,251,981,651 samples, 0.01%)</title><rect x="40.9" y="757" width="0.2" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="43.90" y="767.5" ></text>
</g>
<g >
<title>finish_task_switch.isra.0 (3,002,874,428 samples, 0.03%)</title><rect x="484.7" y="277" width="0.4" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="487.70" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,083,061,574 samples, 0.02%)</title><rect x="449.3" y="357" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="452.29" y="367.5" ></text>
</g>
<g >
<title>ExecEndNode (65,022,561,986 samples, 0.68%)</title><rect x="240.6" y="453" width="8.1" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="243.59" y="463.5" ></text>
</g>
<g >
<title>CacheInvalidateHeapTupleCommon (1,029,877,019 samples, 0.01%)</title><rect x="37.5" y="757" width="0.1" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="40.51" y="767.5" ></text>
</g>
<g >
<title>add_path (12,654,368,477 samples, 0.13%)</title><rect x="991.5" y="373" width="1.6" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="994.54" y="383.5" ></text>
</g>
<g >
<title>RangeVarGetRelidExtended (77,836,027,295 samples, 0.82%)</title><rect x="820.7" y="421" width="9.7" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="823.69" y="431.5" ></text>
</g>
<g >
<title>hash_search (14,621,010,854 samples, 0.15%)</title><rect x="1064.0" y="469" width="1.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1067.02" y="479.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,086,843,988 samples, 0.03%)</title><rect x="1046.9" y="405" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1049.95" y="415.5" ></text>
</g>
<g >
<title>dlist_is_empty (1,718,059,754 samples, 0.02%)</title><rect x="1133.0" y="757" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="1136.02" y="767.5" ></text>
</g>
<g >
<title>pfree (4,119,154,877 samples, 0.04%)</title><rect x="1167.1" y="757" width="0.5" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1170.06" y="767.5" ></text>
</g>
<g >
<title>newNode (5,277,492,908 samples, 0.06%)</title><rect x="819.4" y="453" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="822.39" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64_impl (940,847,819 samples, 0.01%)</title><rect x="508.9" y="453" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="511.91" y="463.5" ></text>
</g>
<g >
<title>hash_bytes (4,378,385,995 samples, 0.05%)</title><rect x="1066.7" y="373" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1069.68" y="383.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (829,036,178 samples, 0.01%)</title><rect x="98.7" y="133" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="101.70" y="143.5" ></text>
</g>
<g >
<title>generate_base_implied_equalities (21,566,085,089 samples, 0.23%)</title><rect x="978.8" y="469" width="2.7" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="981.83" y="479.5" ></text>
</g>
<g >
<title>GetTransactionSnapshot (1,798,680,845 samples, 0.02%)</title><rect x="50.6" y="757" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="53.62" y="767.5" ></text>
</g>
<g >
<title>BufferGetPage (976,646,974 samples, 0.01%)</title><rect x="363.4" y="357" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="366.38" y="367.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (3,053,814,047 samples, 0.03%)</title><rect x="355.9" y="277" width="0.4" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="358.90" y="287.5" ></text>
</g>
<g >
<title>enforce_generic_type_consistency (805,557,954 samples, 0.01%)</title><rect x="836.6" y="373" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="839.61" y="383.5" ></text>
</g>
<g >
<title>makeTargetEntry (3,368,931,210 samples, 0.04%)</title><rect x="835.6" y="437" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="838.59" y="447.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (3,101,695,727 samples, 0.03%)</title><rect x="795.7" y="469" width="0.4" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="798.73" y="479.5" ></text>
</g>
<g >
<title>finish_spin_delay (846,074,828 samples, 0.01%)</title><rect x="1139.0" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1142.02" y="767.5" ></text>
</g>
<g >
<title>ReadBufferExtended (2,510,786,482 samples, 0.03%)</title><rect x="125.2" y="229" width="0.3" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="128.17" y="239.5" ></text>
</g>
<g >
<title>AtEOXact_RelationCache (1,557,139,665 samples, 0.02%)</title><rect x="471.6" y="517" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="474.58" y="527.5" ></text>
</g>
<g >
<title>new_list (1,530,244,283 samples, 0.02%)</title><rect x="1019.0" y="325" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1022.03" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (808,372,747 samples, 0.01%)</title><rect x="340.0" y="197" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="343.03" y="207.5" ></text>
</g>
<g >
<title>addRTEPermissionInfo (4,985,693,855 samples, 0.05%)</title><rect x="896.6" y="469" width="0.6" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="899.59" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,241,928,639 samples, 0.01%)</title><rect x="920.0" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="923.01" y="447.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (1,145,995,108 samples, 0.01%)</title><rect x="326.5" y="181" width="0.2" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="329.53" y="191.5" ></text>
</g>
<g >
<title>PortalRunMulti (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="613" width="0.3" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1161.90" y="623.5" ></text>
</g>
<g >
<title>fireRIRrules (47,954,846,959 samples, 0.50%)</title><rect x="864.4" y="533" width="5.9" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="867.38" y="543.5" ></text>
</g>
<g >
<title>BufTableHashCode (3,127,738,881 samples, 0.03%)</title><rect x="300.1" y="133" width="0.4" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="303.13" y="143.5" ></text>
</g>
<g >
<title>new_list (2,344,286,730 samples, 0.02%)</title><rect x="944.0" y="389" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="946.97" y="399.5" ></text>
</g>
<g >
<title>new_list (1,307,582,965 samples, 0.01%)</title><rect x="899.7" y="453" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="902.65" y="463.5" ></text>
</g>
<g >
<title>planstate_tree_walker_impl (3,142,226,571 samples, 0.03%)</title><rect x="410.6" y="421" width="0.4" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="413.58" y="431.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,765,919,164 samples, 0.02%)</title><rect x="463.8" y="533" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="466.82" y="543.5" ></text>
</g>
<g >
<title>LockHeldByMe (10,678,874,079 samples, 0.11%)</title><rect x="947.2" y="357" width="1.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="950.19" y="367.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,307,280,234 samples, 0.01%)</title><rect x="1015.9" y="261" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1018.86" y="271.5" ></text>
</g>
<g >
<title>palloc0 (6,446,349,723 samples, 0.07%)</title><rect x="412.8" y="469" width="0.8" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="415.78" y="479.5" ></text>
</g>
<g >
<title>rint@plt (849,702,568 samples, 0.01%)</title><rect x="938.7" y="341" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="941.71" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,420,217,013 samples, 0.01%)</title><rect x="870.5" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="873.55" y="495.5" ></text>
</g>
<g >
<title>fireBSTriggers (953,915,431 samples, 0.01%)</title><rect x="1139.2" y="757" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1142.17" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (2,273,793,883 samples, 0.02%)</title><rect x="436.9" y="389" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="439.87" y="399.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (1,587,707,298 samples, 0.02%)</title><rect x="127.6" y="181" width="0.2" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="130.55" y="191.5" ></text>
</g>
<g >
<title>planner (2,204,184,953 samples, 0.02%)</title><rect x="128.2" y="565" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="131.20" y="575.5" ></text>
</g>
<g >
<title>ResourceOwnerDelete (4,101,929,631 samples, 0.04%)</title><rect x="226.3" y="565" width="0.5" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="229.26" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (972,279,135 samples, 0.01%)</title><rect x="949.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="952.72" y="399.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,203,969,060 samples, 0.01%)</title><rect x="126.2" y="437" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="129.16" y="447.5" ></text>
</g>
<g >
<title>TransactionIdSetPageStatusInternal (923,544,865 samples, 0.01%)</title><rect x="478.9" y="437" width="0.2" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="481.95" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,079,964,508 samples, 0.01%)</title><rect x="872.5" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="875.54" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,060,847,647 samples, 0.01%)</title><rect x="838.0" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="841.02" y="351.5" ></text>
</g>
<g >
<title>consume_skb (17,407,689,318 samples, 0.18%)</title><rect x="181.9" y="373" width="2.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="184.94" y="383.5" ></text>
</g>
<g >
<title>index_fetch_heap (136,159,990,272 samples, 1.43%)</title><rect x="296.6" y="309" width="16.9" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="299.55" y="319.5" ></text>
</g>
<g >
<title>EndCommand (16,187,831,597 samples, 0.17%)</title><rect x="217.5" y="581" width="2.0" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="220.52" y="591.5" ></text>
</g>
<g >
<title>AtEOXact_LargeObject (1,336,245,085 samples, 0.01%)</title><rect x="31.4" y="757" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="34.40" y="767.5" ></text>
</g>
<g >
<title>palloc (889,215,306 samples, 0.01%)</title><rect x="968.7" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="971.68" y="351.5" ></text>
</g>
<g >
<title>list_delete_nth_cell (2,645,562,410 samples, 0.03%)</title><rect x="992.4" y="357" width="0.3" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="995.37" y="367.5" ></text>
</g>
<g >
<title>scanNSItemForColumn (9,339,694,682 samples, 0.10%)</title><rect x="841.9" y="341" width="1.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="844.88" y="351.5" ></text>
</g>
<g >
<title>newNode (2,586,926,609 samples, 0.03%)</title><rect x="921.9" y="437" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="924.91" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,277,894,642 samples, 0.01%)</title><rect x="977.2" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="980.17" y="431.5" ></text>
</g>
<g >
<title>bms_add_members (1,796,598,677 samples, 0.02%)</title><rect x="974.2" y="437" width="0.2" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="977.21" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (884,017,872 samples, 0.01%)</title><rect x="481.6" y="437" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="484.61" y="447.5" ></text>
</g>
<g >
<title>_bt_drop_lock_and_maybe_pin (19,161,805,025 samples, 0.20%)</title><rect x="324.9" y="245" width="2.3" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="327.86" y="255.5" ></text>
</g>
<g >
<title>_bt_binsrch (41,571,791,019 samples, 0.44%)</title><rect x="331.1" y="245" width="5.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="334.10" y="255.5" ></text>
</g>
<g >
<title>palloc0 (2,889,692,874 samples, 0.03%)</title><rect x="1118.9" y="693" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1121.88" y="703.5" ></text>
</g>
<g >
<title>AssertBufferLocksPermitCatalogRead (3,216,537,284 samples, 0.03%)</title><rect x="29.9" y="757" width="0.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="32.89" y="767.5" ></text>
</g>
<g >
<title>pfree (1,756,431,581 samples, 0.02%)</title><rect x="248.4" y="373" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="251.40" y="383.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (3,262,946,556 samples, 0.03%)</title><rect x="124.8" y="197" width="0.4" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="127.77" y="207.5" ></text>
</g>
<g >
<title>get_quals_from_indexclauses (5,627,191,605 samples, 0.06%)</title><rect x="1011.5" y="293" width="0.7" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1014.54" y="303.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,652,178,857 samples, 0.03%)</title><rect x="302.7" y="149" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="305.67" y="159.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,940,033,097 samples, 0.02%)</title><rect x="868.2" y="389" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="871.19" y="399.5" ></text>
</g>
<g >
<title>perf_event_context_sched_out (2,445,628,252 samples, 0.03%)</title><rect x="485.1" y="245" width="0.3" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="488.14" y="255.5" ></text>
</g>
<g >
<title>index_insert_cleanup (1,565,966,003 samples, 0.02%)</title><rect x="239.8" y="453" width="0.2" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="242.79" y="463.5" ></text>
</g>
<g >
<title>palloc (1,487,811,388 samples, 0.02%)</title><rect x="953.4" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="956.37" y="415.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (846,366,649 samples, 0.01%)</title><rect x="350.2" y="261" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="353.17" y="271.5" ></text>
</g>
<g >
<title>ep_done_scan (2,646,496,910 samples, 0.03%)</title><rect x="158.8" y="373" width="0.3" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="161.82" y="383.5" ></text>
</g>
<g >
<title>SearchCatCache1 (5,306,197,840 samples, 0.06%)</title><rect x="850.1" y="373" width="0.7" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="853.15" y="383.5" ></text>
</g>
<g >
<title>XLogWrite (855,056,365 samples, 0.01%)</title><rect x="111.6" y="757" width="0.1" height="15.0" fill="rgb(243,175,42)" rx="2" ry="2" />
<text  x="114.63" y="767.5" ></text>
</g>
<g >
<title>hash_bytes (2,914,449,362 samples, 0.03%)</title><rect x="402.3" y="293" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="405.26" y="303.5" ></text>
</g>
<g >
<title>replace_nestloop_params (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="389" width="0.2" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="128.92" y="399.5" ></text>
</g>
<g >
<title>_bt_readpage (2,002,016,725 samples, 0.02%)</title><rect x="1158.9" y="293" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1161.90" y="303.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (2,803,085,885 samples, 0.03%)</title><rect x="389.3" y="229" width="0.4" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="392.30" y="239.5" ></text>
</g>
<g >
<title>lappend (4,279,738,330 samples, 0.05%)</title><rect x="890.9" y="373" width="0.6" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="893.93" y="383.5" ></text>
</g>
<g >
<title>palloc0 (3,567,865,744 samples, 0.04%)</title><rect x="945.6" y="405" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="948.56" y="415.5" ></text>
</g>
<g >
<title>wake_up_q (1,598,667,031 samples, 0.02%)</title><rect x="376.0" y="181" width="0.2" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="378.96" y="191.5" ></text>
</g>
<g >
<title>ExtendCommitTs (937,708,298 samples, 0.01%)</title><rect x="47.0" y="757" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="49.95" y="767.5" ></text>
</g>
<g >
<title>PortalRun (55,519,487,286 samples, 0.58%)</title><rect x="95.7" y="661" width="6.9" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="98.68" y="671.5" ></text>
</g>
<g >
<title>palloc (828,055,787 samples, 0.01%)</title><rect x="885.9" y="389" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="888.89" y="399.5" ></text>
</g>
<g >
<title>BufferIsDirty (4,051,898,174 samples, 0.04%)</title><rect x="393.3" y="325" width="0.5" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="396.25" y="335.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (913,397,512 samples, 0.01%)</title><rect x="1066.1" y="437" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="1069.11" y="447.5" ></text>
</g>
<g >
<title>palloc (1,426,352,211 samples, 0.02%)</title><rect x="883.3" y="485" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="886.28" y="495.5" ></text>
</g>
<g >
<title>is_andclause (1,384,087,670 samples, 0.01%)</title><rect x="1153.0" y="757" width="0.1" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="1155.97" y="767.5" ></text>
</g>
<g >
<title>new_list (1,145,970,129 samples, 0.01%)</title><rect x="885.9" y="405" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="888.86" y="415.5" ></text>
</g>
<g >
<title>finalize_plan (1,348,028,978 samples, 0.01%)</title><rect x="1138.3" y="757" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="1141.33" y="767.5" ></text>
</g>
<g >
<title>_bt_check_natts (1,835,845,779 samples, 0.02%)</title><rect x="129.1" y="757" width="0.2" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="132.11" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,202,706,917 samples, 0.01%)</title><rect x="982.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="985.68" y="399.5" ></text>
</g>
<g >
<title>lappend (1,702,059,381 samples, 0.02%)</title><rect x="897.6" y="469" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="900.58" y="479.5" ></text>
</g>
<g >
<title>new_list (2,523,211,542 samples, 0.03%)</title><rect x="1011.9" y="261" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1014.93" y="271.5" ></text>
</g>
<g >
<title>heapam_tuple_update (360,088,443,077 samples, 3.79%)</title><rect x="350.8" y="389" width="44.7" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="353.81" y="399.5" >heap..</text>
</g>
<g >
<title>TupleDescCompactAttr (2,549,935,098 samples, 0.03%)</title><rect x="360.8" y="341" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="363.80" y="351.5" ></text>
</g>
<g >
<title>transformOptionalSelectInto (432,794,894,788 samples, 4.56%)</title><rect x="804.7" y="533" width="53.8" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="807.72" y="543.5" >trans..</text>
</g>
<g >
<title>palloc0 (1,592,855,321 samples, 0.02%)</title><rect x="277.1" y="373" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="280.10" y="383.5" ></text>
</g>
<g >
<title>gup_fast_fallback (1,716,567,282 samples, 0.02%)</title><rect x="367.0" y="117" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="369.95" y="127.5" ></text>
</g>
<g >
<title>fix_indexqual_references (25,955,712,057 samples, 0.27%)</title><rect x="888.2" y="389" width="3.3" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="891.24" y="399.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (3,218,387,641 samples, 0.03%)</title><rect x="1084.0" y="757" width="0.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1087.01" y="767.5" ></text>
</g>
<g >
<title>palloc0 (3,300,538,546 samples, 0.03%)</title><rect x="1043.7" y="453" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1046.70" y="463.5" ></text>
</g>
<g >
<title>transformWhereClause (112,774,182,229 samples, 1.19%)</title><rect x="844.5" y="485" width="14.0" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="847.47" y="495.5" ></text>
</g>
<g >
<title>RegisterSnapshot (1,335,700,270 samples, 0.01%)</title><rect x="460.2" y="501" width="0.1" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="463.17" y="511.5" ></text>
</g>
<g >
<title>relation_close (1,416,987,912 samples, 0.01%)</title><rect x="922.9" y="453" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="925.91" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (888,058,262 samples, 0.01%)</title><rect x="930.6" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="933.61" y="367.5" ></text>
</g>
<g >
<title>core_yyalloc (972,616,847 samples, 0.01%)</title><rect x="1129.4" y="757" width="0.1" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1132.39" y="767.5" ></text>
</g>
<g >
<title>ExecInitExprRec (847,797,546 samples, 0.01%)</title><rect x="439.2" y="373" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="442.16" y="383.5" ></text>
</g>
<g >
<title>newNode (2,614,686,117 samples, 0.03%)</title><rect x="849.7" y="405" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="852.69" y="415.5" ></text>
</g>
<g >
<title>PostgresMain (3,415,261,054 samples, 0.04%)</title><rect x="1158.9" y="661" width="0.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="1161.90" y="671.5" ></text>
</g>
<g >
<title>_raw_spin_lock (930,113,168 samples, 0.01%)</title><rect x="201.9" y="293" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="204.87" y="303.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (924,851,775 samples, 0.01%)</title><rect x="1134.9" y="565" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1137.90" y="575.5" ></text>
</g>
<g >
<title>palloc0 (2,700,416,501 samples, 0.03%)</title><rect x="878.3" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="881.29" y="463.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,228,776,442 samples, 0.01%)</title><rect x="436.6" y="373" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="439.62" y="383.5" ></text>
</g>
<g >
<title>_bt_getroot (1,853,327,073 samples, 0.02%)</title><rect x="127.8" y="261" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="130.76" y="271.5" ></text>
</g>
<g >
<title>XLogInsert (22,230,827,455 samples, 0.23%)</title><rect x="511.6" y="485" width="2.8" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="514.64" y="495.5" ></text>
</g>
<g >
<title>build_index_paths (198,854,069,398 samples, 2.09%)</title><rect x="993.1" y="373" width="24.7" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="996.12" y="383.5" >b..</text>
</g>
<g >
<title>relation_open (14,417,655,788 samples, 0.15%)</title><rect x="401.6" y="373" width="1.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="404.60" y="383.5" ></text>
</g>
<g >
<title>__x64_sys_fdatasync (2,711,704,930 samples, 0.03%)</title><rect x="507.7" y="389" width="0.4" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="510.73" y="399.5" ></text>
</g>
<g >
<title>is_valid_ascii (4,452,065,626 samples, 0.05%)</title><rect x="1081.3" y="517" width="0.5" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1084.26" y="527.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,307,206,475 samples, 0.01%)</title><rect x="819.1" y="405" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="822.11" y="415.5" ></text>
</g>
<g >
<title>fput (986,021,142 samples, 0.01%)</title><rect x="159.6" y="357" width="0.1" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="162.57" y="367.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,285,427,510 samples, 0.02%)</title><rect x="272.4" y="373" width="0.3" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="275.44" y="383.5" ></text>
</g>
<g >
<title>yy_get_next_buffer (1,378,517,711 samples, 0.01%)</title><rect x="1113.6" y="709" width="0.2" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="1116.63" y="719.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="725" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.62" y="735.5" ></text>
</g>
<g >
<title>UnregisterSnapshotFromOwner (2,838,145,237 samples, 0.03%)</title><rect x="460.7" y="501" width="0.3" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="463.69" y="511.5" ></text>
</g>
<g >
<title>core_yyensure_buffer_stack (2,958,333,713 samples, 0.03%)</title><rect x="872.1" y="501" width="0.4" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="875.12" y="511.5" ></text>
</g>
<g >
<title>obj_cgroup_charge_account (1,483,537,421 samples, 0.02%)</title><rect x="200.2" y="325" width="0.1" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="203.16" y="335.5" ></text>
</g>
<g >
<title>RelationClose (1,219,841,811 samples, 0.01%)</title><rect x="946.9" y="373" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="949.95" y="383.5" ></text>
</g>
<g >
<title>free_parsestate (5,667,331,642 samples, 0.06%)</title><rect x="803.0" y="549" width="0.7" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="806.04" y="559.5" ></text>
</g>
<g >
<title>IndexNext (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="389" width="0.3" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="1161.90" y="399.5" ></text>
</g>
<g >
<title>simplify_function (971,078,648 samples, 0.01%)</title><rect x="128.4" y="453" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="131.36" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,257,991,570 samples, 0.02%)</title><rect x="943.4" y="341" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="946.37" y="351.5" ></text>
</g>
<g >
<title>AllocSetDelete (80,246,583,800 samples, 0.84%)</title><rect x="255.2" y="453" width="10.0" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="258.19" y="463.5" ></text>
</g>
<g >
<title>do_futex (1,637,483,053 samples, 0.02%)</title><rect x="496.9" y="357" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="499.86" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (962,621,502 samples, 0.01%)</title><rect x="112.3" y="741" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="115.26" y="751.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,494,513,226 samples, 0.04%)</title><rect x="1046.9" y="421" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1049.90" y="431.5" ></text>
</g>
<g >
<title>TidQualFromRestrictInfoList (985,908,506 samples, 0.01%)</title><rect x="107.2" y="757" width="0.1" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="110.20" y="767.5" ></text>
</g>
<g >
<title>TransactionIdDidCommit (6,834,045,459 samples, 0.07%)</title><rect x="1147.9" y="693" width="0.9" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="1150.94" y="703.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (1,081,503,302 samples, 0.01%)</title><rect x="1077.3" y="485" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="1080.32" y="495.5" ></text>
</g>
<g >
<title>AllocSetFree (1,939,611,982 samples, 0.02%)</title><rect x="189.5" y="549" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="192.47" y="559.5" ></text>
</g>
<g >
<title>new_list (1,195,319,033 samples, 0.01%)</title><rect x="837.6" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="840.64" y="367.5" ></text>
</g>
<g >
<title>ExecSetupTransitionCaptureState (916,580,491 samples, 0.01%)</title><rect x="456.9" y="453" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="459.90" y="463.5" ></text>
</g>
<g >
<title>ReleaseCatCache (807,592,620 samples, 0.01%)</title><rect x="1040.4" y="341" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1043.37" y="351.5" ></text>
</g>
<g >
<title>ReleaseCatCache (806,247,750 samples, 0.01%)</title><rect x="1055.3" y="405" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1058.34" y="415.5" ></text>
</g>
<g >
<title>list_make1_impl (6,760,858,207 samples, 0.07%)</title><rect x="918.4" y="485" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="921.43" y="495.5" ></text>
</g>
<g >
<title>index_getnext_slot (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="389" width="2.6" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="87.32" y="399.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,576,409,287 samples, 0.02%)</title><rect x="1010.2" y="245" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1013.22" y="255.5" ></text>
</g>
<g >
<title>btendscan (50,966,429,185 samples, 0.54%)</title><rect x="241.9" y="405" width="6.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="244.89" y="415.5" ></text>
</g>
<g >
<title>exprType (1,364,967,939 samples, 0.01%)</title><rect x="1136.0" y="757" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1138.95" y="767.5" ></text>
</g>
<g >
<title>list_copy (3,746,918,873 samples, 0.04%)</title><rect x="931.0" y="389" width="0.5" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="934.01" y="399.5" ></text>
</g>
<g >
<title>GetTransactionSnapshot (35,446,177,013 samples, 0.37%)</title><rect x="219.6" y="581" width="4.4" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="222.63" y="591.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,570,743,990 samples, 0.05%)</title><rect x="807.7" y="357" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="810.71" y="367.5" ></text>
</g>
<g >
<title>wipe_mem (888,410,258 samples, 0.01%)</title><rect x="1189.7" y="757" width="0.1" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="1192.67" y="767.5" ></text>
</g>
<g >
<title>LockAcquireExtended (7,509,499,670 samples, 0.08%)</title><rect x="401.7" y="341" width="0.9" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="404.69" y="351.5" ></text>
</g>
<g >
<title>BufferGetPage (1,001,816,223 samples, 0.01%)</title><rect x="325.2" y="213" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="328.17" y="223.5" ></text>
</g>
<g >
<title>new_list (1,771,169,428 samples, 0.02%)</title><rect x="976.7" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="979.69" y="415.5" ></text>
</g>
<g >
<title>_bt_getbuf (9,892,389,053 samples, 0.10%)</title><rect x="336.9" y="229" width="1.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="339.95" y="239.5" ></text>
</g>
<g >
<title>create_plan_recurse (61,423,603,293 samples, 0.65%)</title><rect x="885.0" y="469" width="7.7" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="888.02" y="479.5" ></text>
</g>
<g >
<title>[[vdso]] (1,473,957,784 samples, 0.02%)</title><rect x="111.9" y="757" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="114.86" y="767.5" ></text>
</g>
<g >
<title>list_nth (4,129,774,446 samples, 0.04%)</title><rect x="1157.8" y="757" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1160.77" y="767.5" ></text>
</g>
<g >
<title>get_typcollation (7,654,916,593 samples, 0.08%)</title><rect x="811.0" y="357" width="1.0" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="814.03" y="367.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (4,551,375,165 samples, 0.05%)</title><rect x="10.9" y="741" width="0.5" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="13.86" y="751.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (14,768,257,788 samples, 0.16%)</title><rect x="1004.9" y="165" width="1.9" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="1007.93" y="175.5" ></text>
</g>
<g >
<title>BufferGetPage (1,168,042,549 samples, 0.01%)</title><rect x="351.7" y="373" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="354.71" y="383.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,764,953,165 samples, 0.02%)</title><rect x="309.3" y="133" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="312.27" y="143.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,617,119,237 samples, 0.03%)</title><rect x="952.6" y="373" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="955.60" y="383.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,107,298,699 samples, 0.01%)</title><rect x="406.2" y="357" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="409.23" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_monotonic_advance_u64 (2,225,920,523 samples, 0.02%)</title><rect x="497.2" y="469" width="0.3" height="15.0" fill="rgb(206,8,1)" rx="2" ry="2" />
<text  x="500.24" y="479.5" ></text>
</g>
<g >
<title>list_length (11,350,819,798 samples, 0.12%)</title><rect x="1156.1" y="757" width="1.5" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="1159.14" y="767.5" ></text>
</g>
<g >
<title>dequeue_entities (18,098,453,702 samples, 0.19%)</title><rect x="486.5" y="245" width="2.3" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="489.51" y="255.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (823,428,921 samples, 0.01%)</title><rect x="934.9" y="325" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="937.91" y="335.5" ></text>
</g>
<g >
<title>fetch_search_path_array (939,929,325 samples, 0.01%)</title><rect x="840.8" y="341" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="843.82" y="351.5" ></text>
</g>
<g >
<title>new_list (1,906,420,937 samples, 0.02%)</title><rect x="447.1" y="373" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="450.12" y="383.5" ></text>
</g>
<g >
<title>PortalRunMulti (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="581" width="1.9" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="129.31" y="591.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,541,077,163 samples, 0.03%)</title><rect x="1185.1" y="693" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1188.13" y="703.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (849,642,535 samples, 0.01%)</title><rect x="1056.3" y="405" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1059.29" y="415.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (2,791,509,702 samples, 0.03%)</title><rect x="275.4" y="389" width="0.3" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="278.36" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (1,115,128,237 samples, 0.01%)</title><rect x="223.7" y="517" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="226.73" y="527.5" ></text>
</g>
<g >
<title>object_aclcheck_ext (1,209,839,797 samples, 0.01%)</title><rect x="428.3" y="309" width="0.2" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="431.33" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,329,175,188 samples, 0.01%)</title><rect x="898.1" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="901.13" y="463.5" ></text>
</g>
<g >
<title>expand_function_arguments (2,372,926,891 samples, 0.02%)</title><rect x="104.9" y="453" width="0.3" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="107.91" y="463.5" ></text>
</g>
<g >
<title>obj_cgroup_charge_pages (1,098,447,585 samples, 0.01%)</title><rect x="198.2" y="293" width="0.1" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="201.20" y="303.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,391,512,428 samples, 0.01%)</title><rect x="1072.6" y="565" width="0.2" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1075.64" y="575.5" ></text>
</g>
<g >
<title>palloc (1,150,244,547 samples, 0.01%)</title><rect x="1173.6" y="581" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1176.62" y="591.5" ></text>
</g>
<g >
<title>pgstat_count_heap_update (10,382,258,527 samples, 0.11%)</title><rect x="394.2" y="357" width="1.3" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="397.20" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,934,024,717 samples, 0.02%)</title><rect x="234.2" y="469" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="237.16" y="479.5" ></text>
</g>
<g >
<title>BufferGetBlock (947,003,842 samples, 0.01%)</title><rect x="338.6" y="213" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="341.60" y="223.5" ></text>
</g>
<g >
<title>pg_client_to_server (1,166,101,651 samples, 0.01%)</title><rect x="1169.5" y="757" width="0.1" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1172.46" y="767.5" ></text>
</g>
<g >
<title>_raw_spin_lock_irq (1,130,088,407 samples, 0.01%)</title><rect x="159.0" y="357" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="162.01" y="367.5" ></text>
</g>
<g >
<title>__rseq_handle_notify_resume (5,984,151,783 samples, 0.06%)</title><rect x="176.3" y="421" width="0.8" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="179.31" y="431.5" ></text>
</g>
<g >
<title>hash_search (2,051,266,570 samples, 0.02%)</title><rect x="948.7" y="357" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="951.69" y="367.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (7,455,537,735 samples, 0.08%)</title><rect x="287.7" y="245" width="0.9" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="290.66" y="255.5" ></text>
</g>
<g >
<title>PinBuffer (3,208,331,571 samples, 0.03%)</title><rect x="408.9" y="277" width="0.4" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="411.91" y="287.5" ></text>
</g>
<g >
<title>ExecOpenIndices (39,830,080,297 samples, 0.42%)</title><rect x="399.2" y="405" width="4.9" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="402.16" y="415.5" ></text>
</g>
<g >
<title>colNameToVar (10,365,370,763 samples, 0.11%)</title><rect x="841.8" y="357" width="1.2" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="844.75" y="367.5" ></text>
</g>
<g >
<title>pg_plan_queries (1,586,811,688,584 samples, 16.70%)</title><rect x="874.0" y="581" width="197.1" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="876.98" y="591.5" >pg_plan_queries</text>
</g>
<g >
<title>AllocSetFree (1,089,343,880 samples, 0.01%)</title><rect x="1068.8" y="453" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1071.81" y="463.5" ></text>
</g>
<g >
<title>SearchCatCache (6,040,567,029 samples, 0.06%)</title><rect x="1021.2" y="261" width="0.7" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="1024.16" y="271.5" ></text>
</g>
<g >
<title>new_list (1,879,408,270 samples, 0.02%)</title><rect x="980.6" y="389" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="983.63" y="399.5" ></text>
</g>
<g >
<title>ExecProcNode (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="501" width="1.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="129.31" y="511.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (2,937,777,304 samples, 0.03%)</title><rect x="149.7" y="549" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="152.70" y="559.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (2,811,815,576 samples, 0.03%)</title><rect x="1158.9" y="469" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1161.90" y="479.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,366,172,720 samples, 0.01%)</title><rect x="1134.9" y="741" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1137.89" y="751.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,346,163,185 samples, 0.01%)</title><rect x="423.3" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="426.26" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,421,498,791 samples, 0.03%)</title><rect x="842.2" y="293" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="845.22" y="303.5" ></text>
</g>
<g >
<title>ExecutePlan (1,159,672,037,327 samples, 12.21%)</title><rect x="267.1" y="501" width="144.0" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="270.07" y="511.5" >ExecutePlan</text>
</g>
<g >
<title>CatalogCacheComputeHashValue (968,028,315 samples, 0.01%)</title><rect x="959.9" y="245" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="962.89" y="255.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (3,365,339,773 samples, 0.04%)</title><rect x="946.4" y="373" width="0.4" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="949.37" y="383.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,682,891,337 samples, 0.02%)</title><rect x="824.8" y="389" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="827.77" y="399.5" ></text>
</g>
<g >
<title>downcase_identifier (10,684,990,481 samples, 0.11%)</title><rect x="1111.9" y="693" width="1.3" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1114.90" y="703.5" ></text>
</g>
<g >
<title>palloc (1,024,250,758 samples, 0.01%)</title><rect x="919.7" y="437" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="922.69" y="447.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (943,883,885 samples, 0.01%)</title><rect x="236.7" y="485" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="239.70" y="495.5" ></text>
</g>
<g >
<title>pfree (2,137,738,734 samples, 0.02%)</title><rect x="1072.2" y="565" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1075.16" y="575.5" ></text>
</g>
<g >
<title>MarkPortalActive (817,349,718 samples, 0.01%)</title><rect x="59.9" y="757" width="0.1" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="62.90" y="767.5" ></text>
</g>
<g >
<title>newNode (6,345,393,856 samples, 0.07%)</title><rect x="949.1" y="421" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="952.06" y="431.5" ></text>
</g>
<g >
<title>palloc (2,504,342,000 samples, 0.03%)</title><rect x="990.9" y="325" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="993.89" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="533" width="3.4" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="105.66" y="543.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,024,748,466 samples, 0.01%)</title><rect x="375.5" y="341" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="378.51" y="351.5" ></text>
</g>
<g >
<title>SearchCatCache1 (1,085,851,288 samples, 0.01%)</title><rect x="94.1" y="757" width="0.2" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="97.12" y="767.5" ></text>
</g>
<g >
<title>exprSetCollation (869,097,649 samples, 0.01%)</title><rect x="809.7" y="357" width="0.1" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="812.69" y="367.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,827,939,197 samples, 0.02%)</title><rect x="1144.6" y="757" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1147.57" y="767.5" ></text>
</g>
<g >
<title>bms_is_member (1,213,294,783 samples, 0.01%)</title><rect x="1011.4" y="245" width="0.1" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="1014.36" y="255.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,422,792,597 samples, 0.04%)</title><rect x="1036.9" y="293" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1039.89" y="303.5" ></text>
</g>
<g >
<title>_raw_spin_lock (2,174,588,545 samples, 0.02%)</title><rect x="194.8" y="405" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="197.76" y="415.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (6,919,100,808 samples, 0.07%)</title><rect x="175.1" y="437" width="0.9" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="178.13" y="447.5" ></text>
</g>
<g >
<title>unix_destruct_scm (8,787,234,774 samples, 0.09%)</title><rect x="183.0" y="341" width="1.1" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="186.01" y="351.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (1,207,080,433 samples, 0.01%)</title><rect x="28.9" y="741" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="31.90" y="751.5" ></text>
</g>
<g >
<title>handle_softirqs (1,206,781,108 samples, 0.01%)</title><rect x="757.8" y="485" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="760.76" y="495.5" ></text>
</g>
<g >
<title>PinBufferForBlock (25,113,249,348 samples, 0.26%)</title><rect x="299.9" y="165" width="3.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="302.88" y="175.5" ></text>
</g>
<g >
<title>fix_indexqual_clause (18,895,327,713 samples, 0.20%)</title><rect x="888.6" y="373" width="2.3" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="891.58" y="383.5" ></text>
</g>
<g >
<title>update_rq_clock_task (1,840,162,593 samples, 0.02%)</title><rect x="174.6" y="341" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="177.64" y="351.5" ></text>
</g>
<g >
<title>PageGetMaxOffsetNumber (873,696,687 samples, 0.01%)</title><rect x="328.9" y="229" width="0.1" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="331.93" y="239.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,210,254,954 samples, 0.03%)</title><rect x="829.7" y="293" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="832.71" y="303.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (923,566,761 samples, 0.01%)</title><rect x="757.6" y="517" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="760.62" y="527.5" ></text>
</g>
<g >
<title>hash_search (5,467,062,859 samples, 0.06%)</title><rect x="239.1" y="405" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="242.10" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,247,899,947 samples, 0.01%)</title><rect x="461.6" y="501" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="464.65" y="511.5" ></text>
</g>
<g >
<title>cfree@GLIBC_2.2.5 (5,909,956,055 samples, 0.06%)</title><rect x="150.1" y="549" width="0.7" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="153.09" y="559.5" ></text>
</g>
<g >
<title>pfree (2,638,862,701 samples, 0.03%)</title><rect x="226.4" y="549" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="229.44" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,270,760,925 samples, 0.01%)</title><rect x="889.9" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="892.90" y="303.5" ></text>
</g>
<g >
<title>llseek@GLIBC_2.2.5 (7,326,056,845 samples, 0.08%)</title><rect x="932.1" y="325" width="0.9" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="935.11" y="335.5" ></text>
</g>
<g >
<title>palloc0 (1,661,697,278 samples, 0.02%)</title><rect x="345.4" y="357" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="348.36" y="367.5" ></text>
</g>
<g >
<title>hash_bytes (2,471,679,304 samples, 0.03%)</title><rect x="96.1" y="133" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="99.12" y="143.5" ></text>
</g>
<g >
<title>check_index_only (19,526,104,321 samples, 0.21%)</title><rect x="994.5" y="357" width="2.5" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="997.53" y="367.5" ></text>
</g>
<g >
<title>get_op_opfamily_strategy (6,971,393,117 samples, 0.07%)</title><rect x="1013.4" y="309" width="0.8" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1016.36" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,767,825,322 samples, 0.02%)</title><rect x="365.7" y="277" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="368.70" y="287.5" ></text>
</g>
<g >
<title>ExecScan (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="421" width="1.9" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="126.99" y="431.5" ></text>
</g>
<g >
<title>btcostestimate (1,973,290,642 samples, 0.02%)</title><rect x="1123.6" y="757" width="0.2" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="1126.58" y="767.5" ></text>
</g>
<g >
<title>fetch_upper_rel (1,145,675,810 samples, 0.01%)</title><rect x="907.0" y="501" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="909.98" y="511.5" ></text>
</g>
<g >
<title>bms_copy (2,406,611,456 samples, 0.03%)</title><rect x="459.8" y="485" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="462.79" y="495.5" ></text>
</g>
<g >
<title>__check_object_size.part.0 (6,202,508,476 samples, 0.07%)</title><rect x="195.5" y="389" width="0.8" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="198.53" y="399.5" ></text>
</g>
<g >
<title>ReadBuffer_common (18,501,516,448 samples, 0.19%)</title><rect x="354.0" y="341" width="2.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="356.99" y="351.5" ></text>
</g>
<g >
<title>palloc (1,137,511,697 samples, 0.01%)</title><rect x="835.4" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="838.37" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,104,816,266 samples, 0.01%)</title><rect x="342.0" y="229" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="344.98" y="239.5" ></text>
</g>
<g >
<title>set_baserel_size_estimates (117,334,772,216 samples, 1.24%)</title><rect x="1027.5" y="405" width="14.6" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="1030.53" y="415.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (2,023,790,705 samples, 0.02%)</title><rect x="758.7" y="533" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="761.66" y="543.5" ></text>
</g>
<g >
<title>SetupLockInTable (22,234,294,926 samples, 0.23%)</title><rect x="371.0" y="277" width="2.8" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="374.04" y="287.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (926,009,828 samples, 0.01%)</title><rect x="86.4" y="181" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="89.39" y="191.5" ></text>
</g>
<g >
<title>relation_open (16,023,700,029 samples, 0.17%)</title><rect x="862.4" y="501" width="2.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="865.37" y="511.5" ></text>
</g>
<g >
<title>is_publishable_relation (1,756,663,451 samples, 0.02%)</title><rect x="1153.8" y="757" width="0.3" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="1156.84" y="767.5" ></text>
</g>
<g >
<title>list_make1_impl (1,664,103,247 samples, 0.02%)</title><rect x="971.4" y="389" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="974.39" y="399.5" ></text>
</g>
<g >
<title>BufTableLookup (2,151,770,197 samples, 0.02%)</title><rect x="355.0" y="261" width="0.2" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="357.96" y="271.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,645,284,842 samples, 0.02%)</title><rect x="823.5" y="373" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="826.48" y="383.5" ></text>
</g>
<g >
<title>pfree (1,492,096,338 samples, 0.02%)</title><rect x="965.7" y="357" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="968.70" y="367.5" ></text>
</g>
<g >
<title>ExecScanFetch (54,879,784,624 samples, 0.58%)</title><rect x="95.7" y="437" width="6.8" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="98.68" y="447.5" ></text>
</g>
<g >
<title>new_list (1,627,707,130 samples, 0.02%)</title><rect x="1056.5" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1059.54" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (997,091,571 samples, 0.01%)</title><rect x="953.8" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="956.76" y="431.5" ></text>
</g>
<g >
<title>ExecClearTuple (878,960,716 samples, 0.01%)</title><rect x="42.3" y="757" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="45.26" y="767.5" ></text>
</g>
<g >
<title>bms_add_member (2,427,715,697 samples, 0.03%)</title><rect x="345.3" y="389" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="348.27" y="399.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (982,893,308 samples, 0.01%)</title><rect x="302.3" y="117" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="305.27" y="127.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (17,922,102,413 samples, 0.19%)</title><rect x="354.1" y="309" width="2.2" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="357.05" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,672,029,327 samples, 0.02%)</title><rect x="1012.0" y="229" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1015.01" y="239.5" ></text>
</g>
<g >
<title>_copy_to_iter (3,332,445,272 samples, 0.04%)</title><rect x="185.8" y="325" width="0.4" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="188.77" y="335.5" ></text>
</g>
<g >
<title>XLogBeginInsert (1,111,656,933 samples, 0.01%)</title><rect x="110.5" y="757" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="113.52" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,725,669,843 samples, 0.02%)</title><rect x="377.6" y="341" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="380.58" y="351.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="357" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="89.91" y="367.5" ></text>
</g>
<g >
<title>pstrdup (2,673,406,419 samples, 0.03%)</title><rect x="921.4" y="437" width="0.3" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="924.39" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (872,316,289 samples, 0.01%)</title><rect x="834.8" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="837.82" y="431.5" ></text>
</g>
<g >
<title>internal_flush (156,715,568,396 samples, 1.65%)</title><rect x="190.5" y="565" width="19.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="193.48" y="575.5" ></text>
</g>
<g >
<title>btbeginscan (1,187,297,176 samples, 0.01%)</title><rect x="1123.2" y="757" width="0.2" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="1126.23" y="767.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,237,052,526 samples, 0.04%)</title><rect x="127.2" y="213" width="0.6" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="130.24" y="223.5" ></text>
</g>
<g >
<title>hash_bytes (2,454,724,767 samples, 0.03%)</title><rect x="831.8" y="341" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="834.84" y="351.5" ></text>
</g>
<g >
<title>find_base_rel (829,894,115 samples, 0.01%)</title><rect x="953.0" y="437" width="0.1" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="956.03" y="447.5" ></text>
</g>
<g >
<title>_bt_getrootheight (2,244,020,908 samples, 0.02%)</title><rect x="933.1" y="389" width="0.3" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="936.14" y="399.5" ></text>
</g>
<g >
<title>transformAExprOp (96,143,299,367 samples, 1.01%)</title><rect x="846.5" y="437" width="12.0" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="849.53" y="447.5" ></text>
</g>
<g >
<title>log_heap_update (1,029,125,105 samples, 0.01%)</title><rect x="1158.8" y="757" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1161.77" y="767.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,435,445,393 samples, 0.02%)</title><rect x="125.9" y="293" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="128.92" y="303.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,344,295,273 samples, 0.01%)</title><rect x="1012.6" y="229" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1015.56" y="239.5" ></text>
</g>
<g >
<title>newNode (2,277,482,878 samples, 0.02%)</title><rect x="889.0" y="309" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="892.05" y="319.5" ></text>
</g>
<g >
<title>add_row_identity_var (7,866,423,926 samples, 0.08%)</title><rect x="920.7" y="453" width="1.0" height="15.0" fill="rgb(210,25,5)" rx="2" ry="2" />
<text  x="923.75" y="463.5" ></text>
</g>
<g >
<title>expr_setup_walker (1,621,681,643 samples, 0.02%)</title><rect x="426.4" y="229" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="429.43" y="239.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,569,759,193 samples, 0.02%)</title><rect x="462.0" y="501" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="464.99" y="511.5" ></text>
</g>
<g >
<title>_bt_returnitem (1,647,450,883 samples, 0.02%)</title><rect x="329.8" y="261" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="332.83" y="271.5" ></text>
</g>
<g >
<title>palloc (1,122,099,526 samples, 0.01%)</title><rect x="1030.4" y="197" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1033.38" y="207.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,387,913,265 samples, 0.01%)</title><rect x="1061.1" y="453" width="0.2" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1064.13" y="463.5" ></text>
</g>
<g >
<title>balance_dirty_pages_ratelimited_flags (2,044,743,615 samples, 0.02%)</title><rect x="502.5" y="357" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="505.52" y="367.5" ></text>
</g>
<g >
<title>assign_collations_walker (16,935,073,265 samples, 0.18%)</title><rect x="806.5" y="421" width="2.1" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="809.51" y="431.5" ></text>
</g>
<g >
<title>malloc (2,362,747,923 samples, 0.02%)</title><rect x="280.7" y="325" width="0.3" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="283.69" y="335.5" ></text>
</g>
<g >
<title>ExecScanFetch (22,631,995,978 samples, 0.24%)</title><rect x="281.9" y="373" width="2.8" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="284.86" y="383.5" ></text>
</g>
<g >
<title>lappend (3,329,453,147 samples, 0.04%)</title><rect x="890.3" y="277" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="893.33" y="287.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,745,573,972 samples, 0.05%)</title><rect x="845.2" y="405" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="848.16" y="415.5" ></text>
</g>
<g >
<title>palloc0 (3,158,810,305 samples, 0.03%)</title><rect x="432.0" y="341" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="434.97" y="351.5" ></text>
</g>
<g >
<title>palloc (1,291,642,293 samples, 0.01%)</title><rect x="358.4" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="361.38" y="367.5" ></text>
</g>
<g >
<title>ScanKeyEntryInitialize (1,514,712,321 samples, 0.02%)</title><rect x="93.7" y="757" width="0.2" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="96.73" y="767.5" ></text>
</g>
<g >
<title>AtEOXact_MultiXact (1,093,084,053 samples, 0.01%)</title><rect x="470.3" y="517" width="0.1" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="473.26" y="527.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,722,705,699 samples, 0.02%)</title><rect x="408.2" y="261" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="411.24" y="271.5" ></text>
</g>
<g >
<title>base_yyparse (1,053,885,578 samples, 0.01%)</title><rect x="871.1" y="549" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="874.14" y="559.5" ></text>
</g>
<g >
<title>process_integer_literal (3,108,710,492 samples, 0.03%)</title><rect x="1113.2" y="709" width="0.4" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="1116.24" y="719.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,527,359,504 samples, 0.04%)</title><rect x="1051.4" y="389" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1054.43" y="399.5" ></text>
</g>
<g >
<title>palloc0 (1,172,266,575 samples, 0.01%)</title><rect x="813.9" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="816.93" y="447.5" ></text>
</g>
<g >
<title>PageGetItem (810,786,055 samples, 0.01%)</title><rect x="81.7" y="757" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="84.74" y="767.5" ></text>
</g>
<g >
<title>skb_release_data (8,416,569,876 samples, 0.09%)</title><rect x="182.0" y="357" width="1.0" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="184.96" y="367.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (2,775,476,542 samples, 0.03%)</title><rect x="375.8" y="277" width="0.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="378.83" y="287.5" ></text>
</g>
<g >
<title>MemoryContextStrdup (3,863,650,694 samples, 0.04%)</title><rect x="818.0" y="421" width="0.5" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="820.99" y="431.5" ></text>
</g>
<g >
<title>palloc0 (2,060,212,515 samples, 0.02%)</title><rect x="921.1" y="405" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="924.13" y="415.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (3,879,442,672 samples, 0.04%)</title><rect x="955.5" y="325" width="0.5" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="958.48" y="335.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,498,102,932 samples, 0.02%)</title><rect x="525.4" y="389" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="528.41" y="399.5" ></text>
</g>
<g >
<title>hash_search (3,070,158,231 samples, 0.03%)</title><rect x="864.0" y="469" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="866.98" y="479.5" ></text>
</g>
<g >
<title>ReleaseCatCache (2,184,628,281 samples, 0.02%)</title><rect x="442.9" y="341" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="445.90" y="351.5" ></text>
</g>
<g >
<title>AllocSetCheck (2,103,968,291 samples, 0.02%)</title><rect x="465.3" y="565" width="0.3" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="468.31" y="575.5" ></text>
</g>
<g >
<title>LockAcquireExtended (48,802,429,981 samples, 0.51%)</title><rect x="368.7" y="293" width="6.0" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="371.67" y="303.5" ></text>
</g>
<g >
<title>list_nth (1,177,242,631 samples, 0.01%)</title><rect x="451.6" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="454.63" y="415.5" ></text>
</g>
<g >
<title>MemoryContextStrdup (2,554,305,394 samples, 0.03%)</title><rect x="921.4" y="421" width="0.3" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="924.40" y="431.5" ></text>
</g>
<g >
<title>add_path (3,606,560,507 samples, 0.04%)</title><rect x="984.8" y="405" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="987.81" y="415.5" ></text>
</g>
<g >
<title>tts_buffer_heap_store_tuple (979,150,011 samples, 0.01%)</title><rect x="405.2" y="389" width="0.1" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="408.20" y="399.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (982,085,081 samples, 0.01%)</title><rect x="382.7" y="325" width="0.2" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="385.75" y="335.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,563,990,102 samples, 0.06%)</title><rect x="1041.4" y="341" width="0.7" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1044.41" y="351.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (922,758,410 samples, 0.01%)</title><rect x="947.9" y="277" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="950.95" y="287.5" ></text>
</g>
<g >
<title>_raw_spin_lock (2,091,856,773 samples, 0.02%)</title><rect x="181.6" y="373" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="184.61" y="383.5" ></text>
</g>
<g >
<title>FreeQueryDesc (5,469,909,814 samples, 0.06%)</title><rect x="460.6" y="533" width="0.7" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="463.57" y="543.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,311,124,659 samples, 0.05%)</title><rect x="369.5" y="277" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="372.55" y="287.5" ></text>
</g>
<g >
<title>pg_plan_query (1,582,101,399,492 samples, 16.65%)</title><rect x="874.6" y="565" width="196.5" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="877.56" y="575.5" >pg_plan_query</text>
</g>
<g >
<title>RelationGetNumberOfBlocksInFork (23,692,429,601 samples, 0.25%)</title><rect x="935.6" y="341" width="2.9" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="938.57" y="351.5" ></text>
</g>
<g >
<title>pull_varattnos (10,972,619,365 samples, 0.12%)</title><rect x="995.6" y="341" width="1.4" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="998.60" y="351.5" ></text>
</g>
<g >
<title>SlruSelectLRUPage (3,119,832,447 samples, 0.03%)</title><rect x="479.6" y="421" width="0.4" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="482.60" y="431.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (1,056,393,843 samples, 0.01%)</title><rect x="86.4" y="197" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="89.39" y="207.5" ></text>
</g>
<g >
<title>lappend (2,653,408,473 samples, 0.03%)</title><rect x="955.6" y="309" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="958.64" y="319.5" ></text>
</g>
<g >
<title>ExecProcNode (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="469" width="1.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="126.99" y="479.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,624,920,452 samples, 0.02%)</title><rect x="451.1" y="373" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="454.09" y="383.5" ></text>
</g>
<g >
<title>Float8GetDatum (915,119,584 samples, 0.01%)</title><rect x="47.5" y="757" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="50.54" y="767.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,705,299,624 samples, 0.06%)</title><rect x="1021.2" y="245" width="0.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1024.20" y="255.5" ></text>
</g>
<g >
<title>prepare_task_switch (9,717,573,584 samples, 0.10%)</title><rect x="165.3" y="341" width="1.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="168.31" y="351.5" ></text>
</g>
<g >
<title>PostgresMain (18,601,171,430 samples, 0.20%)</title><rect x="124.0" y="645" width="2.3" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="126.99" y="655.5" ></text>
</g>
<g >
<title>new_list (1,881,812,419 samples, 0.02%)</title><rect x="457.3" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="460.29" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,775,049,329 samples, 0.05%)</title><rect x="865.7" y="469" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="868.70" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,642,858,338 samples, 0.03%)</title><rect x="428.7" y="309" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="431.74" y="319.5" ></text>
</g>
<g >
<title>finalize_primnode (1,955,552,326 samples, 0.02%)</title><rect x="1138.5" y="757" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="1141.49" y="767.5" ></text>
</g>
<g >
<title>bms_copy (2,294,422,485 samples, 0.02%)</title><rect x="358.6" y="357" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="361.57" y="367.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple (2,699,513,520 samples, 0.03%)</title><rect x="271.6" y="293" width="0.3" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="274.56" y="303.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (7,686,230,723 samples, 0.08%)</title><rect x="810.1" y="341" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="813.07" y="351.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (1,609,362,355 samples, 0.02%)</title><rect x="478.1" y="405" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="481.12" y="415.5" ></text>
</g>
<g >
<title>make_pathtarget_from_tlist (867,532,952 samples, 0.01%)</title><rect x="1160.8" y="757" width="0.1" height="15.0" fill="rgb(213,37,9)" rx="2" ry="2" />
<text  x="1163.79" y="767.5" ></text>
</g>
<g >
<title>tag_hash (3,352,790,488 samples, 0.04%)</title><rect x="407.8" y="245" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="410.81" y="255.5" ></text>
</g>
<g >
<title>eval_const_expressions (16,909,639,231 samples, 0.18%)</title><rect x="1054.7" y="469" width="2.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1057.72" y="479.5" ></text>
</g>
<g >
<title>bms_is_valid_set (5,142,128,027 samples, 0.05%)</title><rect x="1121.8" y="757" width="0.7" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="1124.85" y="767.5" ></text>
</g>
<g >
<title>BufTableLookup (4,607,704,089 samples, 0.05%)</title><rect x="100.5" y="181" width="0.6" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="103.52" y="191.5" ></text>
</g>
<g >
<title>lappend (2,413,851,152 samples, 0.03%)</title><rect x="933.7" y="389" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="936.73" y="399.5" ></text>
</g>
<g >
<title>do_futex (1,140,864,244 samples, 0.01%)</title><rect x="388.9" y="181" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="391.91" y="191.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (15,275,504,734 samples, 0.16%)</title><rect x="126.3" y="437" width="1.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="129.31" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,603,179,522 samples, 0.04%)</title><rect x="959.6" y="277" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="962.65" y="287.5" ></text>
</g>
<g >
<title>AllocSetFree (893,475,780 samples, 0.01%)</title><rect x="254.4" y="373" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="257.40" y="383.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,967,776,911 samples, 0.03%)</title><rect x="388.5" y="277" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="391.51" y="287.5" ></text>
</g>
<g >
<title>wakeup_preempt (1,295,584,164 samples, 0.01%)</title><rect x="207.7" y="309" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="210.68" y="319.5" ></text>
</g>
<g >
<title>bms_del_member (2,101,634,744 samples, 0.02%)</title><rect x="879.1" y="485" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="882.08" y="495.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,998,270,464 samples, 0.03%)</title><rect x="352.4" y="341" width="0.4" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="355.44" y="351.5" ></text>
</g>
<g >
<title>PortalStart (7,958,324,688 samples, 0.08%)</title><rect x="462.5" y="581" width="1.0" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="465.48" y="591.5" ></text>
</g>
<g >
<title>__pick_next_task (16,146,153,076 samples, 0.17%)</title><rect x="162.2" y="341" width="2.0" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="165.16" y="351.5" ></text>
</g>
<g >
<title>list_delete_cell (4,595,504,095 samples, 0.05%)</title><rect x="254.0" y="453" width="0.6" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="257.00" y="463.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (2,427,578,805 samples, 0.03%)</title><rect x="492.3" y="277" width="0.3" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="495.26" y="287.5" ></text>
</g>
<g >
<title>CommitTransactionCommand (826,977,544 samples, 0.01%)</title><rect x="39.1" y="757" width="0.1" height="15.0" fill="rgb(205,4,1)" rx="2" ry="2" />
<text  x="42.06" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (871,546,492 samples, 0.01%)</title><rect x="897.1" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="900.09" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (855,853,252 samples, 0.01%)</title><rect x="822.9" y="341" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="825.91" y="351.5" ></text>
</g>
<g >
<title>futex_wake (2,495,102,696 samples, 0.03%)</title><rect x="389.3" y="133" width="0.4" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="392.34" y="143.5" ></text>
</g>
<g >
<title>unix_stream_read_actor (9,840,489,809 samples, 0.10%)</title><rect x="185.6" y="373" width="1.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="188.60" y="383.5" ></text>
</g>
<g >
<title>create_empty_pathtarget (2,543,206,963 samples, 0.03%)</title><rect x="914.4" y="469" width="0.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="917.43" y="479.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (1,886,182,597 samples, 0.02%)</title><rect x="757.4" y="453" width="0.2" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="760.35" y="463.5" ></text>
</g>
<g >
<title>makeVar (3,616,208,516 samples, 0.04%)</title><rect x="856.4" y="357" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="859.41" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,113,937,086 samples, 0.01%)</title><rect x="818.6" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="821.64" y="415.5" ></text>
</g>
<g >
<title>bms_add_member (2,104,247,530 samples, 0.02%)</title><rect x="1058.5" y="469" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1061.52" y="479.5" ></text>
</g>
<g >
<title>_start (7,654,249,878,114 samples, 80.57%)</title><rect x="132.0" y="757" width="950.7" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="134.98" y="767.5" >_start</text>
</g>
<g >
<title>uint32_hash (1,190,299,119 samples, 0.01%)</title><rect x="864.2" y="453" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="867.21" y="463.5" ></text>
</g>
<g >
<title>WaitEventSetWait (190,440,249,407 samples, 2.00%)</title><rect x="154.4" y="517" width="23.7" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="157.42" y="527.5" >W..</text>
</g>
<g >
<title>TidRangeQualFromRestrictInfoList (3,835,226,336 samples, 0.04%)</title><rect x="1025.4" y="389" width="0.5" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="1028.37" y="399.5" ></text>
</g>
<g >
<title>ExecInterpExprStillValid (17,911,071,976 samples, 0.19%)</title><rect x="269.7" y="373" width="2.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="272.68" y="383.5" ></text>
</g>
<g >
<title>bms_add_member (2,494,408,464 samples, 0.03%)</title><rect x="994.9" y="341" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="997.85" y="351.5" ></text>
</g>
<g >
<title>LockHeldByMe (4,744,407,229 samples, 0.05%)</title><rect x="814.2" y="437" width="0.6" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="817.25" y="447.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (10,124,940,418 samples, 0.11%)</title><rect x="249.8" y="453" width="1.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="252.77" y="463.5" ></text>
</g>
<g >
<title>PortalRunMulti (15,531,807,651 samples, 0.16%)</title><rect x="124.0" y="597" width="1.9" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="126.99" y="607.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,114,597,944 samples, 0.03%)</title><rect x="477.7" y="437" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="480.66" y="447.5" ></text>
</g>
<g >
<title>index_other_operands_eval_cost (4,907,375,378 samples, 0.05%)</title><rect x="1012.7" y="293" width="0.6" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="1015.73" y="303.5" ></text>
</g>
<g >
<title>add_eq_member (13,679,848,707 samples, 0.14%)</title><rect x="968.5" y="389" width="1.7" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="971.51" y="399.5" ></text>
</g>
<g >
<title>PageGetHeapFreeSpace (6,686,027,861 samples, 0.07%)</title><rect x="312.6" y="245" width="0.8" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="315.58" y="255.5" ></text>
</g>
<g >
<title>new_list (2,419,776,979 samples, 0.03%)</title><rect x="922.5" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="925.48" y="447.5" ></text>
</g>
<g >
<title>hash_bytes (13,774,387,338 samples, 0.14%)</title><rect x="1142.6" y="757" width="1.7" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1145.59" y="767.5" ></text>
</g>
<g >
<title>index_getprocinfo (1,366,987,364 samples, 0.01%)</title><rect x="342.1" y="261" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="345.13" y="271.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (851,061,516 samples, 0.01%)</title><rect x="836.9" y="309" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="839.90" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (950,996,549 samples, 0.01%)</title><rect x="302.5" y="101" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="305.49" y="111.5" ></text>
</g>
<g >
<title>hash_search (4,792,212,308 samples, 0.05%)</title><rect x="448.3" y="357" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="451.28" y="367.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,163,465,944 samples, 0.01%)</title><rect x="475.6" y="469" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="478.57" y="479.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,703,431,546 samples, 0.05%)</title><rect x="837.0" y="357" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="840.01" y="367.5" ></text>
</g>
<g >
<title>pfree (1,619,584,340 samples, 0.02%)</title><rect x="944.4" y="373" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="947.36" y="383.5" ></text>
</g>
<g >
<title>pq_getbyte (280,681,017,344 samples, 2.95%)</title><rect x="152.4" y="565" width="34.9" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="155.43" y="575.5" >pq..</text>
</g>
<g >
<title>do_syscall_64 (955,025,299 samples, 0.01%)</title><rect x="370.2" y="181" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="373.23" y="191.5" ></text>
</g>
<g >
<title>bms_overlap (1,776,343,779 samples, 0.02%)</title><rect x="383.0" y="357" width="0.3" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="386.05" y="367.5" ></text>
</g>
<g >
<title>ExecIndexScan (15,501,275,807 samples, 0.16%)</title><rect x="124.0" y="437" width="1.9" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="126.99" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,231,729,022 samples, 0.01%)</title><rect x="969.1" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="972.11" y="335.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (1,494,428,742 samples, 0.02%)</title><rect x="1158.9" y="245" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1161.90" y="255.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (990,876,013 samples, 0.01%)</title><rect x="85.6" y="165" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="88.59" y="175.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (8,740,230,110 samples, 0.09%)</title><rect x="809.9" y="357" width="1.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="812.94" y="367.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (824,654,824 samples, 0.01%)</title><rect x="341.7" y="197" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="344.67" y="207.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (820,084,821 samples, 0.01%)</title><rect x="1067.3" y="405" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1070.27" y="415.5" ></text>
</g>
<g >
<title>list_free_private (2,318,654,561 samples, 0.02%)</title><rect x="944.3" y="389" width="0.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="947.27" y="399.5" ></text>
</g>
<g >
<title>create_projection_plan (1,415,588,197 samples, 0.01%)</title><rect x="86.9" y="517" width="0.2" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="89.91" y="527.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (49,053,997,604 samples, 0.52%)</title><rect x="60.0" y="757" width="6.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="63.03" y="767.5" ></text>
</g>
<g >
<title>FastPathGrantRelationLock (5,173,974,241 samples, 0.05%)</title><rect x="940.7" y="341" width="0.7" height="15.0" fill="rgb(245,188,44)" rx="2" ry="2" />
<text  x="943.73" y="351.5" ></text>
</g>
<g >
<title>AfterTriggerBeginXact (1,148,444,201 samples, 0.01%)</title><rect x="1073.7" y="533" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1076.74" y="543.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,068,417,493 samples, 0.01%)</title><rect x="836.9" y="325" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="839.87" y="335.5" ></text>
</g>
<g >
<title>pq_sendint8 (1,724,627,968 samples, 0.02%)</title><rect x="190.2" y="565" width="0.2" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="193.16" y="575.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (988,610,300 samples, 0.01%)</title><rect x="946.6" y="357" width="0.1" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="949.57" y="367.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,124,852,091 samples, 0.01%)</title><rect x="830.9" y="325" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="833.89" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (27,464,774,605 samples, 0.29%)</title><rect x="102.7" y="501" width="3.4" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="105.66" y="511.5" ></text>
</g>
<g >
<title>reweight_entity (1,823,561,732 samples, 0.02%)</title><rect x="206.9" y="277" width="0.2" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="209.86" y="287.5" ></text>
</g>
<g >
<title>BufTableHashCode (2,986,958,315 samples, 0.03%)</title><rect x="100.2" y="181" width="0.3" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="103.15" y="191.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64 (1,743,954,405 samples, 0.02%)</title><rect x="508.8" y="469" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="511.81" y="479.5" ></text>
</g>
<g >
<title>bms_add_member (4,392,799,912 samples, 0.05%)</title><rect x="974.6" y="421" width="0.6" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="977.64" y="431.5" ></text>
</g>
<g >
<title>int4hashfast (1,287,358,690 samples, 0.01%)</title><rect x="431.5" y="277" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="434.47" y="287.5" ></text>
</g>
<g >
<title>palloc0 (3,127,518,799 samples, 0.03%)</title><rect x="1023.6" y="373" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1026.57" y="383.5" ></text>
</g>
<g >
<title>PostmasterMain (17,479,689,687 samples, 0.18%)</title><rect x="126.3" y="709" width="2.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="129.31" y="719.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (3,553,452,724 samples, 0.04%)</title><rect x="333.1" y="213" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="336.05" y="223.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,374,947,025 samples, 0.01%)</title><rect x="99.5" y="165" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="102.46" y="175.5" ></text>
</g>
<g >
<title>psi_task_switch (3,698,538,555 samples, 0.04%)</title><rect x="485.8" y="277" width="0.4" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="488.76" y="287.5" ></text>
</g>
<g >
<title>TransactionLogFetch (9,829,693,526 samples, 0.10%)</title><rect x="308.6" y="197" width="1.3" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="311.63" y="207.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,981,319,256 samples, 0.02%)</title><rect x="357.7" y="373" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="360.70" y="383.5" ></text>
</g>
<g >
<title>pull_varnos (4,371,358,221 samples, 0.05%)</title><rect x="967.1" y="373" width="0.6" height="15.0" fill="rgb(229,112,26)" rx="2" ry="2" />
<text  x="970.13" y="383.5" ></text>
</g>
<g >
<title>palloc0 (1,338,335,883 samples, 0.01%)</title><rect x="967.4" y="293" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="970.42" y="303.5" ></text>
</g>
<g >
<title>SearchSysCache1 (6,531,736,579 samples, 0.07%)</title><rect x="811.2" y="341" width="0.8" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="814.16" y="351.5" ></text>
</g>
<g >
<title>btgettuple (11,204,751,701 samples, 0.12%)</title><rect x="282.8" y="309" width="1.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="285.81" y="319.5" ></text>
</g>
<g >
<title>_bt_checkpage (4,086,931,287 samples, 0.04%)</title><rect x="337.3" y="213" width="0.5" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="340.30" y="223.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (3,324,290,637 samples, 0.03%)</title><rect x="78.0" y="757" width="0.4" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="80.98" y="767.5" ></text>
</g>
<g >
<title>newNode (3,481,640,756 samples, 0.04%)</title><rect x="1016.5" y="341" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1019.47" y="351.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (16,271,788,347 samples, 0.17%)</title><rect x="806.6" y="405" width="2.0" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="809.59" y="415.5" ></text>
</g>
<g >
<title>tag_hash (2,484,839,112 samples, 0.03%)</title><rect x="831.8" y="357" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="834.84" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (11,012,596,052 samples, 0.12%)</title><rect x="889.6" y="325" width="1.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="892.55" y="335.5" ></text>
</g>
<g >
<title>gup_fast_pte_range (1,610,530,955 samples, 0.02%)</title><rect x="492.4" y="261" width="0.2" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="495.36" y="271.5" ></text>
</g>
<g >
<title>hash_search (7,548,364,557 samples, 0.08%)</title><rect x="214.0" y="549" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="217.00" y="559.5" ></text>
</g>
<g >
<title>check_preempt_wakeup_fair (1,053,352,688 samples, 0.01%)</title><rect x="207.7" y="293" width="0.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="210.71" y="303.5" ></text>
</g>
<g >
<title>__futex_wait (3,462,706,530 samples, 0.04%)</title><rect x="366.7" y="165" width="0.5" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="369.74" y="175.5" ></text>
</g>
<g >
<title>put_prev_task_fair (1,021,834,507 samples, 0.01%)</title><rect x="484.4" y="261" width="0.2" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="487.44" y="271.5" ></text>
</g>
<g >
<title>UnGrantLock (1,870,407,717 samples, 0.02%)</title><rect x="526.5" y="453" width="0.2" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="529.52" y="463.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,346,004,108 samples, 0.01%)</title><rect x="478.1" y="341" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="481.15" y="351.5" ></text>
</g>
<g >
<title>fetch_att (950,924,577 samples, 0.01%)</title><rect x="1138.0" y="757" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1140.95" y="767.5" ></text>
</g>
<g >
<title>_bt_compare (7,061,984,186 samples, 0.07%)</title><rect x="126.9" y="245" width="0.9" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="129.89" y="255.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,584,145,858 samples, 0.02%)</title><rect x="101.4" y="133" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="104.41" y="143.5" ></text>
</g>
<g >
<title>AtEOXact_LogicalRepWorkers (1,099,561,921 samples, 0.01%)</title><rect x="31.6" y="757" width="0.2" height="15.0" fill="rgb(231,119,28)" rx="2" ry="2" />
<text  x="34.63" y="767.5" ></text>
</g>
<g >
<title>pfree (2,829,074,053 samples, 0.03%)</title><rect x="803.1" y="533" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="806.10" y="543.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (952,086,204 samples, 0.01%)</title><rect x="474.8" y="485" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="477.81" y="495.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,724,147,688 samples, 0.02%)</title><rect x="1148.5" y="565" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="1151.50" y="575.5" ></text>
</g>
<g >
<title>lcons (2,105,903,530 samples, 0.02%)</title><rect x="1070.7" y="485" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1073.68" y="495.5" ></text>
</g>
<g >
<title>pq_getmsgstring (15,774,286,067 samples, 0.17%)</title><rect x="1079.9" y="597" width="1.9" height="15.0" fill="rgb(233,133,31)" rx="2" ry="2" />
<text  x="1082.88" y="607.5" ></text>
</g>
<g >
<title>stack_is_too_deep (3,171,470,865 samples, 0.03%)</title><rect x="1184.3" y="757" width="0.4" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1187.29" y="767.5" ></text>
</g>
<g >
<title>tag_hash (2,865,863,888 samples, 0.03%)</title><rect x="453.4" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="456.36" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,413,464,147 samples, 0.01%)</title><rect x="1060.6" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1063.57" y="431.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (47,440,171,408 samples, 0.50%)</title><rect x="13.3" y="741" width="5.9" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="16.29" y="751.5" ></text>
</g>
<g >
<title>ExecScanFetch (20,863,793,009 samples, 0.22%)</title><rect x="84.3" y="421" width="2.6" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="87.32" y="431.5" ></text>
</g>
<g >
<title>hash_search (5,152,638,625 samples, 0.05%)</title><rect x="402.0" y="325" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="404.98" y="335.5" ></text>
</g>
<g >
<title>palloc (1,387,788,426 samples, 0.01%)</title><rect x="969.1" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="972.09" y="351.5" ></text>
</g>
<g >
<title>pg_ulltoa_n (3,294,194,350 samples, 0.03%)</title><rect x="218.4" y="549" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="221.42" y="559.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (925,846,291 samples, 0.01%)</title><rect x="801.9" y="549" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="804.95" y="559.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,641,964,487 samples, 0.03%)</title><rect x="1032.7" y="165" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1035.71" y="175.5" ></text>
</g>
<g >
<title>ObjectIdGetDatum (3,796,972,719 samples, 0.04%)</title><rect x="80.4" y="757" width="0.5" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="83.42" y="767.5" ></text>
</g>
<g >
<title>cost_index (155,139,797,715 samples, 1.63%)</title><rect x="997.1" y="341" width="19.3" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1000.14" y="351.5" ></text>
</g>
<g >
<title>XLogInsertAllowed (816,275,749 samples, 0.01%)</title><rect x="111.0" y="757" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="114.04" y="767.5" ></text>
</g>
<g >
<title>palloc0 (1,565,664,833 samples, 0.02%)</title><rect x="420.5" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="423.49" y="447.5" ></text>
</g>
<g >
<title>heap_prune_satisfies_vacuum (11,039,765,090 samples, 0.12%)</title><rect x="1147.4" y="725" width="1.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1150.42" y="735.5" ></text>
</g>
<g >
<title>pfree (1,239,943,872 samples, 0.01%)</title><rect x="954.0" y="421" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="956.96" y="431.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,450,686,337 samples, 0.02%)</title><rect x="296.2" y="293" width="0.2" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="299.23" y="303.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,170,068,295 samples, 0.05%)</title><rect x="901.6" y="389" width="0.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="904.64" y="399.5" ></text>
</g>
<g >
<title>match_restriction_clauses_to_index (28,212,274,403 samples, 0.30%)</title><rect x="1018.4" y="389" width="3.6" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1021.45" y="399.5" ></text>
</g>
<g >
<title>__x64_sys_futex (27,245,196,607 samples, 0.29%)</title><rect x="491.6" y="373" width="3.4" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="494.65" y="383.5" ></text>
</g>
<g >
<title>BufferDescriptorGetBuffer (2,939,238,642 samples, 0.03%)</title><rect x="98.1" y="165" width="0.3" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="101.05" y="175.5" ></text>
</g>
<g >
<title>bms_is_subset (1,792,146,787 samples, 0.02%)</title><rect x="951.4" y="437" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="954.37" y="447.5" ></text>
</g>
<g >
<title>SearchSysCache3 (9,217,732,283 samples, 0.10%)</title><rect x="1032.0" y="213" width="1.2" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1035.02" y="223.5" ></text>
</g>
<g >
<title>newNode (2,313,482,458 samples, 0.02%)</title><rect x="812.2" y="469" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="815.16" y="479.5" ></text>
</g>
<g >
<title>_raw_spin_lock_irq (849,000,005 samples, 0.01%)</title><rect x="158.6" y="373" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="161.62" y="383.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,172,402,378 samples, 0.01%)</title><rect x="369.9" y="229" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="372.94" y="239.5" ></text>
</g>
<g >
<title>BufferGetBlock (884,278,557 samples, 0.01%)</title><rect x="325.2" y="197" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="328.18" y="207.5" ></text>
</g>
<g >
<title>extract_restriction_or_clauses (1,451,081,424 samples, 0.02%)</title><rect x="978.5" y="469" width="0.2" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="981.52" y="479.5" ></text>
</g>
<g >
<title>coerce_type (807,331,385 samples, 0.01%)</title><rect x="844.1" y="421" width="0.1" height="15.0" fill="rgb(205,2,0)" rx="2" ry="2" />
<text  x="847.06" y="431.5" ></text>
</g>
<g >
<title>palloc0 (3,523,434,439 samples, 0.04%)</title><rect x="891.8" y="357" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="894.79" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,064,622,194 samples, 0.02%)</title><rect x="817.7" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="820.70" y="399.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (7,827,969,943 samples, 0.08%)</title><rect x="869.3" y="485" width="1.0" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="872.30" y="495.5" ></text>
</g>
<g >
<title>uint32_hash (982,120,582 samples, 0.01%)</title><rect x="449.5" y="357" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="452.55" y="367.5" ></text>
</g>
</g>
</svg>
master-flame-a.svgapplication/octet-streamDownload
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" width="1200" height="838" onload="init(evt)" viewBox="0 0 1200 838" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples. -->
<!-- NOTES:  -->
<defs>
	<linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
		<stop stop-color="#eeeeee" offset="5%" />
		<stop stop-color="#eeeeb0" offset="95%" />
	</linearGradient>
</defs>
<style type="text/css">
	text { font-family:Verdana; font-size:12px; fill:rgb(0,0,0); }
	#search, #ignorecase { opacity:0.1; cursor:pointer; }
	#search:hover, #search.show, #ignorecase:hover, #ignorecase.show { opacity:1; }
	#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }
	#title { text-anchor:middle; font-size:17px}
	#unzoom { cursor:pointer; }
	#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
	.hide { display:none; }
	.parent { opacity:0.5; }
</style>
<script type="text/ecmascript">
<![CDATA[
	"use strict";
	var details, searchbtn, unzoombtn, matchedtxt, svg, searching, currentSearchTerm, ignorecase, ignorecaseBtn;
	function init(evt) {
		details = document.getElementById("details").firstChild;
		searchbtn = document.getElementById("search");
		ignorecaseBtn = document.getElementById("ignorecase");
		unzoombtn = document.getElementById("unzoom");
		matchedtxt = document.getElementById("matched");
		svg = document.getElementsByTagName("svg")[0];
		searching = 0;
		currentSearchTerm = null;

		// use GET parameters to restore a flamegraphs state.
		var params = get_params();
		if (params.x && params.y)
			zoom(find_group(document.querySelector('[x="' + params.x + '"][y="' + params.y + '"]')));
                if (params.s) search(params.s);
	}

	// event listeners
	window.addEventListener("click", function(e) {
		var target = find_group(e.target);
		if (target) {
			if (target.nodeName == "a") {
				if (e.ctrlKey === false) return;
				e.preventDefault();
			}
			if (target.classList.contains("parent")) unzoom(true);
			zoom(target);
			if (!document.querySelector('.parent')) {
				// we have basically done a clearzoom so clear the url
				var params = get_params();
				if (params.x) delete params.x;
				if (params.y) delete params.y;
				history.replaceState(null, null, parse_params(params));
				unzoombtn.classList.add("hide");
				return;
			}

			// set parameters for zoom state
			var el = target.querySelector("rect");
			if (el && el.attributes && el.attributes.y && el.attributes._orig_x) {
				var params = get_params()
				params.x = el.attributes._orig_x.value;
				params.y = el.attributes.y.value;
				history.replaceState(null, null, parse_params(params));
			}
		}
		else if (e.target.id == "unzoom") clearzoom();
		else if (e.target.id == "search") search_prompt();
		else if (e.target.id == "ignorecase") toggle_ignorecase();
	}, false)

	// mouse-over for info
	// show
	window.addEventListener("mouseover", function(e) {
		var target = find_group(e.target);
		if (target) details.nodeValue = "Function: " + g_to_text(target);
	}, false)

	// clear
	window.addEventListener("mouseout", function(e) {
		var target = find_group(e.target);
		if (target) details.nodeValue = ' ';
	}, false)

	// ctrl-F for search
	// ctrl-I to toggle case-sensitive search
	window.addEventListener("keydown",function (e) {
		if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {
			e.preventDefault();
			search_prompt();
		}
		else if (e.ctrlKey && e.keyCode === 73) {
			e.preventDefault();
			toggle_ignorecase();
		}
	}, false)

	// functions
	function get_params() {
		var params = {};
		var paramsarr = window.location.search.substr(1).split('&');
		for (var i = 0; i < paramsarr.length; ++i) {
			var tmp = paramsarr[i].split("=");
			if (!tmp[0] || !tmp[1]) continue;
			params[tmp[0]]  = decodeURIComponent(tmp[1]);
		}
		return params;
	}
	function parse_params(params) {
		var uri = "?";
		for (var key in params) {
			uri += key + '=' + encodeURIComponent(params[key]) + '&';
		}
		if (uri.slice(-1) == "&")
			uri = uri.substring(0, uri.length - 1);
		if (uri == '?')
			uri = window.location.href.split('?')[0];
		return uri;
	}
	function find_child(node, selector) {
		var children = node.querySelectorAll(selector);
		if (children.length) return children[0];
	}
	function find_group(node) {
		var parent = node.parentElement;
		if (!parent) return;
		if (parent.id == "frames") return node;
		return find_group(parent);
	}
	function orig_save(e, attr, val) {
		if (e.attributes["_orig_" + attr] != undefined) return;
		if (e.attributes[attr] == undefined) return;
		if (val == undefined) val = e.attributes[attr].value;
		e.setAttribute("_orig_" + attr, val);
	}
	function orig_load(e, attr) {
		if (e.attributes["_orig_"+attr] == undefined) return;
		e.attributes[attr].value = e.attributes["_orig_" + attr].value;
		e.removeAttribute("_orig_"+attr);
	}
	function g_to_text(e) {
		var text = find_child(e, "title").firstChild.nodeValue;
		return (text)
	}
	function g_to_func(e) {
		var func = g_to_text(e);
		// if there's any manipulation we want to do to the function
		// name before it's searched, do it here before returning.
		return (func);
	}
	function update_text(e) {
		var r = find_child(e, "rect");
		var t = find_child(e, "text");
		var w = parseFloat(r.attributes.width.value) -3;
		var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
		t.attributes.x.value = parseFloat(r.attributes.x.value) + 3;

		// Smaller than this size won't fit anything
		if (w < 2 * 12 * 0.59) {
			t.textContent = "";
			return;
		}

		t.textContent = txt;
		var sl = t.getSubStringLength(0, txt.length);
		// check if only whitespace or if we can fit the entire string into width w
		if (/^ *$/.test(txt) || sl < w)
			return;

		// this isn't perfect, but gives a good starting point
		// and avoids calling getSubStringLength too often
		var start = Math.floor((w/sl) * txt.length);
		for (var x = start; x > 0; x = x-2) {
			if (t.getSubStringLength(0, x + 2) <= w) {
				t.textContent = txt.substring(0, x) + "..";
				return;
			}
		}
		t.textContent = "";
	}

	// zoom
	function zoom_reset(e) {
		if (e.attributes != undefined) {
			orig_load(e, "x");
			orig_load(e, "width");
		}
		if (e.childNodes == undefined) return;
		for (var i = 0, c = e.childNodes; i < c.length; i++) {
			zoom_reset(c[i]);
		}
	}
	function zoom_child(e, x, ratio) {
		if (e.attributes != undefined) {
			if (e.attributes.x != undefined) {
				orig_save(e, "x");
				e.attributes.x.value = (parseFloat(e.attributes.x.value) - x - 10) * ratio + 10;
				if (e.tagName == "text")
					e.attributes.x.value = find_child(e.parentNode, "rect[x]").attributes.x.value + 3;
			}
			if (e.attributes.width != undefined) {
				orig_save(e, "width");
				e.attributes.width.value = parseFloat(e.attributes.width.value) * ratio;
			}
		}

		if (e.childNodes == undefined) return;
		for (var i = 0, c = e.childNodes; i < c.length; i++) {
			zoom_child(c[i], x - 10, ratio);
		}
	}
	function zoom_parent(e) {
		if (e.attributes) {
			if (e.attributes.x != undefined) {
				orig_save(e, "x");
				e.attributes.x.value = 10;
			}
			if (e.attributes.width != undefined) {
				orig_save(e, "width");
				e.attributes.width.value = parseInt(svg.width.baseVal.value) - (10 * 2);
			}
		}
		if (e.childNodes == undefined) return;
		for (var i = 0, c = e.childNodes; i < c.length; i++) {
			zoom_parent(c[i]);
		}
	}
	function zoom(node) {
		var attr = find_child(node, "rect").attributes;
		var width = parseFloat(attr.width.value);
		var xmin = parseFloat(attr.x.value);
		var xmax = parseFloat(xmin + width);
		var ymin = parseFloat(attr.y.value);
		var ratio = (svg.width.baseVal.value - 2 * 10) / width;

		// XXX: Workaround for JavaScript float issues (fix me)
		var fudge = 0.0001;

		unzoombtn.classList.remove("hide");

		var el = document.getElementById("frames").children;
		for (var i = 0; i < el.length; i++) {
			var e = el[i];
			var a = find_child(e, "rect").attributes;
			var ex = parseFloat(a.x.value);
			var ew = parseFloat(a.width.value);
			var upstack;
			// Is it an ancestor
			if (0 == 0) {
				upstack = parseFloat(a.y.value) > ymin;
			} else {
				upstack = parseFloat(a.y.value) < ymin;
			}
			if (upstack) {
				// Direct ancestor
				if (ex <= xmin && (ex+ew+fudge) >= xmax) {
					e.classList.add("parent");
					zoom_parent(e);
					update_text(e);
				}
				// not in current path
				else
					e.classList.add("hide");
			}
			// Children maybe
			else {
				// no common path
				if (ex < xmin || ex + fudge >= xmax) {
					e.classList.add("hide");
				}
				else {
					zoom_child(e, xmin, ratio);
					update_text(e);
				}
			}
		}
		search();
	}
	function unzoom(dont_update_text) {
		unzoombtn.classList.add("hide");
		var el = document.getElementById("frames").children;
		for(var i = 0; i < el.length; i++) {
			el[i].classList.remove("parent");
			el[i].classList.remove("hide");
			zoom_reset(el[i]);
			if(!dont_update_text) update_text(el[i]);
		}
		search();
	}
	function clearzoom() {
		unzoom();

		// remove zoom state
		var params = get_params();
		if (params.x) delete params.x;
		if (params.y) delete params.y;
		history.replaceState(null, null, parse_params(params));
	}

	// search
	function toggle_ignorecase() {
		ignorecase = !ignorecase;
		if (ignorecase) {
			ignorecaseBtn.classList.add("show");
		} else {
			ignorecaseBtn.classList.remove("show");
		}
		reset_search();
		search();
	}
	function reset_search() {
		var el = document.querySelectorAll("#frames rect");
		for (var i = 0; i < el.length; i++) {
			orig_load(el[i], "fill")
		}
		var params = get_params();
		delete params.s;
		history.replaceState(null, null, parse_params(params));
	}
	function search_prompt() {
		if (!searching) {
			var term = prompt("Enter a search term (regexp " +
			    "allowed, eg: ^ext4_)"
			    + (ignorecase ? ", ignoring case" : "")
			    + "\nPress Ctrl-i to toggle case sensitivity", "");
			if (term != null) search(term);
		} else {
			reset_search();
			searching = 0;
			currentSearchTerm = null;
			searchbtn.classList.remove("show");
			searchbtn.firstChild.nodeValue = "Search"
			matchedtxt.classList.add("hide");
			matchedtxt.firstChild.nodeValue = ""
		}
	}
	function search(term) {
		if (term) currentSearchTerm = term;

		var re = new RegExp(currentSearchTerm, ignorecase ? 'i' : '');
		var el = document.getElementById("frames").children;
		var matches = new Object();
		var maxwidth = 0;
		for (var i = 0; i < el.length; i++) {
			var e = el[i];
			var func = g_to_func(e);
			var rect = find_child(e, "rect");
			if (func == null || rect == null)
				continue;

			// Save max width. Only works as we have a root frame
			var w = parseFloat(rect.attributes.width.value);
			if (w > maxwidth)
				maxwidth = w;

			if (func.match(re)) {
				// highlight
				var x = parseFloat(rect.attributes.x.value);
				orig_save(rect, "fill");
				rect.attributes.fill.value = "rgb(230,0,230)";

				// remember matches
				if (matches[x] == undefined) {
					matches[x] = w;
				} else {
					if (w > matches[x]) {
						// overwrite with parent
						matches[x] = w;
					}
				}
				searching = 1;
			}
		}
		if (!searching)
			return;
		var params = get_params();
		params.s = currentSearchTerm;
		history.replaceState(null, null, parse_params(params));

		searchbtn.classList.add("show");
		searchbtn.firstChild.nodeValue = "Reset Search";

		// calculate percent matched, excluding vertical overlap
		var count = 0;
		var lastx = -1;
		var lastw = 0;
		var keys = Array();
		for (k in matches) {
			if (matches.hasOwnProperty(k))
				keys.push(k);
		}
		// sort the matched frames by their x location
		// ascending, then width descending
		keys.sort(function(a, b){
			return a - b;
		});
		// Step through frames saving only the biggest bottom-up frames
		// thanks to the sort order. This relies on the tree property
		// where children are always smaller than their parents.
		var fudge = 0.0001;	// JavaScript floating point
		for (var k in keys) {
			var x = parseFloat(keys[k]);
			var w = matches[keys[k]];
			if (x >= lastx + lastw - fudge) {
				count += w;
				lastx = x;
				lastw = w;
			}
		}
		// display matched percent
		matchedtxt.classList.remove("hide");
		var pct = 100 * count / maxwidth;
		if (pct != 100) pct = pct.toFixed(1)
		matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
	}
]]>
</script>
<rect x="0.0" y="0" width="1200.0" height="838.0" fill="url(#background)"  />
<text id="title" x="600.00" y="24" >Flame Graph</text>
<text id="details" x="10.00" y="821" > </text>
<text id="unzoom" x="10.00" y="24" class="hide">Reset Zoom</text>
<text id="search" x="1090.00" y="24" >Search</text>
<text id="ignorecase" x="1174.00" y="24" >ic</text>
<text id="matched" x="1090.00" y="821" > </text>
<g id="frames">
<g >
<title>dequeue_entities (44,609,841,011 samples, 0.47%)</title><rect x="166.1" y="309" width="5.5" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="169.07" y="319.5" ></text>
</g>
<g >
<title>pgstat_get_transactional_drops (4,536,783,496 samples, 0.05%)</title><rect x="509.5" y="501" width="0.5" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="512.46" y="511.5" ></text>
</g>
<g >
<title>relation_close (1,570,372,860 samples, 0.02%)</title><rect x="860.8" y="501" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="863.81" y="511.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (3,526,644,061 samples, 0.04%)</title><rect x="1061.4" y="437" width="0.5" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1064.44" y="447.5" ></text>
</g>
<g >
<title>ExecProcNode (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="453" width="1.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="127.59" y="463.5" ></text>
</g>
<g >
<title>ExecAssignExprContext (10,370,485,290 samples, 0.11%)</title><rect x="269.1" y="421" width="1.3" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="272.06" y="431.5" ></text>
</g>
<g >
<title>AllocSetReset (8,240,249,433 samples, 0.09%)</title><rect x="132.8" y="501" width="1.0" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="135.78" y="511.5" ></text>
</g>
<g >
<title>new_list (1,594,352,004 samples, 0.02%)</title><rect x="908.7" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="911.65" y="447.5" ></text>
</g>
<g >
<title>ShutdownExprContext (884,907,702 samples, 0.01%)</title><rect x="251.3" y="469" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="254.31" y="479.5" ></text>
</g>
<g >
<title>pg_plan_query (1,980,044,175 samples, 0.02%)</title><rect x="85.4" y="629" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="88.40" y="639.5" ></text>
</g>
<g >
<title>palloc0 (1,472,769,210 samples, 0.02%)</title><rect x="274.3" y="373" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="277.28" y="383.5" ></text>
</g>
<g >
<title>TransactionIdDidCommit (896,760,605 samples, 0.01%)</title><rect x="106.2" y="757" width="0.2" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="109.25" y="767.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (840,264,135 samples, 0.01%)</title><rect x="869.0" y="421" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="871.96" y="431.5" ></text>
</g>
<g >
<title>palloc (1,421,209,056 samples, 0.02%)</title><rect x="986.3" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="989.31" y="351.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (845,456,735 samples, 0.01%)</title><rect x="407.8" y="373" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="410.79" y="383.5" ></text>
</g>
<g >
<title>GlobalVisHorizonKindForRel (808,252,124 samples, 0.01%)</title><rect x="309.9" y="229" width="0.1" height="15.0" fill="rgb(238,154,37)" rx="2" ry="2" />
<text  x="312.87" y="239.5" ></text>
</g>
<g >
<title>inode_to_bdi (1,186,215,350 samples, 0.01%)</title><rect x="496.4" y="341" width="0.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="499.41" y="351.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (42,534,757,602 samples, 0.45%)</title><rect x="322.9" y="261" width="5.3" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="325.90" y="271.5" ></text>
</g>
<g >
<title>smgrdestroyall (893,520,797 samples, 0.01%)</title><rect x="1183.3" y="757" width="0.1" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1186.32" y="767.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (827,869,453 samples, 0.01%)</title><rect x="471.0" y="421" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="473.95" y="431.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (2,903,008,530 samples, 0.03%)</title><rect x="320.5" y="181" width="0.3" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="323.45" y="191.5" ></text>
</g>
<g >
<title>addRTEPermissionInfo (826,596,041 samples, 0.01%)</title><rect x="1084.0" y="757" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="1087.02" y="767.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (19,742,946,219 samples, 0.21%)</title><rect x="399.3" y="325" width="2.4" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="402.26" y="335.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (3,268,834,145 samples, 0.03%)</title><rect x="220.0" y="549" width="0.4" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="222.99" y="559.5" ></text>
</g>
<g >
<title>skb_set_owner_w (1,248,760,553 samples, 0.01%)</title><rect x="197.5" y="389" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="200.51" y="399.5" ></text>
</g>
<g >
<title>internal_flush (152,378,027,097 samples, 1.61%)</title><rect x="188.2" y="565" width="19.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="191.22" y="575.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (10,200,075,643 samples, 0.11%)</title><rect x="417.5" y="325" width="1.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="420.47" y="335.5" ></text>
</g>
<g >
<title>palloc (1,144,057,692 samples, 0.01%)</title><rect x="954.0" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="957.03" y="415.5" ></text>
</g>
<g >
<title>uint32_hash (1,335,522,170 samples, 0.01%)</title><rect x="939.0" y="373" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="941.95" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32_impl (1,052,098,152 samples, 0.01%)</title><rect x="118.6" y="741" width="0.2" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="121.62" y="751.5" ></text>
</g>
<g >
<title>pg_verify_mbstr (10,386,466,929 samples, 0.11%)</title><rect x="1081.8" y="549" width="1.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1084.78" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,185,811,592 samples, 0.01%)</title><rect x="408.2" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="411.21" y="431.5" ></text>
</g>
<g >
<title>set_base_rel_pathlists (337,516,953,485 samples, 3.58%)</title><rect x="984.2" y="453" width="42.2" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="987.18" y="463.5" >set..</text>
</g>
<g >
<title>get_hash_entry (10,993,113,674 samples, 0.12%)</title><rect x="1065.4" y="437" width="1.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1068.35" y="447.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,169,333,570 samples, 0.01%)</title><rect x="446.3" y="341" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="449.31" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (909,430,388 samples, 0.01%)</title><rect x="101.1" y="165" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="104.08" y="175.5" ></text>
</g>
<g >
<title>__futex_wait (1,043,939,329 samples, 0.01%)</title><rect x="354.4" y="133" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="357.39" y="143.5" ></text>
</g>
<g >
<title>MemoryContextDelete (16,262,479,630 samples, 0.17%)</title><rect x="131.9" y="565" width="2.0" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="134.86" y="575.5" ></text>
</g>
<g >
<title>BufferGetPage (937,295,373 samples, 0.01%)</title><rect x="327.1" y="229" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="330.12" y="239.5" ></text>
</g>
<g >
<title>ForEachLWLockHeldByMe (1,608,393,264 samples, 0.02%)</title><rect x="30.0" y="741" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="33.02" y="751.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (18,089,130,645 samples, 0.19%)</title><rect x="915.5" y="469" width="2.3" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="918.50" y="479.5" ></text>
</g>
<g >
<title>standard_planner (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="613" width="3.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="104.45" y="623.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (975,983,726 samples, 0.01%)</title><rect x="126.7" y="421" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="129.71" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,893,822,152 samples, 0.02%)</title><rect x="232.2" y="469" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="235.15" y="479.5" ></text>
</g>
<g >
<title>futex_wake (3,089,114,190 samples, 0.03%)</title><rect x="352.0" y="165" width="0.4" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="354.97" y="175.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,349,435,659 samples, 0.02%)</title><rect x="992.4" y="309" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="995.36" y="319.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,265,159,031 samples, 0.01%)</title><rect x="367.8" y="229" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="370.76" y="239.5" ></text>
</g>
<g >
<title>ExecEndPlan (115,614,014,685 samples, 1.23%)</title><rect x="235.2" y="501" width="14.4" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="238.19" y="511.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,418,667,084 samples, 0.02%)</title><rect x="354.7" y="197" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="357.65" y="207.5" ></text>
</g>
<g >
<title>ExecScanExtended (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="421" width="0.5" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1160.94" y="431.5" ></text>
</g>
<g >
<title>ExecResetTupleTable (21,709,987,811 samples, 0.23%)</title><rect x="246.9" y="485" width="2.7" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="249.90" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,221,868,667 samples, 0.01%)</title><rect x="954.4" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="957.40" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (907,106,914 samples, 0.01%)</title><rect x="888.5" y="277" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="891.47" y="287.5" ></text>
</g>
<g >
<title>SearchSysCacheExists (7,899,066,715 samples, 0.08%)</title><rect x="1021.5" y="293" width="1.0" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="1024.52" y="303.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (3,813,690,657 samples, 0.04%)</title><rect x="990.8" y="357" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="993.75" y="367.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (19,200,341,315 samples, 0.20%)</title><rect x="865.7" y="485" width="2.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="868.71" y="495.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,160,407,318 samples, 0.01%)</title><rect x="1000.1" y="293" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1003.10" y="303.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (889,101,814 samples, 0.01%)</title><rect x="965.1" y="325" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="968.12" y="335.5" ></text>
</g>
<g >
<title>SetCurrentStatementStartTimestamp (3,618,088,738 samples, 0.04%)</title><rect x="207.5" y="597" width="0.5" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="210.50" y="607.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (13,466,994,473 samples, 0.14%)</title><rect x="916.1" y="437" width="1.7" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="919.07" y="447.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="405" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="88.40" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (883,619,455 samples, 0.01%)</title><rect x="1054.5" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1057.46" y="399.5" ></text>
</g>
<g >
<title>__strncmp_avx2 (1,304,416,648 samples, 0.01%)</title><rect x="832.3" y="437" width="0.1" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="835.26" y="447.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,468,590,769 samples, 0.03%)</title><rect x="941.5" y="341" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="944.48" y="351.5" ></text>
</g>
<g >
<title>transform_MERGE_to_join (801,036,985 samples, 0.01%)</title><rect x="1187.8" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1190.85" y="767.5" ></text>
</g>
<g >
<title>PinBufferForBlock (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="213" width="2.3" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="102.04" y="223.5" ></text>
</g>
<g >
<title>select_idle_cpu (15,517,127,849 samples, 0.16%)</title><rect x="200.9" y="277" width="1.9" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="203.88" y="287.5" ></text>
</g>
<g >
<title>mdnblocks (17,176,022,184 samples, 0.18%)</title><rect x="936.0" y="277" width="2.1" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="938.96" y="287.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (12,978,984,891 samples, 0.14%)</title><rect x="888.9" y="325" width="1.6" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="891.87" y="335.5" ></text>
</g>
<g >
<title>DeconstructQualifiedName (857,300,887 samples, 0.01%)</title><rect x="40.8" y="757" width="0.1" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="43.77" y="767.5" ></text>
</g>
<g >
<title>_bt_getroot (1,932,277,437 samples, 0.02%)</title><rect x="126.0" y="261" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="129.00" y="271.5" ></text>
</g>
<g >
<title>StartReadBuffer (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="245" width="2.3" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="102.04" y="255.5" ></text>
</g>
<g >
<title>new_list (1,472,325,070 samples, 0.02%)</title><rect x="1057.6" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1060.63" y="399.5" ></text>
</g>
<g >
<title>BufferIsValid (810,877,120 samples, 0.01%)</title><rect x="309.7" y="213" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="312.70" y="223.5" ></text>
</g>
<g >
<title>__update_load_avg_cfs_rq (2,424,785,884 samples, 0.03%)</title><rect x="170.4" y="261" width="0.3" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="173.42" y="271.5" ></text>
</g>
<g >
<title>__slab_free (1,699,667,684 samples, 0.02%)</title><rect x="182.2" y="357" width="0.2" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="185.20" y="367.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,235,986,369 samples, 0.01%)</title><rect x="162.0" y="325" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="165.04" y="335.5" ></text>
</g>
<g >
<title>newNode (8,998,838,963 samples, 0.10%)</title><rect x="894.2" y="517" width="1.1" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="897.21" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (827,924,465 samples, 0.01%)</title><rect x="439.2" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="442.22" y="367.5" ></text>
</g>
<g >
<title>DecrTupleDescRefCount (3,147,505,294 samples, 0.03%)</title><rect x="247.5" y="469" width="0.4" height="15.0" fill="rgb(243,174,41)" rx="2" ry="2" />
<text  x="250.54" y="479.5" ></text>
</g>
<g >
<title>GetNewTransactionId (33,490,748,475 samples, 0.35%)</title><rect x="348.6" y="325" width="4.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="351.63" y="335.5" ></text>
</g>
<g >
<title>simplify_function (886,882,643 samples, 0.01%)</title><rect x="124.5" y="469" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="127.48" y="479.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (6,583,216,669 samples, 0.07%)</title><rect x="1052.3" y="469" width="0.8" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1055.28" y="479.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (886,882,643 samples, 0.01%)</title><rect x="124.5" y="533" width="0.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="127.48" y="543.5" ></text>
</g>
<g >
<title>list_make1_impl (1,831,392,866 samples, 0.02%)</title><rect x="977.2" y="421" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="980.15" y="431.5" ></text>
</g>
<g >
<title>ExecBSUpdateTriggers (882,636,965 samples, 0.01%)</title><rect x="41.7" y="757" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="44.66" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,336,268,219 samples, 0.02%)</title><rect x="927.9" y="389" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="930.95" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,564,809,533 samples, 0.02%)</title><rect x="508.1" y="405" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="511.09" y="415.5" ></text>
</g>
<g >
<title>bms_copy (1,605,762,392 samples, 0.02%)</title><rect x="1121.9" y="757" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="1124.94" y="767.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,200,269,769 samples, 0.01%)</title><rect x="845.1" y="389" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="848.13" y="399.5" ></text>
</g>
<g >
<title>coerce_type_typmod (1,673,279,291 samples, 0.02%)</title><rect x="842.0" y="421" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="844.97" y="431.5" ></text>
</g>
<g >
<title>SIGetDataEntries (2,079,922,811 samples, 0.02%)</title><rect x="1075.1" y="485" width="0.3" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1078.12" y="495.5" ></text>
</g>
<g >
<title>_bt_returnitem (1,525,724,336 samples, 0.02%)</title><rect x="328.2" y="261" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="331.22" y="271.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (2,316,874,039 samples, 0.02%)</title><rect x="248.8" y="405" width="0.3" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="251.85" y="415.5" ></text>
</g>
<g >
<title>PageGetHeapFreeSpace (3,881,061,467 samples, 0.04%)</title><rect x="365.7" y="357" width="0.5" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="368.72" y="367.5" ></text>
</g>
<g >
<title>ExecProcNode (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="517" width="1.7" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="125.45" y="527.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (8,937,557,829 samples, 0.09%)</title><rect x="337.8" y="229" width="1.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="340.77" y="239.5" ></text>
</g>
<g >
<title>tag_hash (2,370,298,217 samples, 0.03%)</title><rect x="99.1" y="149" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="102.14" y="159.5" ></text>
</g>
<g >
<title>pick_next_task_fair (3,652,879,866 samples, 0.04%)</title><rect x="476.7" y="261" width="0.4" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="479.66" y="271.5" ></text>
</g>
<g >
<title>btcostestimate (1,229,153,874 samples, 0.01%)</title><rect x="1123.9" y="757" width="0.1" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="1126.87" y="767.5" ></text>
</g>
<g >
<title>fix_indexqual_clause (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="437" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="88.40" y="447.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (4,860,839,258 samples, 0.05%)</title><rect x="1052.5" y="437" width="0.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1055.50" y="447.5" ></text>
</g>
<g >
<title>palloc (2,064,559,650 samples, 0.02%)</title><rect x="1119.6" y="693" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1122.59" y="703.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,174,188,956 samples, 0.01%)</title><rect x="270.2" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="273.19" y="351.5" ></text>
</g>
<g >
<title>LWLockRelease (1,474,388,567 samples, 0.02%)</title><rect x="325.5" y="181" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="328.47" y="191.5" ></text>
</g>
<g >
<title>message_level_is_interesting (1,021,529,034 samples, 0.01%)</title><rect x="1077.7" y="517" width="0.1" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="1080.68" y="527.5" ></text>
</g>
<g >
<title>index_getnext_slot (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="389" width="2.8" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="85.62" y="399.5" ></text>
</g>
<g >
<title>expand_function_arguments (1,531,027,389 samples, 0.02%)</title><rect x="103.9" y="453" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="106.88" y="463.5" ></text>
</g>
<g >
<title>string_hash (3,160,551,655 samples, 0.03%)</title><rect x="212.2" y="533" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="215.24" y="543.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,939,724,728 samples, 0.02%)</title><rect x="470.0" y="405" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="472.97" y="415.5" ></text>
</g>
<g >
<title>_raw_spin_lock (1,016,382,047 samples, 0.01%)</title><rect x="382.3" y="53" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="385.25" y="63.5" ></text>
</g>
<g >
<title>AtEOXact_PgStat_Relations (2,540,477,139 samples, 0.03%)</title><rect x="462.7" y="501" width="0.3" height="15.0" fill="rgb(228,108,26)" rx="2" ry="2" />
<text  x="465.73" y="511.5" ></text>
</g>
<g >
<title>new_list (1,706,761,396 samples, 0.02%)</title><rect x="914.0" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="916.96" y="463.5" ></text>
</g>
<g >
<title>newNode (3,501,699,598 samples, 0.04%)</title><rect x="269.9" y="373" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="272.90" y="383.5" ></text>
</g>
<g >
<title>ep_item_poll.isra.0 (10,109,401,802 samples, 0.11%)</title><rect x="156.4" y="373" width="1.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="159.42" y="383.5" ></text>
</g>
<g >
<title>uint32_hash (1,301,747,877 samples, 0.01%)</title><rect x="395.7" y="325" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="398.71" y="335.5" ></text>
</g>
<g >
<title>index_getprocinfo (1,666,586,934 samples, 0.02%)</title><rect x="340.5" y="261" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="343.48" y="271.5" ></text>
</g>
<g >
<title>PortalRun (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="661" width="6.7" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="97.67" y="671.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesUpdate (1,458,865,369 samples, 0.02%)</title><rect x="361.8" y="357" width="0.2" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="364.81" y="367.5" ></text>
</g>
<g >
<title>list_nth_cell (954,016,919 samples, 0.01%)</title><rect x="1036.4" y="245" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1039.43" y="255.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (11,170,231,142 samples, 0.12%)</title><rect x="923.2" y="437" width="1.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="926.21" y="447.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (821,427,597 samples, 0.01%)</title><rect x="695.3" y="469" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="698.32" y="479.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (884,216,088 samples, 0.01%)</title><rect x="85.3" y="165" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="88.28" y="175.5" ></text>
</g>
<g >
<title>IndexNext (420,755,174,438 samples, 4.46%)</title><rect x="289.3" y="341" width="52.6" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="292.31" y="351.5" >Index..</text>
</g>
<g >
<title>ExecInitNode (234,870,732,994 samples, 2.49%)</title><rect x="413.5" y="453" width="29.4" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="416.54" y="463.5" >Ex..</text>
</g>
<g >
<title>pfree (3,506,769,842 samples, 0.04%)</title><rect x="1165.9" y="757" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1168.88" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,504,451,388 samples, 0.02%)</title><rect x="889.3" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="892.30" y="303.5" ></text>
</g>
<g >
<title>pq_sendbyte (1,742,272,272 samples, 0.02%)</title><rect x="187.9" y="581" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="190.89" y="591.5" ></text>
</g>
<g >
<title>pfree (980,030,519 samples, 0.01%)</title><rect x="396.0" y="357" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="398.99" y="367.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,745,947,782 samples, 0.02%)</title><rect x="309.6" y="229" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="312.58" y="239.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (3,026,918,879 samples, 0.03%)</title><rect x="791.3" y="485" width="0.4" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="794.28" y="495.5" ></text>
</g>
<g >
<title>eval_const_expressions (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="565" width="3.4" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="104.45" y="575.5" ></text>
</g>
<g >
<title>tts_virtual_clear (1,811,635,697 samples, 0.02%)</title><rect x="283.0" y="325" width="0.2" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="285.97" y="335.5" ></text>
</g>
<g >
<title>raw_parser (1,820,219,272 samples, 0.02%)</title><rect x="1174.9" y="757" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1177.88" y="767.5" ></text>
</g>
<g >
<title>list_free_private (1,688,689,809 samples, 0.02%)</title><rect x="251.9" y="405" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="254.88" y="415.5" ></text>
</g>
<g >
<title>transform_MERGE_to_join (927,930,830 samples, 0.01%)</title><rect x="1071.9" y="501" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1074.93" y="511.5" ></text>
</g>
<g >
<title>pfree (1,223,751,280 samples, 0.01%)</title><rect x="251.9" y="389" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="254.93" y="399.5" ></text>
</g>
<g >
<title>hash_search (3,709,488,072 samples, 0.04%)</title><rect x="924.8" y="421" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="927.76" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1161.36" y="415.5" ></text>
</g>
<g >
<title>ReadBufferExtended (1,217,297,839 samples, 0.01%)</title><rect x="124.1" y="229" width="0.1" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="127.08" y="239.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,054,547,032 samples, 0.01%)</title><rect x="100.8" y="165" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="103.85" y="175.5" ></text>
</g>
<g >
<title>is_publishable_relation (1,721,336,997 samples, 0.02%)</title><rect x="1152.6" y="757" width="0.3" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="1155.64" y="767.5" ></text>
</g>
<g >
<title>hash_search (6,111,571,259 samples, 0.06%)</title><rect x="214.3" y="565" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="217.32" y="575.5" ></text>
</g>
<g >
<title>pg_nextpower2_32 (972,440,580 samples, 0.01%)</title><rect x="1169.2" y="757" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1172.24" y="767.5" ></text>
</g>
<g >
<title>hash_search (14,167,339,736 samples, 0.15%)</title><rect x="519.4" y="437" width="1.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="522.45" y="447.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="629" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1136.99" y="639.5" ></text>
</g>
<g >
<title>list_nth (972,641,823 samples, 0.01%)</title><rect x="275.5" y="421" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="278.50" y="431.5" ></text>
</g>
<g >
<title>buildRelationAliases (15,895,879,834 samples, 0.17%)</title><rect x="813.3" y="453" width="1.9" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="816.26" y="463.5" ></text>
</g>
<g >
<title>lappend (2,157,485,124 samples, 0.02%)</title><rect x="913.9" y="469" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="916.91" y="479.5" ></text>
</g>
<g >
<title>GetSnapshotData (32,597,887,246 samples, 0.35%)</title><rect x="217.4" y="565" width="4.1" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="220.39" y="575.5" ></text>
</g>
<g >
<title>relation_openrv_extended (960,881,819 samples, 0.01%)</title><rect x="1175.6" y="757" width="0.2" height="15.0" fill="rgb(231,119,28)" rx="2" ry="2" />
<text  x="1178.65" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,350,219,871 samples, 0.02%)</title><rect x="896.6" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="899.64" y="447.5" ></text>
</g>
<g >
<title>palloc (1,768,400,892 samples, 0.02%)</title><rect x="897.9" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="900.90" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (904,894,254 samples, 0.01%)</title><rect x="1064.1" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1067.14" y="415.5" ></text>
</g>
<g >
<title>flush_ps_display (849,619,800 samples, 0.01%)</title><rect x="1083.7" y="565" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1086.68" y="575.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,147,617,831 samples, 0.01%)</title><rect x="1147.6" y="549" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1150.60" y="559.5" ></text>
</g>
<g >
<title>ShowTransactionState (1,411,235,634 samples, 0.01%)</title><rect x="525.9" y="517" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="528.94" y="527.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (993,035,057 samples, 0.01%)</title><rect x="943.2" y="357" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="946.16" y="367.5" ></text>
</g>
<g >
<title>get_mergejoin_opfamilies (854,829,596 samples, 0.01%)</title><rect x="1139.8" y="757" width="0.1" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1142.82" y="767.5" ></text>
</g>
<g >
<title>security_socket_sendmsg (5,329,158,552 samples, 0.06%)</title><rect x="191.0" y="421" width="0.7" height="15.0" fill="rgb(243,174,41)" rx="2" ry="2" />
<text  x="194.00" y="431.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (5,564,804,788 samples, 0.06%)</title><rect x="810.8" y="453" width="0.7" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="813.85" y="463.5" ></text>
</g>
<g >
<title>BufferGetPage (1,064,825,412 samples, 0.01%)</title><rect x="329.3" y="245" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="332.33" y="255.5" ></text>
</g>
<g >
<title>AtStart_Cache (3,598,246,854 samples, 0.04%)</title><rect x="1074.9" y="533" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1077.93" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,140,821,202 samples, 0.01%)</title><rect x="910.9" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="913.94" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,017,895,535 samples, 0.02%)</title><rect x="232.1" y="485" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="235.14" y="495.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (10,766,951,846 samples, 0.11%)</title><rect x="1067.3" y="453" width="1.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="1070.33" y="463.5" ></text>
</g>
<g >
<title>palloc (1,325,893,978 samples, 0.01%)</title><rect x="914.0" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="917.00" y="447.5" ></text>
</g>
<g >
<title>pq_endmessage (6,147,319,867 samples, 0.07%)</title><rect x="187.1" y="581" width="0.8" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="190.12" y="591.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,945,937,579 samples, 0.02%)</title><rect x="1022.1" y="229" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1025.13" y="239.5" ></text>
</g>
<g >
<title>lappend (2,841,864,162 samples, 0.03%)</title><rect x="873.2" y="565" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="876.19" y="575.5" ></text>
</g>
<g >
<title>tag_hash (2,042,969,333 samples, 0.02%)</title><rect x="924.3" y="389" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="927.35" y="399.5" ></text>
</g>
<g >
<title>tag_hash (2,531,410,707 samples, 0.03%)</title><rect x="367.1" y="213" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="370.07" y="223.5" ></text>
</g>
<g >
<title>AtEOXact_RelationCache (1,726,573,042 samples, 0.02%)</title><rect x="463.5" y="517" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="466.46" y="527.5" ></text>
</g>
<g >
<title>OidFunctionCall4Coll (57,983,539,238 samples, 0.61%)</title><rect x="1030.1" y="325" width="7.3" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="1033.13" y="335.5" ></text>
</g>
<g >
<title>index_getnext_slot (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="373" width="0.5" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="1160.94" y="383.5" ></text>
</g>
<g >
<title>postmaster_child_launch (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="693" width="0.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1160.94" y="703.5" ></text>
</g>
<g >
<title>_bt_getbuf (10,283,440,666 samples, 0.11%)</title><rect x="335.4" y="229" width="1.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="338.38" y="239.5" ></text>
</g>
<g >
<title>switch_fpu_return (955,877,429 samples, 0.01%)</title><rect x="483.5" y="373" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="486.45" y="383.5" ></text>
</g>
<g >
<title>lappend (2,327,362,867 samples, 0.02%)</title><rect x="439.3" y="389" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="442.33" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (4,221,430,584 samples, 0.04%)</title><rect x="382.0" y="277" width="0.5" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="385.00" y="287.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (2,058,654,412 samples, 0.02%)</title><rect x="485.9" y="277" width="0.3" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="488.94" y="287.5" ></text>
</g>
<g >
<title>InjectionPointCacheRefresh (998,805,454 samples, 0.01%)</title><rect x="53.0" y="757" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="55.97" y="767.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (1,734,568,088 samples, 0.02%)</title><rect x="1066.5" y="389" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1069.50" y="399.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (15,994,622,931 samples, 0.17%)</title><rect x="1055.9" y="453" width="2.0" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1058.91" y="463.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (869,731,970 samples, 0.01%)</title><rect x="811.4" y="373" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="814.42" y="383.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (2,712,718,359 samples, 0.03%)</title><rect x="837.9" y="293" width="0.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="840.88" y="303.5" ></text>
</g>
<g >
<title>ReadCommand (298,155,292,459 samples, 3.16%)</title><rect x="149.0" y="597" width="37.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="152.01" y="607.5" >Rea..</text>
</g>
<g >
<title>LWLockAttemptLock (3,240,948,731 samples, 0.03%)</title><rect x="354.0" y="261" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="356.96" y="271.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (1,007,738,949 samples, 0.01%)</title><rect x="124.1" y="181" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="127.11" y="191.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (3,328,780,547 samples, 0.04%)</title><rect x="260.6" y="389" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="263.60" y="399.5" ></text>
</g>
<g >
<title>__x64_sys_futex (2,057,497,817 samples, 0.02%)</title><rect x="363.7" y="229" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="366.67" y="239.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,402,503,013 samples, 0.04%)</title><rect x="1056.7" y="389" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1059.72" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,448,518,772 samples, 0.02%)</title><rect x="186.9" y="517" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="189.88" y="527.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (856,424,802 samples, 0.01%)</title><rect x="1039.1" y="277" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1042.11" y="287.5" ></text>
</g>
<g >
<title>deconstruct_distribute (136,244,414,543 samples, 1.44%)</title><rect x="958.2" y="453" width="17.1" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="961.24" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (2,441,439,382 samples, 0.03%)</title><rect x="381.3" y="261" width="0.3" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="384.27" y="271.5" ></text>
</g>
<g >
<title>grouping_planner (1,355,416,857 samples, 0.01%)</title><rect x="1141.3" y="757" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="1144.31" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,169,167,772 samples, 0.01%)</title><rect x="1134.1" y="757" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1137.13" y="767.5" ></text>
</g>
<g >
<title>get_hash_value (2,742,402,533 samples, 0.03%)</title><rect x="298.1" y="117" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="301.06" y="127.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (979,107,130 samples, 0.01%)</title><rect x="360.5" y="293" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="363.53" y="303.5" ></text>
</g>
<g >
<title>StartReadBuffer (19,822,422,365 samples, 0.21%)</title><rect x="399.3" y="341" width="2.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="402.25" y="351.5" ></text>
</g>
<g >
<title>sentinel_ok (1,234,799,096 samples, 0.01%)</title><rect x="241.7" y="357" width="0.2" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="244.71" y="367.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,799,233,040 samples, 0.05%)</title><rect x="1011.0" y="261" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1014.03" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,423,443,899 samples, 0.02%)</title><rect x="299.6" y="101" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="302.61" y="111.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,691,565,329 samples, 0.03%)</title><rect x="96.4" y="149" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="99.40" y="159.5" ></text>
</g>
<g >
<title>object_aclcheck_ext (1,732,004,186 samples, 0.02%)</title><rect x="420.7" y="309" width="0.3" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="423.75" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (964,051,646 samples, 0.01%)</title><rect x="910.6" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="913.56" y="415.5" ></text>
</g>
<g >
<title>copy_folio_from_iter_atomic (15,223,444,617 samples, 0.16%)</title><rect x="496.6" y="357" width="1.9" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="499.56" y="367.5" ></text>
</g>
<g >
<title>fix_expr_common (1,377,975,702 samples, 0.01%)</title><rect x="1138.2" y="757" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1141.21" y="767.5" ></text>
</g>
<g >
<title>hash_seq_search (13,420,471,149 samples, 0.14%)</title><rect x="523.6" y="453" width="1.7" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="526.63" y="463.5" ></text>
</g>
<g >
<title>Int32GetDatum (817,840,254 samples, 0.01%)</title><rect x="113.3" y="741" width="0.1" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="116.35" y="751.5" ></text>
</g>
<g >
<title>op_mergejoinable (6,818,044,956 samples, 0.07%)</title><rect x="964.5" y="389" width="0.8" height="15.0" fill="rgb(248,197,47)" rx="2" ry="2" />
<text  x="967.49" y="399.5" ></text>
</g>
<g >
<title>hash_bytes (1,548,960,502 samples, 0.02%)</title><rect x="214.9" y="533" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="217.88" y="543.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (17,569,121,246 samples, 0.19%)</title><rect x="366.5" y="293" width="2.2" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="369.52" y="303.5" ></text>
</g>
<g >
<title>ExecInitResultSlot (6,331,168,981 samples, 0.07%)</title><rect x="424.7" y="389" width="0.7" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="427.66" y="399.5" ></text>
</g>
<g >
<title>new_list (1,445,642,892 samples, 0.02%)</title><rect x="972.4" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="975.38" y="383.5" ></text>
</g>
<g >
<title>security_file_permission (3,924,233,997 samples, 0.04%)</title><rect x="494.8" y="373" width="0.5" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="497.80" y="383.5" ></text>
</g>
<g >
<title>hash_search (4,196,822,565 samples, 0.04%)</title><rect x="829.0" y="373" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="832.04" y="383.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,780,876,791 samples, 0.03%)</title><rect x="368.4" y="261" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="371.37" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (970,714,727 samples, 0.01%)</title><rect x="1078.5" y="485" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="1081.50" y="495.5" ></text>
</g>
<g >
<title>__x64_sys_futex (3,395,729,856 samples, 0.04%)</title><rect x="351.9" y="197" width="0.5" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="354.93" y="207.5" ></text>
</g>
<g >
<title>IsCatalogRelationOid (985,473,924 samples, 0.01%)</title><rect x="53.9" y="757" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="56.85" y="767.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (15,331,721,823 samples, 0.16%)</title><rect x="132.0" y="549" width="1.9" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="134.98" y="559.5" ></text>
</g>
<g >
<title>hash_bytes (2,321,759,875 samples, 0.02%)</title><rect x="99.1" y="133" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="102.15" y="143.5" ></text>
</g>
<g >
<title>PreCommit_Notify (1,537,457,936 samples, 0.02%)</title><rect x="85.8" y="757" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="88.77" y="767.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,616,845,989 samples, 0.02%)</title><rect x="871.6" y="517" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="874.61" y="527.5" ></text>
</g>
<g >
<title>palloc (1,116,623,723 samples, 0.01%)</title><rect x="884.9" y="389" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="887.86" y="399.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (954,369,994 samples, 0.01%)</title><rect x="804.0" y="309" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="806.97" y="319.5" ></text>
</g>
<g >
<title>ExecBuildProjectionInfo (1,130,090,161 samples, 0.01%)</title><rect x="41.8" y="757" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="44.77" y="767.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (22,636,958,565 samples, 0.24%)</title><rect x="823.5" y="325" width="2.8" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="826.51" y="335.5" ></text>
</g>
<g >
<title>sock_def_readable (64,886,684,787 samples, 0.69%)</title><rect x="197.7" y="405" width="8.1" height="15.0" fill="rgb(216,54,13)" rx="2" ry="2" />
<text  x="200.67" y="415.5" ></text>
</g>
<g >
<title>pg_class_aclmask (976,567,161 samples, 0.01%)</title><rect x="1168.0" y="757" width="0.1" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="1170.99" y="767.5" ></text>
</g>
<g >
<title>generate_bitmap_or_paths (8,472,270,638 samples, 0.09%)</title><rect x="991.7" y="389" width="1.0" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="994.68" y="399.5" ></text>
</g>
<g >
<title>SearchSysCache1 (7,411,280,652 samples, 0.08%)</title><rect x="423.1" y="341" width="1.0" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="426.15" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,325,292,276 samples, 0.01%)</title><rect x="1021.3" y="277" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1024.26" y="287.5" ></text>
</g>
<g >
<title>eqsel (803,901,079 samples, 0.01%)</title><rect x="126.6" y="309" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="129.56" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,134,267,539 samples, 0.01%)</title><rect x="921.2" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="924.24" y="399.5" ></text>
</g>
<g >
<title>InitResultRelInfo (11,487,035,796 samples, 0.12%)</title><rect x="446.5" y="437" width="1.4" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="449.50" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,162,909,345 samples, 0.01%)</title><rect x="812.1" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="815.09" y="415.5" ></text>
</g>
<g >
<title>secure_raw_read (77,421,488,984 samples, 0.82%)</title><rect x="175.1" y="517" width="9.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="178.14" y="527.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,811,279,423 samples, 0.02%)</title><rect x="359.8" y="325" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="362.85" y="335.5" ></text>
</g>
<g >
<title>ExecUpdateEpilogue (2,949,777,813 samples, 0.03%)</title><rect x="388.4" y="421" width="0.3" height="15.0" fill="rgb(217,58,14)" rx="2" ry="2" />
<text  x="391.36" y="431.5" ></text>
</g>
<g >
<title>check_stack_depth (1,900,378,308 samples, 0.02%)</title><rect x="1136.0" y="741" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="1138.95" y="751.5" ></text>
</g>
<g >
<title>lappend_oid (4,421,698,625 samples, 0.05%)</title><rect x="963.8" y="373" width="0.6" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="966.85" y="383.5" ></text>
</g>
<g >
<title>simplify_function (11,519,963,246 samples, 0.12%)</title><rect x="1056.5" y="437" width="1.4" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1059.47" y="447.5" ></text>
</g>
<g >
<title>bms_add_members (5,398,164,773 samples, 0.06%)</title><rect x="374.7" y="357" width="0.7" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="377.68" y="367.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,273,246,242 samples, 0.01%)</title><rect x="822.9" y="293" width="0.2" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="825.91" y="303.5" ></text>
</g>
<g >
<title>get_attavgwidth (8,943,524,925 samples, 0.09%)</title><rect x="1041.0" y="373" width="1.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="1043.96" y="383.5" ></text>
</g>
<g >
<title>bms_add_member (1,186,205,643 samples, 0.01%)</title><rect x="1121.7" y="757" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1124.66" y="767.5" ></text>
</g>
<g >
<title>LockBuffer (3,125,009,808 samples, 0.03%)</title><rect x="339.8" y="213" width="0.4" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="342.78" y="223.5" ></text>
</g>
<g >
<title>__sigsetjmp (1,375,675,284 samples, 0.01%)</title><rect x="455.7" y="565" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="458.67" y="575.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (2,072,179,170 samples, 0.02%)</title><rect x="750.4" y="453" width="0.3" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="753.43" y="463.5" ></text>
</g>
<g >
<title>CheckVarSlotCompatibility (2,002,627,166 samples, 0.02%)</title><rect x="283.8" y="277" width="0.3" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="286.80" y="287.5" ></text>
</g>
<g >
<title>ExecInitResultTypeTL (36,986,567,270 samples, 0.39%)</title><rect x="433.7" y="421" width="4.6" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="436.69" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (850,106,674 samples, 0.01%)</title><rect x="960.4" y="277" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="963.36" y="287.5" ></text>
</g>
<g >
<title>PortalRunMulti (1,809,544,240,944 samples, 19.17%)</title><rect x="228.3" y="565" width="226.3" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="231.32" y="575.5" >PortalRunMulti</text>
</g>
<g >
<title>uint32_hash (1,278,737,501 samples, 0.01%)</title><rect x="943.9" y="341" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="946.92" y="351.5" ></text>
</g>
<g >
<title>add_path (2,695,413,284 samples, 0.03%)</title><rect x="908.5" y="485" width="0.4" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="911.52" y="495.5" ></text>
</g>
<g >
<title>tts_virtual_clear (896,934,222 samples, 0.01%)</title><rect x="279.1" y="357" width="0.1" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="282.06" y="367.5" ></text>
</g>
<g >
<title>AllocSetFree (1,345,184,668 samples, 0.01%)</title><rect x="227.3" y="549" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="230.28" y="559.5" ></text>
</g>
<g >
<title>newNode (1,833,519,991 samples, 0.02%)</title><rect x="913.7" y="453" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="916.68" y="463.5" ></text>
</g>
<g >
<title>simple_copy_to_iter (5,438,340,316 samples, 0.06%)</title><rect x="183.7" y="325" width="0.6" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="186.66" y="335.5" ></text>
</g>
<g >
<title>enqueue_task (9,464,080,257 samples, 0.10%)</title><rect x="203.7" y="309" width="1.2" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="206.75" y="319.5" ></text>
</g>
<g >
<title>btcostestimate (134,549,017,592 samples, 1.43%)</title><rect x="999.2" y="325" width="16.9" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="1002.24" y="335.5" ></text>
</g>
<g >
<title>palloc0 (1,727,098,358 samples, 0.02%)</title><rect x="412.6" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="415.62" y="447.5" ></text>
</g>
<g >
<title>get_func_retset (5,164,053,148 samples, 0.05%)</title><rect x="834.8" y="373" width="0.6" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="837.78" y="383.5" ></text>
</g>
<g >
<title>bms_add_member (2,646,656,516 samples, 0.03%)</title><rect x="996.3" y="341" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="999.26" y="351.5" ></text>
</g>
<g >
<title>pfree (2,278,179,465 samples, 0.02%)</title><rect x="510.4" y="501" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="513.37" y="511.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,019,356,282 samples, 0.01%)</title><rect x="948.4" y="325" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="951.44" y="335.5" ></text>
</g>
<g >
<title>list_nth (858,517,561 samples, 0.01%)</title><rect x="858.8" y="517" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="861.82" y="527.5" ></text>
</g>
<g >
<title>palloc0 (1,787,073,093 samples, 0.02%)</title><rect x="408.1" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="411.14" y="447.5" ></text>
</g>
<g >
<title>extract_restriction_or_clauses (1,474,552,298 samples, 0.02%)</title><rect x="979.7" y="469" width="0.2" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="982.69" y="479.5" ></text>
</g>
<g >
<title>newNode (2,377,118,512 samples, 0.03%)</title><rect x="815.7" y="437" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="818.72" y="447.5" ></text>
</g>
<g >
<title>distribute_quals_to_rels (134,542,989,127 samples, 1.43%)</title><rect x="958.5" y="437" width="16.8" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="961.45" y="447.5" ></text>
</g>
<g >
<title>AllocSetFree (1,206,733,225 samples, 0.01%)</title><rect x="453.3" y="501" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="456.28" y="511.5" ></text>
</g>
<g >
<title>palloc (879,906,094 samples, 0.01%)</title><rect x="1031.5" y="197" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1034.52" y="207.5" ></text>
</g>
<g >
<title>index_rescan (9,037,716,011 samples, 0.10%)</title><rect x="340.8" y="325" width="1.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="343.78" y="335.5" ></text>
</g>
<g >
<title>MemoryContextCreate (885,634,093 samples, 0.01%)</title><rect x="414.9" y="357" width="0.1" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="417.90" y="367.5" ></text>
</g>
<g >
<title>psi_task_switch (9,609,811,820 samples, 0.10%)</title><rect x="164.3" y="341" width="1.2" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="167.28" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (1,162,126,249 samples, 0.01%)</title><rect x="221.2" y="517" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="224.23" y="527.5" ></text>
</g>
<g >
<title>btgettuple (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="357" width="2.8" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="85.62" y="367.5" ></text>
</g>
<g >
<title>gup_fast_fallback (6,342,070,772 samples, 0.07%)</title><rect x="482.6" y="293" width="0.8" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="485.62" y="303.5" ></text>
</g>
<g >
<title>restriction_selectivity (803,901,079 samples, 0.01%)</title><rect x="126.6" y="357" width="0.1" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="129.56" y="367.5" ></text>
</g>
<g >
<title>ExecAllocTableSlot (9,540,734,165 samples, 0.10%)</title><rect x="438.4" y="405" width="1.2" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="441.44" y="415.5" ></text>
</g>
<g >
<title>TransactionIdSetTreeStatus (27,894,218,216 samples, 0.30%)</title><rect x="469.6" y="485" width="3.5" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="472.60" y="495.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,329,600,988 samples, 0.01%)</title><rect x="1147.6" y="613" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="1150.58" y="623.5" ></text>
</g>
<g >
<title>LockHeldByMe (11,964,267,916 samples, 0.13%)</title><rect x="865.8" y="453" width="1.5" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="868.82" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,062,419,827 samples, 0.02%)</title><rect x="466.4" y="469" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="469.36" y="479.5" ></text>
</g>
<g >
<title>ReadBuffer_common (1,217,297,839 samples, 0.01%)</title><rect x="124.1" y="213" width="0.1" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="127.08" y="223.5" ></text>
</g>
<g >
<title>uint32_hash (856,965,698 samples, 0.01%)</title><rect x="1023.7" y="325" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1026.68" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,054,077,783 samples, 0.01%)</title><rect x="893.2" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="896.18" y="415.5" ></text>
</g>
<g >
<title>new_list (2,336,239,174 samples, 0.02%)</title><rect x="1058.1" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1061.13" y="447.5" ></text>
</g>
<g >
<title>exprLocation (857,172,637 samples, 0.01%)</title><rect x="1134.8" y="757" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1137.81" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,508,173,110 samples, 0.03%)</title><rect x="991.4" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="994.37" y="367.5" ></text>
</g>
<g >
<title>pg_plan_queries (1,592,790,151,937 samples, 16.88%)</title><rect x="872.9" y="581" width="199.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="875.90" y="591.5" >pg_plan_queries</text>
</g>
<g >
<title>MemoryContextResetOnly (120,098,610,922 samples, 1.27%)</title><rect x="133.9" y="581" width="15.0" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="136.90" y="591.5" ></text>
</g>
<g >
<title>TupleDescInitEntry (24,035,314,701 samples, 0.25%)</title><rect x="434.5" y="373" width="3.0" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="437.48" y="383.5" ></text>
</g>
<g >
<title>MemoryContextCreate (1,137,352,553 samples, 0.01%)</title><rect x="1061.3" y="437" width="0.1" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="1064.26" y="447.5" ></text>
</g>
<g >
<title>relation_close (1,031,319,326 samples, 0.01%)</title><rect x="947.3" y="389" width="0.1" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="950.29" y="399.5" ></text>
</g>
<g >
<title>finish_spin_delay (841,824,473 samples, 0.01%)</title><rect x="1137.9" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1140.89" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (947,168,503 samples, 0.01%)</title><rect x="930.7" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="933.74" y="367.5" ></text>
</g>
<g >
<title>CacheInvalidateHeapTuple (901,488,488 samples, 0.01%)</title><rect x="348.0" y="357" width="0.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="350.98" y="367.5" ></text>
</g>
<g >
<title>table_open (20,152,969,643 samples, 0.21%)</title><rect x="444.0" y="421" width="2.5" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="446.98" y="431.5" ></text>
</g>
<g >
<title>StartReadBuffer (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="229" width="0.7" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="87.72" y="239.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,120,549,236 samples, 0.01%)</title><rect x="370.4" y="341" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="373.44" y="351.5" ></text>
</g>
<g >
<title>BufferGetTag (3,613,573,362 samples, 0.04%)</title><rect x="385.5" y="325" width="0.5" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="388.55" y="335.5" ></text>
</g>
<g >
<title>CreateTemplateTupleDesc (1,797,188,146 samples, 0.02%)</title><rect x="448.6" y="405" width="0.3" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="451.64" y="415.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,232,023,931 samples, 0.01%)</title><rect x="398.3" y="373" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="401.31" y="383.5" ></text>
</g>
<g >
<title>AllocSetFree (2,591,435,686 samples, 0.03%)</title><rect x="239.3" y="373" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="242.25" y="383.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (4,194,040,225 samples, 0.04%)</title><rect x="791.1" y="517" width="0.6" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="794.14" y="527.5" ></text>
</g>
<g >
<title>hash_search (6,194,876,050 samples, 0.07%)</title><rect x="861.3" y="437" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="864.32" y="447.5" ></text>
</g>
<g >
<title>ExecEvalSysVar (2,583,218,835 samples, 0.03%)</title><rect x="287.4" y="277" width="0.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="290.44" y="287.5" ></text>
</g>
<g >
<title>futex_wait (998,538,076 samples, 0.01%)</title><rect x="1147.6" y="501" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1150.60" y="511.5" ></text>
</g>
<g >
<title>index_pages_fetched (2,376,375,065 samples, 0.03%)</title><rect x="1017.0" y="325" width="0.3" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="1019.97" y="335.5" ></text>
</g>
<g >
<title>RemoveLocalLock (21,731,815,424 samples, 0.23%)</title><rect x="519.1" y="453" width="2.8" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="522.15" y="463.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (2,871,252,156 samples, 0.03%)</title><rect x="123.2" y="197" width="0.3" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="126.18" y="207.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,570,517,922 samples, 0.02%)</title><rect x="866.3" y="405" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="869.25" y="415.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (2,277,626,347 samples, 0.02%)</title><rect x="260.3" y="389" width="0.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="263.32" y="399.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,512,834,785 samples, 0.03%)</title><rect x="1031.3" y="213" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1034.32" y="223.5" ></text>
</g>
<g >
<title>PostmasterMain (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="741" width="0.6" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="1160.94" y="751.5" ></text>
</g>
<g >
<title>RelationClose (1,945,796,678 samples, 0.02%)</title><rect x="799.2" y="501" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="802.17" y="511.5" ></text>
</g>
<g >
<title>pfree (2,175,455,848 samples, 0.02%)</title><rect x="989.2" y="373" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="992.24" y="383.5" ></text>
</g>
<g >
<title>palloc (2,658,903,298 samples, 0.03%)</title><rect x="396.1" y="389" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="399.12" y="399.5" ></text>
</g>
<g >
<title>lappend (2,638,851,454 samples, 0.03%)</title><rect x="447.9" y="437" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="450.94" y="447.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (7,507,322,641 samples, 0.08%)</title><rect x="317.9" y="213" width="1.0" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="320.95" y="223.5" ></text>
</g>
<g >
<title>tas (1,173,950,265 samples, 0.01%)</title><rect x="381.0" y="293" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="383.98" y="303.5" ></text>
</g>
<g >
<title>bms_add_member (2,432,403,574 samples, 0.03%)</title><rect x="274.2" y="405" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="277.16" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (871,552,014 samples, 0.01%)</title><rect x="991.6" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="994.58" y="351.5" ></text>
</g>
<g >
<title>ReadBufferExtended (21,054,539,429 samples, 0.22%)</title><rect x="399.1" y="373" width="2.7" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="402.13" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,162,738,352 samples, 0.01%)</title><rect x="820.7" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="823.74" y="367.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="293" width="0.1" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="1161.26" y="303.5" ></text>
</g>
<g >
<title>tas (1,439,882,217 samples, 0.02%)</title><rect x="505.1" y="469" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="508.13" y="479.5" ></text>
</g>
<g >
<title>LWLockReleaseClearVar (2,494,259,995 samples, 0.03%)</title><rect x="508.4" y="437" width="0.4" height="15.0" fill="rgb(253,221,52)" rx="2" ry="2" />
<text  x="511.44" y="447.5" ></text>
</g>
<g >
<title>FastPathGrantRelationLock (5,194,552,493 samples, 0.06%)</title><rect x="940.7" y="341" width="0.7" height="15.0" fill="rgb(245,188,44)" rx="2" ry="2" />
<text  x="943.75" y="351.5" ></text>
</g>
<g >
<title>ExecProcNode (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="517" width="6.6" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="97.67" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (3,442,211,971 samples, 0.04%)</title><rect x="1113.8" y="661" width="0.5" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1116.83" y="671.5" ></text>
</g>
<g >
<title>consume_skb (19,769,311,821 samples, 0.21%)</title><rect x="178.9" y="373" width="2.5" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="181.89" y="383.5" ></text>
</g>
<g >
<title>heap_attisnull (896,042,609 samples, 0.01%)</title><rect x="930.9" y="389" width="0.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="933.94" y="399.5" ></text>
</g>
<g >
<title>AtEOXact_SMgr (1,186,657,000 samples, 0.01%)</title><rect x="32.7" y="757" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="35.70" y="767.5" ></text>
</g>
<g >
<title>ExecShutdownNode (7,482,760,707 samples, 0.08%)</title><rect x="402.1" y="485" width="1.0" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="405.13" y="495.5" ></text>
</g>
<g >
<title>palloc (1,314,488,179 samples, 0.01%)</title><rect x="814.0" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="817.02" y="415.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,052,727,188 samples, 0.01%)</title><rect x="949.2" y="325" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="952.17" y="335.5" ></text>
</g>
<g >
<title>LWLockDequeueSelf (1,724,061,747 samples, 0.02%)</title><rect x="350.3" y="293" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="353.29" y="303.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,769,656,770 samples, 0.07%)</title><rect x="444.2" y="357" width="0.9" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="447.24" y="367.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,900,587,970 samples, 0.04%)</title><rect x="1046.5" y="437" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1049.50" y="447.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,645,442,955 samples, 0.02%)</title><rect x="390.3" y="309" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="393.31" y="319.5" ></text>
</g>
<g >
<title>new_list (2,058,610,380 samples, 0.02%)</title><rect x="986.2" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="989.24" y="367.5" ></text>
</g>
<g >
<title>requeue_delayed_entity (1,176,825,282 samples, 0.01%)</title><rect x="199.3" y="309" width="0.1" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="202.30" y="319.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="325" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="127.24" y="335.5" ></text>
</g>
<g >
<title>hash_search (14,741,721,992 samples, 0.16%)</title><rect x="1065.1" y="469" width="1.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1068.11" y="479.5" ></text>
</g>
<g >
<title>assign_collations_walker (1,730,961,408 samples, 0.02%)</title><rect x="1086.1" y="757" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="1089.09" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,068,578,031 samples, 0.01%)</title><rect x="998.2" y="229" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1001.24" y="239.5" ></text>
</g>
<g >
<title>int4eq (1,267,825,877 samples, 0.01%)</title><rect x="1151.0" y="757" width="0.2" height="15.0" fill="rgb(206,9,2)" rx="2" ry="2" />
<text  x="1154.03" y="767.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturnSwitchContext (17,878,920,687 samples, 0.19%)</title><rect x="266.6" y="405" width="2.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="269.58" y="415.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,269,574,136 samples, 0.01%)</title><rect x="1147.6" y="581" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1150.59" y="591.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,279,621,137 samples, 0.01%)</title><rect x="381.8" y="213" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="384.77" y="223.5" ></text>
</g>
<g >
<title>ReservePrivateRefCountEntry (912,643,647 samples, 0.01%)</title><rect x="98.0" y="181" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="101.00" y="191.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (986,436,085 samples, 0.01%)</title><rect x="901.9" y="357" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="904.85" y="367.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,309,653,025 samples, 0.02%)</title><rect x="281.9" y="261" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="284.90" y="271.5" ></text>
</g>
<g >
<title>set_plain_rel_size (122,012,443,606 samples, 1.29%)</title><rect x="1027.7" y="421" width="15.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="1030.68" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,659,349,186 samples, 0.02%)</title><rect x="1167.1" y="757" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="1170.11" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,223,491,178 samples, 0.01%)</title><rect x="214.1" y="533" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="217.13" y="543.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (932,553,470 samples, 0.01%)</title><rect x="374.4" y="325" width="0.1" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="377.36" y="335.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,514,034,625 samples, 0.05%)</title><rect x="902.4" y="357" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="905.40" y="367.5" ></text>
</g>
<g >
<title>CommitTransaction (546,132,443,488 samples, 5.79%)</title><rect x="458.5" y="533" width="68.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="461.48" y="543.5" >CommitT..</text>
</g>
<g >
<title>LWLockReleaseInternal (3,210,172,762 samples, 0.03%)</title><rect x="470.3" y="437" width="0.4" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="473.27" y="447.5" ></text>
</g>
<g >
<title>castNodeImpl (888,537,112 samples, 0.01%)</title><rect x="249.5" y="469" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="252.48" y="479.5" ></text>
</g>
<g >
<title>AllocSetFree (1,374,826,315 samples, 0.01%)</title><rect x="995.8" y="325" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="998.76" y="335.5" ></text>
</g>
<g >
<title>check_list_invariants (1,862,307,263 samples, 0.02%)</title><rect x="1126.1" y="757" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1129.11" y="767.5" ></text>
</g>
<g >
<title>new_list (2,030,594,774 samples, 0.02%)</title><rect x="978.0" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="980.98" y="415.5" ></text>
</g>
<g >
<title>query_planner (1,163,868,766 samples, 0.01%)</title><rect x="126.5" y="501" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="129.52" y="511.5" ></text>
</g>
<g >
<title>contain_volatile_functions_checker (4,858,271,807 samples, 0.05%)</title><rect x="960.3" y="325" width="0.6" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="963.28" y="335.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,743,984,268 samples, 0.02%)</title><rect x="329.1" y="245" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="332.11" y="255.5" ></text>
</g>
<g >
<title>lappend (2,821,354,275 samples, 0.03%)</title><rect x="956.5" y="309" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="959.51" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,123,019,123 samples, 0.01%)</title><rect x="915.0" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="917.98" y="447.5" ></text>
</g>
<g >
<title>pg_plan_queries (1,980,044,175 samples, 0.02%)</title><rect x="85.4" y="645" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="88.40" y="655.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (6,468,839,087 samples, 0.07%)</title><rect x="504.1" y="453" width="0.8" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="507.13" y="463.5" ></text>
</g>
<g >
<title>find_forced_null_var (843,860,143 samples, 0.01%)</title><rect x="1137.7" y="757" width="0.1" height="15.0" fill="rgb(241,167,39)" rx="2" ry="2" />
<text  x="1140.66" y="767.5" ></text>
</g>
<g >
<title>ExecInitQual (33,642,488,605 samples, 0.36%)</title><rect x="429.5" y="421" width="4.2" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="432.48" y="431.5" ></text>
</g>
<g >
<title>_bt_check_natts (1,792,144,631 samples, 0.02%)</title><rect x="337.4" y="213" width="0.2" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="340.39" y="223.5" ></text>
</g>
<g >
<title>namehashfast (5,147,521,155 samples, 0.05%)</title><rect x="825.3" y="293" width="0.7" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="828.33" y="303.5" ></text>
</g>
<g >
<title>TidQualFromRestrictInfoList (9,002,939,102 samples, 0.10%)</title><rect x="1024.7" y="389" width="1.1" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="1027.70" y="399.5" ></text>
</g>
<g >
<title>ExecProject (21,317,769,585 samples, 0.23%)</title><rect x="266.2" y="421" width="2.6" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="269.16" y="431.5" ></text>
</g>
<g >
<title>cost_bitmap_heap_scan (10,255,495,868 samples, 0.11%)</title><rect x="989.9" y="373" width="1.3" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="992.95" y="383.5" ></text>
</g>
<g >
<title>add_eq_member (12,901,464,004 samples, 0.14%)</title><rect x="969.7" y="389" width="1.6" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="972.73" y="399.5" ></text>
</g>
<g >
<title>coerce_to_boolean (11,278,646,338 samples, 0.12%)</title><rect x="842.4" y="469" width="1.4" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="845.41" y="479.5" ></text>
</g>
<g >
<title>preprocess_expression (2,344,763,542 samples, 0.02%)</title><rect x="1185.0" y="741" width="0.3" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1187.98" y="751.5" ></text>
</g>
<g >
<title>newNode (2,873,440,280 samples, 0.03%)</title><rect x="415.3" y="373" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="418.35" y="383.5" ></text>
</g>
<g >
<title>AssignTransactionId (823,317,383 samples, 0.01%)</title><rect x="30.6" y="757" width="0.1" height="15.0" fill="rgb(205,2,0)" rx="2" ry="2" />
<text  x="33.61" y="767.5" ></text>
</g>
<g >
<title>postmaster_child_launch (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="709" width="3.0" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="85.62" y="719.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,265,129,932 samples, 0.02%)</title><rect x="980.6" y="437" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="983.56" y="447.5" ></text>
</g>
<g >
<title>CommitTransactionCommandInternal (551,870,577,369 samples, 5.85%)</title><rect x="457.9" y="549" width="69.0" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="460.90" y="559.5" >CommitT..</text>
</g>
<g >
<title>newNode (4,678,035,860 samples, 0.05%)</title><rect x="885.2" y="405" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="888.21" y="415.5" ></text>
</g>
<g >
<title>MemoryContextDeleteChildren (16,748,185,265 samples, 0.18%)</title><rect x="131.8" y="581" width="2.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="134.81" y="591.5" ></text>
</g>
<g >
<title>ExecScan (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="421" width="1.7" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="125.45" y="431.5" ></text>
</g>
<g >
<title>palloc (1,177,560,783 samples, 0.01%)</title><rect x="448.7" y="389" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="451.72" y="399.5" ></text>
</g>
<g >
<title>charhashfast (917,724,734 samples, 0.01%)</title><rect x="1033.6" y="149" width="0.2" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="1036.65" y="159.5" ></text>
</g>
<g >
<title>palloc (1,073,581,464 samples, 0.01%)</title><rect x="970.4" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="973.42" y="351.5" ></text>
</g>
<g >
<title>ScanKeywords_hash_func (9,764,857,678 samples, 0.10%)</title><rect x="1111.5" y="693" width="1.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1114.47" y="703.5" ></text>
</g>
<g >
<title>assign_expr_collations (29,974,105,509 samples, 0.32%)</title><rect x="804.8" y="421" width="3.7" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="807.79" y="431.5" ></text>
</g>
<g >
<title>IndexNext (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="373" width="1.7" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="125.45" y="383.5" ></text>
</g>
<g >
<title>ReceiveSharedInvalidMessages (2,478,924,841 samples, 0.03%)</title><rect x="1075.1" y="501" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="1078.07" y="511.5" ></text>
</g>
<g >
<title>fix_indexqual_clause (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="405" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="127.24" y="415.5" ></text>
</g>
<g >
<title>XLogRegisterData (876,454,542 samples, 0.01%)</title><rect x="110.0" y="757" width="0.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="112.97" y="767.5" ></text>
</g>
<g >
<title>fmgr_info (1,384,961,187 samples, 0.01%)</title><rect x="426.8" y="389" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="429.84" y="399.5" ></text>
</g>
<g >
<title>set_plan_references (1,050,845,289 samples, 0.01%)</title><rect x="1181.8" y="757" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1184.81" y="767.5" ></text>
</g>
<g >
<title>__x64_sys_futex (875,345,104 samples, 0.01%)</title><rect x="467.7" y="389" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="470.66" y="399.5" ></text>
</g>
<g >
<title>GetSnapshotDataReuse (1,165,508,791 samples, 0.01%)</title><rect x="231.6" y="517" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="234.65" y="527.5" ></text>
</g>
<g >
<title>ExecProcNode (1,366,990,215 samples, 0.01%)</title><rect x="44.9" y="757" width="0.2" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="47.95" y="767.5" ></text>
</g>
<g >
<title>palloc0 (3,286,733,012 samples, 0.03%)</title><rect x="972.9" y="373" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="975.89" y="383.5" ></text>
</g>
<g >
<title>_raw_spin_lock (1,251,815,712 samples, 0.01%)</title><rect x="199.0" y="293" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="202.04" y="303.5" ></text>
</g>
<g >
<title>ExecInitRangeTable (2,606,015,496 samples, 0.03%)</title><rect x="451.5" y="485" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="454.54" y="495.5" ></text>
</g>
<g >
<title>table_openrv_extended (1,207,798,570 samples, 0.01%)</title><rect x="1186.0" y="757" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1189.00" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,212,375,279 samples, 0.01%)</title><rect x="968.5" y="277" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="971.53" y="287.5" ></text>
</g>
<g >
<title>sched_tick (2,649,178,293 samples, 0.03%)</title><rect x="751.3" y="421" width="0.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="754.30" y="431.5" ></text>
</g>
<g >
<title>AllocSetFree (1,919,691,010 samples, 0.02%)</title><rect x="996.6" y="309" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="999.63" y="319.5" ></text>
</g>
<g >
<title>bms_join (809,165,867 samples, 0.01%)</title><rect x="1070.2" y="469" width="0.1" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="1073.20" y="479.5" ></text>
</g>
<g >
<title>ExecProcNode (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="485" width="0.5" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="1160.94" y="495.5" ></text>
</g>
<g >
<title>remove_entity_load_avg (2,199,158,142 samples, 0.02%)</title><rect x="203.5" y="293" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="206.45" y="303.5" ></text>
</g>
<g >
<title>assign_expr_collations (18,306,350,816 samples, 0.19%)</title><rect x="802.3" y="437" width="2.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="805.33" y="447.5" ></text>
</g>
<g >
<title>WALInsertLockRelease (5,128,079,044 samples, 0.05%)</title><rect x="381.9" y="309" width="0.7" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="384.94" y="319.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (70,619,346,809 samples, 0.75%)</title><rect x="475.3" y="437" width="8.9" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="478.34" y="447.5" ></text>
</g>
<g >
<title>RelationClose (881,867,195 samples, 0.01%)</title><rect x="947.3" y="373" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="950.31" y="383.5" ></text>
</g>
<g >
<title>ExecProcNode (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="469" width="1.7" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="125.45" y="479.5" ></text>
</g>
<g >
<title>hash_bytes (928,776,546 samples, 0.01%)</title><rect x="1186.8" y="741" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1189.75" y="751.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,188,010,356 samples, 0.01%)</title><rect x="162.0" y="309" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="165.04" y="319.5" ></text>
</g>
<g >
<title>__send (144,233,095,396 samples, 1.53%)</title><rect x="189.1" y="501" width="18.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="192.12" y="511.5" ></text>
</g>
<g >
<title>tag_hash (2,627,518,791 samples, 0.03%)</title><rect x="811.2" y="405" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="814.20" y="415.5" ></text>
</g>
<g >
<title>SS_identify_outer_params (833,019,497 samples, 0.01%)</title><rect x="92.7" y="757" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="95.75" y="767.5" ></text>
</g>
<g >
<title>expression_returns_set_walker (3,067,200,346 samples, 0.03%)</title><rect x="843.4" y="389" width="0.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="846.40" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberTupleDesc (1,480,415,491 samples, 0.02%)</title><rect x="438.9" y="357" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="441.88" y="367.5" ></text>
</g>
<g >
<title>newNode (4,062,879,036 samples, 0.04%)</title><rect x="275.0" y="405" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="277.95" y="415.5" ></text>
</g>
<g >
<title>ExecScan (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="405" width="1.9" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="127.59" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,699,263,065 samples, 0.03%)</title><rect x="1117.3" y="693" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1120.26" y="703.5" ></text>
</g>
<g >
<title>ExecAllocTableSlot (5,700,218,093 samples, 0.06%)</title><rect x="424.7" y="373" width="0.7" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="427.73" y="383.5" ></text>
</g>
<g >
<title>BufferIsLockedByMe (2,605,202,366 samples, 0.03%)</title><rect x="306.0" y="181" width="0.3" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="308.95" y="191.5" ></text>
</g>
<g >
<title>BufferGetBlock (2,773,242,713 samples, 0.03%)</title><rect x="34.8" y="757" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="37.85" y="767.5" ></text>
</g>
<g >
<title>ExecARUpdateTriggers (1,132,727,843 samples, 0.01%)</title><rect x="388.5" y="405" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="391.53" y="415.5" ></text>
</g>
<g >
<title>makeRangeVar (990,654,009 samples, 0.01%)</title><rect x="1158.7" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1161.68" y="767.5" ></text>
</g>
<g >
<title>LockAcquireExtended (51,931,090,020 samples, 0.55%)</title><rect x="353.0" y="293" width="6.5" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="356.00" y="303.5" ></text>
</g>
<g >
<title>palloc (803,901,079 samples, 0.01%)</title><rect x="126.6" y="213" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="129.56" y="223.5" ></text>
</g>
<g >
<title>sysvec_thermal (1,603,621,551 samples, 0.02%)</title><rect x="751.8" y="517" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="754.82" y="527.5" ></text>
</g>
<g >
<title>PreCommit_on_commit_actions (1,801,731,502 samples, 0.02%)</title><rect x="86.0" y="757" width="0.3" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="89.03" y="767.5" ></text>
</g>
<g >
<title>update_load_avg (12,921,477,856 samples, 0.14%)</title><rect x="169.8" y="277" width="1.6" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="172.79" y="287.5" ></text>
</g>
<g >
<title>folio_unlock (4,152,362,695 samples, 0.04%)</title><rect x="500.5" y="341" width="0.5" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="503.50" y="351.5" ></text>
</g>
<g >
<title>downcase_truncate_identifier (11,746,088,250 samples, 0.12%)</title><rect x="1112.9" y="709" width="1.5" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="1115.95" y="719.5" ></text>
</g>
<g >
<title>AllocSetFree (832,152,457 samples, 0.01%)</title><rect x="396.0" y="341" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="399.01" y="351.5" ></text>
</g>
<g >
<title>newNode (4,192,684,663 samples, 0.04%)</title><rect x="1117.7" y="725" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1120.67" y="735.5" ></text>
</g>
<g >
<title>LWLockRelease (2,987,909,976 samples, 0.03%)</title><rect x="354.5" y="277" width="0.4" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="357.54" y="287.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (895,593,165 samples, 0.01%)</title><rect x="916.5" y="341" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="919.48" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,240,732,324 samples, 0.01%)</title><rect x="980.7" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="983.69" y="415.5" ></text>
</g>
<g >
<title>AssertBufferLocksPermitCatalogRead (3,061,951,675 samples, 0.03%)</title><rect x="29.8" y="757" width="0.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="32.84" y="767.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,411,983,178 samples, 0.01%)</title><rect x="84.7" y="181" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="87.72" y="191.5" ></text>
</g>
<g >
<title>try_to_wake_up (58,468,647,381 samples, 0.62%)</title><rect x="198.4" y="341" width="7.3" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="201.37" y="351.5" ></text>
</g>
<g >
<title>new_list (2,195,988,287 samples, 0.02%)</title><rect x="813.9" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="816.92" y="431.5" ></text>
</g>
<g >
<title>assign_list_collations (31,314,159,188 samples, 0.33%)</title><rect x="804.6" y="437" width="3.9" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="807.62" y="447.5" ></text>
</g>
<g >
<title>fix_indexqual_references (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="421" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="127.24" y="431.5" ></text>
</g>
<g >
<title>ExecModifyTable (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="517" width="2.8" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="85.62" y="527.5" ></text>
</g>
<g >
<title>table_relation_size (21,746,464,267 samples, 0.23%)</title><rect x="935.5" y="325" width="2.7" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="938.51" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,302,601,961 samples, 0.01%)</title><rect x="799.9" y="517" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="802.94" y="527.5" ></text>
</g>
<g >
<title>btint4cmp (2,611,690,680 samples, 0.03%)</title><rect x="125.2" y="213" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="128.20" y="223.5" ></text>
</g>
<g >
<title>calc_bucket (841,043,364 samples, 0.01%)</title><rect x="1124.6" y="757" width="0.1" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1127.60" y="767.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (964,209,146 samples, 0.01%)</title><rect x="57.1" y="757" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="60.13" y="767.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,351,734,174 samples, 0.02%)</title><rect x="954.3" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="957.27" y="463.5" ></text>
</g>
<g >
<title>dequeue_task_fair (46,559,784,827 samples, 0.49%)</title><rect x="165.8" y="325" width="5.9" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="168.85" y="335.5" ></text>
</g>
<g >
<title>__strlen_avx2 (961,239,536 samples, 0.01%)</title><rect x="816.3" y="405" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="819.27" y="415.5" ></text>
</g>
<g >
<title>bms_is_member (1,941,864,028 samples, 0.02%)</title><rect x="274.5" y="405" width="0.2" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="277.46" y="415.5" ></text>
</g>
<g >
<title>try_to_wake_up (1,340,438,214 samples, 0.01%)</title><rect x="382.3" y="101" width="0.1" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="385.25" y="111.5" ></text>
</g>
<g >
<title>HeapTupleHeaderXminCommitted (4,321,397,844 samples, 0.05%)</title><rect x="304.7" y="213" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="307.72" y="223.5" ></text>
</g>
<g >
<title>AllocSetAllocFromNewBlock (15,479,105,145 samples, 0.16%)</title><rect x="1050.0" y="453" width="1.9" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1052.99" y="463.5" ></text>
</g>
<g >
<title>generic_perform_write (40,387,485,949 samples, 0.43%)</title><rect x="496.1" y="373" width="5.0" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="499.07" y="383.5" ></text>
</g>
<g >
<title>futex_hash (1,702,364,238 samples, 0.02%)</title><rect x="482.2" y="309" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="485.21" y="319.5" ></text>
</g>
<g >
<title>compute_bitmap_pages (3,421,295,827 samples, 0.04%)</title><rect x="990.3" y="357" width="0.4" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="993.30" y="367.5" ></text>
</g>
<g >
<title>AllocSetCheck (4,898,370,828 samples, 0.05%)</title><rect x="132.1" y="517" width="0.6" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="135.13" y="527.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,475,135,117 samples, 0.02%)</title><rect x="949.0" y="341" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="951.96" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,256,968,893 samples, 0.01%)</title><rect x="1009.1" y="229" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1012.11" y="239.5" ></text>
</g>
<g >
<title>table_open (15,396,761,244 samples, 0.16%)</title><rect x="947.4" y="405" width="1.9" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="950.42" y="415.5" ></text>
</g>
<g >
<title>newNode (6,957,211,177 samples, 0.07%)</title><rect x="404.6" y="485" width="0.9" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="407.61" y="495.5" ></text>
</g>
<g >
<title>XLogBytePosToRecPtr (827,416,833 samples, 0.01%)</title><rect x="507.6" y="437" width="0.1" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="510.56" y="447.5" ></text>
</g>
<g >
<title>PageAddItemExtended (25,953,764,920 samples, 0.28%)</title><rect x="370.8" y="341" width="3.3" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="373.83" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (844,972,722 samples, 0.01%)</title><rect x="365.5" y="309" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="368.46" y="319.5" ></text>
</g>
<g >
<title>func_parallel (5,443,923,981 samples, 0.06%)</title><rect x="916.4" y="389" width="0.7" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="919.38" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,436,306,255 samples, 0.02%)</title><rect x="518.2" y="421" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="521.15" y="431.5" ></text>
</g>
<g >
<title>sentinel_ok (29,348,997,617 samples, 0.31%)</title><rect x="1177.3" y="757" width="3.7" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="1180.31" y="767.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,165,860,514 samples, 0.01%)</title><rect x="296.8" y="229" width="0.2" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="299.83" y="239.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,344,625,077 samples, 0.07%)</title><rect x="1052.3" y="453" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1055.31" y="463.5" ></text>
</g>
<g >
<title>_int_malloc (1,758,812,344 samples, 0.02%)</title><rect x="277.9" y="309" width="0.2" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="280.92" y="319.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,443,430,509 samples, 0.02%)</title><rect x="942.3" y="341" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="945.34" y="351.5" ></text>
</g>
<g >
<title>lcons (2,535,022,961 samples, 0.03%)</title><rect x="985.5" y="405" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="988.52" y="415.5" ></text>
</g>
<g >
<title>wake_up_q (1,113,480,315 samples, 0.01%)</title><rect x="363.8" y="181" width="0.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="366.79" y="191.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,264,346,157 samples, 0.05%)</title><rect x="107.4" y="757" width="0.6" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="110.42" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,405,905,643 samples, 0.03%)</title><rect x="997.5" y="293" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1000.52" y="303.5" ></text>
</g>
<g >
<title>hash_search (5,926,219,706 samples, 0.06%)</title><rect x="923.4" y="389" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="926.38" y="399.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (132,733,292,669 samples, 1.41%)</title><rect x="695.4" y="533" width="16.6" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="698.45" y="543.5" ></text>
</g>
<g >
<title>__ieee754_log_fma (4,258,866,422 samples, 0.05%)</title><rect x="1000.2" y="309" width="0.6" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="1003.25" y="319.5" ></text>
</g>
<g >
<title>PostmasterMain (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="709" width="2.2" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="127.59" y="719.5" ></text>
</g>
<g >
<title>palloc0 (3,336,249,450 samples, 0.04%)</title><rect x="433.3" y="389" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="436.26" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,751,648,202 samples, 0.03%)</title><rect x="220.7" y="517" width="0.4" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="223.71" y="527.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,068,819,513 samples, 0.06%)</title><rect x="843.0" y="405" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="846.04" y="415.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,707,956,023 samples, 0.02%)</title><rect x="490.5" y="421" width="0.3" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="493.55" y="431.5" ></text>
</g>
<g >
<title>palloc0 (8,093,043,450 samples, 0.09%)</title><rect x="894.3" y="501" width="1.0" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="897.32" y="511.5" ></text>
</g>
<g >
<title>TupleDescAttr (808,950,298 samples, 0.01%)</title><rect x="273.2" y="373" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="276.25" y="383.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (7,138,908,017 samples, 0.08%)</title><rect x="750.9" y="517" width="0.9" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="753.87" y="527.5" ></text>
</g>
<g >
<title>_bt_search (96,418,204,906 samples, 1.02%)</title><rect x="328.4" y="261" width="12.1" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="331.41" y="271.5" ></text>
</g>
<g >
<title>table_tuple_update (350,760,558,599 samples, 3.72%)</title><rect x="344.5" y="405" width="43.8" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="347.49" y="415.5" >tabl..</text>
</g>
<g >
<title>ExecEvalExprNoReturn (47,706,242,851 samples, 0.51%)</title><rect x="283.3" y="325" width="5.9" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="286.25" y="335.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,592,678,236 samples, 0.07%)</title><rect x="923.3" y="405" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="926.31" y="415.5" ></text>
</g>
<g >
<title>add_base_rels_to_query (1,134,314,921 samples, 0.01%)</title><rect x="1084.1" y="757" width="0.2" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="1087.14" y="767.5" ></text>
</g>
<g >
<title>smgrnblocks (20,398,860,181 samples, 0.22%)</title><rect x="935.7" y="293" width="2.5" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="938.68" y="303.5" ></text>
</g>
<g >
<title>rw_verify_area (4,366,212,188 samples, 0.05%)</title><rect x="494.7" y="389" width="0.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="497.75" y="399.5" ></text>
</g>
<g >
<title>TransactionIdIsCurrentTransactionId (1,690,707,375 samples, 0.02%)</title><rect x="307.9" y="213" width="0.2" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="310.91" y="223.5" ></text>
</g>
<g >
<title>hash_seq_term (1,005,306,872 samples, 0.01%)</title><rect x="525.2" y="437" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="528.18" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache4 (7,604,694,999 samples, 0.08%)</title><rect x="1009.7" y="261" width="1.0" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1012.73" y="271.5" ></text>
</g>
<g >
<title>hash_search (2,894,261,413 samples, 0.03%)</title><rect x="1068.8" y="437" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1071.79" y="447.5" ></text>
</g>
<g >
<title>dequeue_task (1,249,574,075 samples, 0.01%)</title><rect x="165.7" y="325" width="0.1" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="168.69" y="335.5" ></text>
</g>
<g >
<title>_mm_set_epi8 (1,074,395,731 samples, 0.01%)</title><rect x="1082.8" y="469" width="0.1" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="1085.78" y="479.5" ></text>
</g>
<g >
<title>hash_search (13,725,016,480 samples, 0.15%)</title><rect x="836.5" y="341" width="1.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="839.50" y="351.5" ></text>
</g>
<g >
<title>relation_open (1,482,340,560 samples, 0.02%)</title><rect x="1175.5" y="757" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1178.46" y="767.5" ></text>
</g>
<g >
<title>message_level_is_interesting (1,260,539,747 samples, 0.01%)</title><rect x="526.0" y="501" width="0.1" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="528.95" y="511.5" ></text>
</g>
<g >
<title>examine_simple_variable (24,393,389,847 samples, 0.26%)</title><rect x="1032.5" y="229" width="3.0" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1035.46" y="239.5" ></text>
</g>
<g >
<title>raw_parser (25,233,527,771 samples, 0.27%)</title><rect x="869.7" y="565" width="3.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="872.72" y="575.5" ></text>
</g>
<g >
<title>pfree (2,387,636,561 samples, 0.03%)</title><rect x="996.6" y="325" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="999.61" y="335.5" ></text>
</g>
<g >
<title>futex_do_wait (45,502,494,796 samples, 0.48%)</title><rect x="476.2" y="325" width="5.6" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="479.16" y="335.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (14,115,185,319 samples, 0.15%)</title><rect x="959.7" y="373" width="1.8" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="962.69" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (859,975,462 samples, 0.01%)</title><rect x="941.6" y="293" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="944.62" y="303.5" ></text>
</g>
<g >
<title>_bt_readpage (4,418,702,744 samples, 0.05%)</title><rect x="122.6" y="277" width="0.6" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="125.63" y="287.5" ></text>
</g>
<g >
<title>lappend (4,359,146,263 samples, 0.05%)</title><rect x="813.7" y="437" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="816.68" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,987,708,070 samples, 0.02%)</title><rect x="474.2" y="437" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="477.22" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,396,679,795 samples, 0.01%)</title><rect x="393.4" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="396.36" y="335.5" ></text>
</g>
<g >
<title>bsearch (1,529,024,200 samples, 0.02%)</title><rect x="1123.5" y="757" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1126.47" y="767.5" ></text>
</g>
<g >
<title>fireBSTriggers (1,229,467,339 samples, 0.01%)</title><rect x="396.8" y="437" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="399.81" y="447.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (4,321,298,885 samples, 0.05%)</title><rect x="104.1" y="453" width="0.5" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="107.07" y="463.5" ></text>
</g>
<g >
<title>BTreeTupleIsPosting (2,140,764,742 samples, 0.02%)</title><rect x="333.7" y="197" width="0.3" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="336.68" y="207.5" ></text>
</g>
<g >
<title>index_getnext_tid (237,218,907,839 samples, 2.51%)</title><rect x="311.1" y="309" width="29.7" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="314.11" y="319.5" >in..</text>
</g>
<g >
<title>expression_tree_walker_impl (841,007,805 samples, 0.01%)</title><rect x="917.6" y="373" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="920.64" y="383.5" ></text>
</g>
<g >
<title>fastgetattr (5,353,035,267 samples, 0.06%)</title><rect x="360.8" y="325" width="0.6" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="363.76" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (901,591,047 samples, 0.01%)</title><rect x="433.1" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="436.05" y="367.5" ></text>
</g>
<g >
<title>__kmalloc_node_track_caller_noprof (10,158,866,217 samples, 0.11%)</title><rect x="194.4" y="341" width="1.3" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="197.43" y="351.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (811,735,690 samples, 0.01%)</title><rect x="354.7" y="69" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="357.71" y="79.5" ></text>
</g>
<g >
<title>QueryRewrite (99,675,392,984 samples, 1.06%)</title><rect x="857.1" y="549" width="12.4" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="860.07" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,797,000,574 samples, 0.02%)</title><rect x="877.1" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="880.08" y="447.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="469" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1161.36" y="479.5" ></text>
</g>
<g >
<title>yy_get_next_buffer (888,763,302 samples, 0.01%)</title><rect x="1114.8" y="709" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="1117.80" y="719.5" ></text>
</g>
<g >
<title>TransactionIdDidCommit (10,496,693,307 samples, 0.11%)</title><rect x="306.6" y="213" width="1.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="309.57" y="223.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,927,547,871 samples, 0.02%)</title><rect x="1143.2" y="741" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1146.16" y="751.5" ></text>
</g>
<g >
<title>new_list (1,909,598,113 samples, 0.02%)</title><rect x="931.4" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="934.36" y="383.5" ></text>
</g>
<g >
<title>pg_detoast_datum_copy (7,273,075,970 samples, 0.08%)</title><rect x="1008.4" y="277" width="0.9" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1011.37" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,537,337,234 samples, 0.02%)</title><rect x="509.8" y="469" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="512.83" y="479.5" ></text>
</g>
<g >
<title>bms_num_members (1,022,321,259 samples, 0.01%)</title><rect x="967.0" y="373" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="969.97" y="383.5" ></text>
</g>
<g >
<title>reweight_entity (1,805,726,795 samples, 0.02%)</title><rect x="204.2" y="277" width="0.2" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="207.22" y="287.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,266,295,508 samples, 0.01%)</title><rect x="422.4" y="357" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="425.43" y="367.5" ></text>
</g>
<g >
<title>ResourceOwnerReleaseInternal (11,986,176,793 samples, 0.13%)</title><rect x="224.8" y="549" width="1.5" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="227.80" y="559.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (1,413,804,480 samples, 0.01%)</title><rect x="331.3" y="213" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="334.26" y="223.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (73,962,204,548 samples, 0.78%)</title><rect x="475.0" y="453" width="9.3" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="478.01" y="463.5" ></text>
</g>
<g >
<title>CreateExprContextInternal (10,164,508,494 samples, 0.11%)</title><rect x="269.1" y="389" width="1.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="272.07" y="399.5" ></text>
</g>
<g >
<title>skb_copy_datagram_iter (9,666,214,220 samples, 0.10%)</title><rect x="183.1" y="357" width="1.2" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="186.14" y="367.5" ></text>
</g>
<g >
<title>initStringInfoInternal (2,763,394,108 samples, 0.03%)</title><rect x="186.8" y="549" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="189.77" y="559.5" ></text>
</g>
<g >
<title>AllocSetFree (3,442,687,947 samples, 0.04%)</title><rect x="978.9" y="405" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="981.85" y="415.5" ></text>
</g>
<g >
<title>CommitTransactionCommand (552,265,060,314 samples, 5.85%)</title><rect x="457.9" y="565" width="69.0" height="15.0" fill="rgb(205,4,1)" rx="2" ry="2" />
<text  x="460.86" y="575.5" >CommitT..</text>
</g>
<g >
<title>palloc0 (7,190,338,074 samples, 0.08%)</title><rect x="914.2" y="453" width="0.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="917.24" y="463.5" ></text>
</g>
<g >
<title>RelationClose (893,152,908 samples, 0.01%)</title><rect x="235.5" y="437" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="238.54" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache (6,365,005,371 samples, 0.07%)</title><rect x="1021.7" y="261" width="0.8" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="1024.71" y="271.5" ></text>
</g>
<g >
<title>palloc0 (14,229,353,896 samples, 0.15%)</title><rect x="276.5" y="373" width="1.7" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="279.46" y="383.5" ></text>
</g>
<g >
<title>SearchSysCache3 (9,950,853,040 samples, 0.11%)</title><rect x="1032.8" y="213" width="1.3" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1035.82" y="223.5" ></text>
</g>
<g >
<title>create_projection_path (6,027,077,110 samples, 0.06%)</title><rect x="909.4" y="469" width="0.7" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="912.37" y="479.5" ></text>
</g>
<g >
<title>tag_hash (2,253,185,102 samples, 0.02%)</title><rect x="1068.4" y="405" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1071.39" y="415.5" ></text>
</g>
<g >
<title>pfree (3,096,556,378 samples, 0.03%)</title><rect x="239.2" y="389" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="242.22" y="399.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (27,770,679,032 samples, 0.29%)</title><rect x="900.3" y="453" width="3.5" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="903.33" y="463.5" ></text>
</g>
<g >
<title>_bt_lockbuf (1,014,336,284 samples, 0.01%)</title><rect x="128.7" y="757" width="0.1" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="131.70" y="767.5" ></text>
</g>
<g >
<title>ExecCloseRangeTableRelations (2,987,973,071 samples, 0.03%)</title><rect x="235.3" y="485" width="0.4" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="238.30" y="495.5" ></text>
</g>
<g >
<title>PageGetItemId (2,795,865,339 samples, 0.03%)</title><rect x="114.5" y="741" width="0.3" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="117.48" y="751.5" ></text>
</g>
<g >
<title>pairingheap_remove_first (1,214,163,430 samples, 0.01%)</title><rect x="453.1" y="453" width="0.1" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="456.08" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,307,608,901 samples, 0.01%)</title><rect x="454.3" y="485" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="457.27" y="495.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,708,225,655 samples, 0.02%)</title><rect x="153.6" y="469" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="156.59" y="479.5" ></text>
</g>
<g >
<title>LockRelationOid (9,032,967,679 samples, 0.10%)</title><rect x="440.2" y="389" width="1.1" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="443.16" y="399.5" ></text>
</g>
<g >
<title>pg_rewrite_query (101,275,917,104 samples, 1.07%)</title><rect x="856.9" y="565" width="12.6" height="15.0" fill="rgb(216,54,13)" rx="2" ry="2" />
<text  x="859.87" y="575.5" ></text>
</g>
<g >
<title>__skb_datagram_iter (9,468,900,748 samples, 0.10%)</title><rect x="183.2" y="341" width="1.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="186.16" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,179,681,233 samples, 0.01%)</title><rect x="381.6" y="245" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="384.59" y="255.5" ></text>
</g>
<g >
<title>AllocSetFree (991,095,160 samples, 0.01%)</title><rect x="966.8" y="341" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="969.82" y="351.5" ></text>
</g>
<g >
<title>RegisterSnapshot (1,967,816,461 samples, 0.02%)</title><rect x="452.2" y="501" width="0.3" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="455.25" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,313,224,788 samples, 0.01%)</title><rect x="977.6" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="980.58" y="415.5" ></text>
</g>
<g >
<title>DataChecksumsEnabled (1,119,487,320 samples, 0.01%)</title><rect x="39.7" y="757" width="0.1" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="42.68" y="767.5" ></text>
</g>
<g >
<title>malloc (8,764,774,558 samples, 0.09%)</title><rect x="291.9" y="229" width="1.1" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="294.88" y="239.5" ></text>
</g>
<g >
<title>psi_group_change (2,778,596,089 samples, 0.03%)</title><rect x="478.7" y="261" width="0.3" height="15.0" fill="rgb(226,101,24)" rx="2" ry="2" />
<text  x="481.70" y="271.5" ></text>
</g>
<g >
<title>LockAcquire (52,297,781,411 samples, 0.55%)</title><rect x="353.0" y="309" width="6.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="355.97" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (2,214,540,114 samples, 0.02%)</title><rect x="381.3" y="245" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="384.30" y="255.5" ></text>
</g>
<g >
<title>_bt_saveitem (818,358,067 samples, 0.01%)</title><rect x="328.1" y="229" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="331.10" y="239.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (826,835,031 samples, 0.01%)</title><rect x="471.1" y="437" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="474.13" y="447.5" ></text>
</g>
<g >
<title>pg_strtoint32_safe (2,371,567,009 samples, 0.03%)</title><rect x="1114.5" y="693" width="0.3" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="1117.51" y="703.5" ></text>
</g>
<g >
<title>__strcmp_avx2 (1,465,582,292 samples, 0.02%)</title><rect x="856.4" y="341" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="859.38" y="351.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,705,728,369 samples, 0.02%)</title><rect x="456.2" y="533" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="459.21" y="543.5" ></text>
</g>
<g >
<title>palloc (1,358,103,921 samples, 0.01%)</title><rect x="448.1" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="451.09" y="415.5" ></text>
</g>
<g >
<title>ExecInterpExpr (8,520,137,263 samples, 0.09%)</title><rect x="267.7" y="357" width="1.1" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="270.73" y="367.5" ></text>
</g>
<g >
<title>ReleaseBuffer (2,521,246,785 samples, 0.03%)</title><rect x="374.2" y="357" width="0.3" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="377.17" y="367.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,145,954,015 samples, 0.02%)</title><rect x="408.1" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="411.09" y="463.5" ></text>
</g>
<g >
<title>futex_wait (1,079,025,424 samples, 0.01%)</title><rect x="354.4" y="149" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="357.39" y="159.5" ></text>
</g>
<g >
<title>ReindexIsProcessingIndex (850,445,588 samples, 0.01%)</title><rect x="939.7" y="389" width="0.2" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="942.74" y="399.5" ></text>
</g>
<g >
<title>new_list (2,035,549,112 samples, 0.02%)</title><rect x="873.3" y="549" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="876.29" y="559.5" ></text>
</g>
<g >
<title>ExecutorFinish (4,338,389,646 samples, 0.05%)</title><rect x="262.7" y="533" width="0.5" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="265.69" y="543.5" ></text>
</g>
<g >
<title>_int_free (828,419,092 samples, 0.01%)</title><rect x="241.1" y="341" width="0.1" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="244.15" y="351.5" ></text>
</g>
<g >
<title>planner (1,980,044,175 samples, 0.02%)</title><rect x="85.4" y="613" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="88.40" y="623.5" ></text>
</g>
<g >
<title>_bt_search (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="341" width="6.6" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="97.67" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,268,213,975 samples, 0.02%)</title><rect x="906.2" y="469" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="909.19" y="479.5" ></text>
</g>
<g >
<title>decimalLength64 (2,190,721,698 samples, 0.02%)</title><rect x="216.1" y="533" width="0.3" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="219.09" y="543.5" ></text>
</g>
<g >
<title>ExecInitExprRec (2,231,249,152 samples, 0.02%)</title><rect x="419.9" y="325" width="0.3" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="422.90" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (950,247,137 samples, 0.01%)</title><rect x="239.7" y="373" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="242.66" y="383.5" ></text>
</g>
<g >
<title>PageIsNew (2,381,822,848 samples, 0.03%)</title><rect x="339.0" y="213" width="0.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="341.98" y="223.5" ></text>
</g>
<g >
<title>new_list (1,847,739,682 samples, 0.02%)</title><rect x="899.5" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="902.47" y="463.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (26,954,828,634 samples, 0.29%)</title><rect x="101.5" y="485" width="3.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="104.45" y="495.5" ></text>
</g>
<g >
<title>TupleDescAttr (894,857,132 samples, 0.01%)</title><rect x="125.7" y="181" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="128.74" y="191.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,646,971,050 samples, 0.05%)</title><rect x="864.3" y="469" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="867.29" y="479.5" ></text>
</g>
<g >
<title>list_make2_impl (1,660,456,441 samples, 0.02%)</title><rect x="835.4" y="373" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="838.42" y="383.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (924,681,049 samples, 0.01%)</title><rect x="940.3" y="357" width="0.1" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="943.31" y="367.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (870,659,640 samples, 0.01%)</title><rect x="368.6" y="245" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="371.61" y="255.5" ></text>
</g>
<g >
<title>get_hash_value (2,716,594,892 samples, 0.03%)</title><rect x="942.0" y="325" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="945.00" y="335.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (2,296,979,005 samples, 0.02%)</title><rect x="750.4" y="485" width="0.3" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="753.42" y="495.5" ></text>
</g>
<g >
<title>palloc (2,058,238,751 samples, 0.02%)</title><rect x="810.1" y="421" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="813.12" y="431.5" ></text>
</g>
<g >
<title>newNode (3,981,846,272 samples, 0.04%)</title><rect x="972.8" y="389" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="975.80" y="399.5" ></text>
</g>
<g >
<title>__task_rq_lock (10,255,302,708 samples, 0.11%)</title><rect x="486.5" y="293" width="1.3" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="489.53" y="303.5" ></text>
</g>
<g >
<title>ReceiveSharedInvalidMessages (1,082,477,386 samples, 0.01%)</title><rect x="817.6" y="405" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="820.63" y="415.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,304,125,293 samples, 0.01%)</title><rect x="381.8" y="245" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="384.76" y="255.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (6,694,101,075 samples, 0.07%)</title><rect x="350.7" y="293" width="0.8" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="353.69" y="303.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,327,474,517 samples, 0.01%)</title><rect x="1078.5" y="501" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1081.45" y="511.5" ></text>
</g>
<g >
<title>_raw_spin_lock_irq (1,112,845,984 samples, 0.01%)</title><rect x="155.9" y="373" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="158.87" y="383.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,093,233,231 samples, 0.01%)</title><rect x="902.7" y="325" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="905.69" y="335.5" ></text>
</g>
<g >
<title>PortalCleanup (989,688,808 samples, 0.01%)</title><rect x="82.1" y="757" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="85.10" y="767.5" ></text>
</g>
<g >
<title>RelationGetIndexPredicate (1,384,412,834 samples, 0.01%)</title><rect x="931.6" y="405" width="0.2" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="934.60" y="415.5" ></text>
</g>
<g >
<title>new_list (1,266,917,604 samples, 0.01%)</title><rect x="897.4" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="900.43" y="463.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,035,384,585 samples, 0.02%)</title><rect x="1059.7" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1062.72" y="463.5" ></text>
</g>
<g >
<title>lappend (1,878,007,627 samples, 0.02%)</title><rect x="1018.6" y="373" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1021.62" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_exchange_u64 (802,816,385 samples, 0.01%)</title><rect x="508.7" y="421" width="0.1" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="511.66" y="431.5" ></text>
</g>
<g >
<title>exprType (981,423,568 samples, 0.01%)</title><rect x="842.2" y="437" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="845.21" y="447.5" ></text>
</g>
<g >
<title>cost_qual_eval_node (1,187,333,350 samples, 0.01%)</title><rect x="1014.1" y="277" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1017.09" y="287.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (3,293,381,650 samples, 0.03%)</title><rect x="468.9" y="485" width="0.4" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="471.89" y="495.5" ></text>
</g>
<g >
<title>afterTriggerMarkEvents (877,934,083 samples, 0.01%)</title><rect x="1084.8" y="757" width="0.1" height="15.0" fill="rgb(247,197,47)" rx="2" ry="2" />
<text  x="1087.83" y="767.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,250,538,008 samples, 0.01%)</title><rect x="845.1" y="405" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="848.12" y="415.5" ></text>
</g>
<g >
<title>get_mergejoin_opfamilies (22,920,093,897 samples, 0.24%)</title><rect x="961.5" y="389" width="2.9" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="964.54" y="399.5" ></text>
</g>
<g >
<title>BufferGetPage (5,333,782,740 samples, 0.06%)</title><rect x="35.4" y="757" width="0.7" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="38.43" y="767.5" ></text>
</g>
<g >
<title>perf_event_context_sched_out (3,430,244,645 samples, 0.04%)</title><rect x="477.9" y="245" width="0.4" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="480.85" y="255.5" ></text>
</g>
<g >
<title>palloc (1,148,150,974 samples, 0.01%)</title><rect x="930.7" y="373" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="933.71" y="383.5" ></text>
</g>
<g >
<title>palloc (1,165,557,821 samples, 0.01%)</title><rect x="869.3" y="501" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="872.30" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (923,558,060 samples, 0.01%)</title><rect x="908.7" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="911.73" y="415.5" ></text>
</g>
<g >
<title>LockReleaseAll (111,666,082,896 samples, 1.18%)</title><rect x="511.4" y="469" width="13.9" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="514.37" y="479.5" ></text>
</g>
<g >
<title>obj_cgroup_charge_account (2,189,928,372 samples, 0.02%)</title><rect x="195.3" y="309" width="0.3" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="198.33" y="319.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (3,988,285,981 samples, 0.04%)</title><rect x="320.8" y="181" width="0.5" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="323.84" y="191.5" ></text>
</g>
<g >
<title>hash_bytes (2,812,418,602 samples, 0.03%)</title><rect x="445.4" y="325" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="448.38" y="335.5" ></text>
</g>
<g >
<title>WALInsertLockRelease (2,560,671,873 samples, 0.03%)</title><rect x="508.4" y="453" width="0.4" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="511.44" y="463.5" ></text>
</g>
<g >
<title>simplify_function (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="645" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1176.04" y="655.5" ></text>
</g>
<g >
<title>asm_sysvec_call_function (1,502,625,927 samples, 0.02%)</title><rect x="1085.7" y="757" width="0.2" height="15.0" fill="rgb(245,186,44)" rx="2" ry="2" />
<text  x="1088.71" y="767.5" ></text>
</g>
<g >
<title>palloc (1,047,860,825 samples, 0.01%)</title><rect x="995.4" y="325" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="998.38" y="335.5" ></text>
</g>
<g >
<title>get_relids_in_jointree (2,175,679,586 samples, 0.02%)</title><rect x="1070.3" y="469" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="1073.31" y="479.5" ></text>
</g>
<g >
<title>GrantLockLocal (1,269,024,101 samples, 0.01%)</title><rect x="819.6" y="373" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="822.59" y="383.5" ></text>
</g>
<g >
<title>x64_sys_call (935,978,999 samples, 0.01%)</title><rect x="501.6" y="421" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="504.58" y="431.5" ></text>
</g>
<g >
<title>ExecEvalStepOp (7,283,519,034 samples, 0.08%)</title><rect x="266.8" y="341" width="0.9" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="269.78" y="351.5" ></text>
</g>
<g >
<title>set_cheapest (3,588,110,733 samples, 0.04%)</title><rect x="1071.5" y="501" width="0.4" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1074.48" y="511.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (7,632,899,953 samples, 0.08%)</title><rect x="974.3" y="357" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="977.29" y="367.5" ></text>
</g>
<g >
<title>ttwu_queue_wakelist (2,565,818,662 samples, 0.03%)</title><rect x="488.3" y="293" width="0.4" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="491.34" y="303.5" ></text>
</g>
<g >
<title>palloc (1,884,702,141 samples, 0.02%)</title><rect x="186.8" y="533" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="189.83" y="543.5" ></text>
</g>
<g >
<title>pull_up_subqueries (6,624,951,097 samples, 0.07%)</title><rect x="1070.6" y="501" width="0.8" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1073.58" y="511.5" ></text>
</g>
<g >
<title>new_list (1,833,516,540 samples, 0.02%)</title><rect x="910.9" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="913.87" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,328,934,805 samples, 0.01%)</title><rect x="829.1" y="357" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="832.08" y="367.5" ></text>
</g>
<g >
<title>parse_analyze_fixedparams (906,372,933 samples, 0.01%)</title><rect x="1165.7" y="757" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1168.66" y="767.5" ></text>
</g>
<g >
<title>PostgresMain (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="645" width="2.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="125.45" y="655.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,895,188,301 samples, 0.06%)</title><rect x="846.6" y="389" width="0.7" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="849.61" y="399.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (2,625,063,016 samples, 0.03%)</title><rect x="49.0" y="757" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="52.01" y="767.5" ></text>
</g>
<g >
<title>create_index_path (155,049,870,900 samples, 1.64%)</title><rect x="998.4" y="357" width="19.4" height="15.0" fill="rgb(245,186,44)" rx="2" ry="2" />
<text  x="1001.39" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="661" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1187.99" y="671.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,495,535,754 samples, 0.06%)</title><rect x="849.2" y="357" width="0.7" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="852.24" y="367.5" ></text>
</g>
<g >
<title>tag_hash (2,248,010,757 samples, 0.02%)</title><rect x="443.6" y="373" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="446.58" y="383.5" ></text>
</g>
<g >
<title>__raw_spin_lock_irqsave (3,270,947,458 samples, 0.03%)</title><rect x="198.6" y="325" width="0.4" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="201.62" y="335.5" ></text>
</g>
<g >
<title>preprocess_targetlist (41,342,837,335 samples, 0.44%)</title><rect x="920.1" y="485" width="5.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="923.08" y="495.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,873,122,891 samples, 0.02%)</title><rect x="382.2" y="197" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="385.18" y="207.5" ></text>
</g>
<g >
<title>set_baserel_size_estimates (118,236,449,128 samples, 1.25%)</title><rect x="1028.2" y="405" width="14.7" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="1031.15" y="415.5" ></text>
</g>
<g >
<title>hash_bytes (2,173,969,297 samples, 0.02%)</title><rect x="1068.4" y="389" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1071.40" y="399.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (12,902,695,064 samples, 0.14%)</title><rect x="1065.2" y="453" width="1.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1068.15" y="463.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (832,734,647 samples, 0.01%)</title><rect x="1148.7" y="677" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="1151.67" y="687.5" ></text>
</g>
<g >
<title>RelationPutHeapTuple (31,612,839,450 samples, 0.33%)</title><rect x="370.2" y="357" width="4.0" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="373.22" y="367.5" ></text>
</g>
<g >
<title>ReadBuffer_common (20,377,094,062 samples, 0.22%)</title><rect x="399.2" y="357" width="2.5" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="402.19" y="367.5" ></text>
</g>
<g >
<title>hash_bytes (3,730,371,931 samples, 0.04%)</title><rect x="1067.8" y="373" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1070.79" y="383.5" ></text>
</g>
<g >
<title>AllocSetFree (1,096,138,663 samples, 0.01%)</title><rect x="860.6" y="485" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="863.57" y="495.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,395,180,249 samples, 0.01%)</title><rect x="463.1" y="485" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="466.11" y="495.5" ></text>
</g>
<g >
<title>IsQueryIdEnabled (1,009,384,549 samples, 0.01%)</title><rect x="798.5" y="549" width="0.2" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="801.53" y="559.5" ></text>
</g>
<g >
<title>palloc (1,353,926,140 samples, 0.01%)</title><rect x="966.6" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="969.55" y="351.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,319,632,706 samples, 0.02%)</title><rect x="1052.8" y="373" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1055.81" y="383.5" ></text>
</g>
<g >
<title>int4hashfast (865,147,581 samples, 0.01%)</title><rect x="436.3" y="293" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="439.31" y="303.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,018,738,034 samples, 0.01%)</title><rect x="232.0" y="469" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="235.01" y="479.5" ></text>
</g>
<g >
<title>palloc (1,241,574,374 samples, 0.01%)</title><rect x="882.1" y="485" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="885.05" y="495.5" ></text>
</g>
<g >
<title>update_curr (6,550,188,136 samples, 0.07%)</title><rect x="479.9" y="213" width="0.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="482.94" y="223.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,182,937,326 samples, 0.07%)</title><rect x="956.1" y="341" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="959.09" y="351.5" ></text>
</g>
<g >
<title>TransactionBlockStatusCode (2,112,935,424 samples, 0.02%)</title><rect x="186.4" y="581" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="189.42" y="591.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,116,701,077 samples, 0.01%)</title><rect x="438.9" y="341" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="441.92" y="351.5" ></text>
</g>
<g >
<title>pfree (1,284,277,633 samples, 0.01%)</title><rect x="860.6" y="501" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="863.56" y="511.5" ></text>
</g>
<g >
<title>bms_del_member (2,799,737,983 samples, 0.03%)</title><rect x="995.6" y="357" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="998.60" y="367.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,090,639,102 samples, 0.01%)</title><rect x="308.3" y="245" width="0.1" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="311.28" y="255.5" ></text>
</g>
<g >
<title>pgstat_assert_is_up (1,503,457,719 samples, 0.02%)</title><rect x="1170.3" y="757" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1173.30" y="767.5" ></text>
</g>
<g >
<title>contain_volatile_functions (14,390,307,810 samples, 0.15%)</title><rect x="959.7" y="389" width="1.8" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="962.65" y="399.5" ></text>
</g>
<g >
<title>hash_seq_init (1,254,122,740 samples, 0.01%)</title><rect x="464.9" y="501" width="0.2" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="467.95" y="511.5" ></text>
</g>
<g >
<title>__alloc_skb (28,043,432,861 samples, 0.30%)</title><rect x="194.0" y="373" width="3.5" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="197.01" y="383.5" ></text>
</g>
<g >
<title>__intel_pmu_enable_all.isra.0 (3,099,312,016 samples, 0.03%)</title><rect x="161.4" y="293" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="164.45" y="303.5" ></text>
</g>
<g >
<title>ReadBufferExtended (28,642,624,945 samples, 0.30%)</title><rect x="297.3" y="229" width="3.5" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="300.26" y="239.5" ></text>
</g>
<g >
<title>scanRTEForColumn (4,510,394,038 samples, 0.05%)</title><rect x="856.1" y="357" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="859.12" y="367.5" ></text>
</g>
<g >
<title>do_syscall_64 (67,910,376,964 samples, 0.72%)</title><rect x="475.6" y="405" width="8.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="478.57" y="415.5" ></text>
</g>
<g >
<title>palloc (1,721,193,023 samples, 0.02%)</title><rect x="449.3" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="452.32" y="431.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,152,010,878 samples, 0.01%)</title><rect x="432.1" y="341" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="435.15" y="351.5" ></text>
</g>
<g >
<title>ReleaseSysCache (925,980,135 samples, 0.01%)</title><rect x="1046.0" y="421" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1048.99" y="431.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (8,185,054,479 samples, 0.09%)</title><rect x="842.8" y="421" width="1.0" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="845.78" y="431.5" ></text>
</g>
<g >
<title>IsSharedRelation (961,748,983 samples, 0.01%)</title><rect x="822.1" y="373" width="0.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="825.13" y="383.5" ></text>
</g>
<g >
<title>update_rq_clock_task (1,820,436,576 samples, 0.02%)</title><rect x="171.8" y="341" width="0.2" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="174.78" y="351.5" ></text>
</g>
<g >
<title>makeVar (4,238,120,949 samples, 0.04%)</title><rect x="839.7" y="325" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="842.69" y="335.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,050,202,787 samples, 0.01%)</title><rect x="296.8" y="181" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="299.84" y="191.5" ></text>
</g>
<g >
<title>exprType (1,306,598,250 samples, 0.01%)</title><rect x="971.8" y="389" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="974.84" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,826,006,787 samples, 0.03%)</title><rect x="804.3" y="389" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="807.26" y="399.5" ></text>
</g>
<g >
<title>palloc0 (1,392,446,115 samples, 0.01%)</title><rect x="976.6" y="405" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="979.63" y="415.5" ></text>
</g>
<g >
<title>exprSetCollation (1,581,019,860 samples, 0.02%)</title><rect x="805.7" y="357" width="0.2" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="808.67" y="367.5" ></text>
</g>
<g >
<title>GrantLockLocal (1,196,112,337 samples, 0.01%)</title><rect x="440.4" y="357" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="443.36" y="367.5" ></text>
</g>
<g >
<title>get_index_paths (209,048,791,762 samples, 2.22%)</title><rect x="992.7" y="389" width="26.2" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="995.74" y="399.5" >g..</text>
</g>
<g >
<title>relation_excluded_by_constraints (2,114,880,286 samples, 0.02%)</title><rect x="1027.4" y="421" width="0.3" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="1030.40" y="431.5" ></text>
</g>
<g >
<title>setNamespaceLateralState (1,148,446,640 samples, 0.01%)</title><rect x="831.2" y="469" width="0.2" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="834.22" y="479.5" ></text>
</g>
<g >
<title>new_list (3,588,930,733 samples, 0.04%)</title><rect x="890.6" y="357" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="893.64" y="367.5" ></text>
</g>
<g >
<title>uint32_hash (1,084,099,088 samples, 0.01%)</title><rect x="863.1" y="453" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="866.09" y="463.5" ></text>
</g>
<g >
<title>palloc (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1161.36" y="431.5" ></text>
</g>
<g >
<title>heap_form_tuple (20,010,634,338 samples, 0.21%)</title><rect x="389.1" y="373" width="2.5" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="392.13" y="383.5" ></text>
</g>
<g >
<title>assign_query_collations_walker (51,851,117,851 samples, 0.55%)</title><rect x="802.1" y="453" width="6.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="805.05" y="463.5" ></text>
</g>
<g >
<title>RewriteQuery (1,177,632,887 samples, 0.01%)</title><rect x="92.3" y="757" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="95.30" y="767.5" ></text>
</g>
<g >
<title>hash_search (5,038,581,927 samples, 0.05%)</title><rect x="445.1" y="357" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="448.10" y="367.5" ></text>
</g>
<g >
<title>_bt_checkkeys (4,711,355,390 samples, 0.05%)</title><rect x="124.6" y="245" width="0.6" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="127.60" y="255.5" ></text>
</g>
<g >
<title>makeVar (4,115,783,126 samples, 0.04%)</title><rect x="854.8" y="357" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="857.79" y="367.5" ></text>
</g>
<g >
<title>new_list (3,191,104,075 samples, 0.03%)</title><rect x="1115.3" y="725" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1118.31" y="735.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,781,918,362 samples, 0.02%)</title><rect x="350.1" y="261" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="353.07" y="271.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,374,137,660 samples, 0.01%)</title><rect x="1072.7" y="565" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1075.68" y="575.5" ></text>
</g>
<g >
<title>hash_bytes (2,265,032,465 samples, 0.02%)</title><rect x="942.1" y="293" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="945.06" y="303.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (2,970,671,409 samples, 0.03%)</title><rect x="791.3" y="469" width="0.4" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="794.28" y="479.5" ></text>
</g>
<g >
<title>MemoryContextReset (8,127,661,512 samples, 0.09%)</title><rect x="459.6" y="501" width="1.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="462.65" y="511.5" ></text>
</g>
<g >
<title>lappend (4,251,657,302 samples, 0.05%)</title><rect x="809.9" y="453" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="812.88" y="463.5" ></text>
</g>
<g >
<title>core_yyalloc (3,304,972,244 samples, 0.04%)</title><rect x="871.8" y="517" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="874.81" y="527.5" ></text>
</g>
<g >
<title>perf_ctx_unlock (1,018,016,787 samples, 0.01%)</title><rect x="161.9" y="309" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="164.90" y="319.5" ></text>
</g>
<g >
<title>_bt_check_compare (4,418,702,744 samples, 0.05%)</title><rect x="122.6" y="245" width="0.6" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="125.63" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (870,022,115 samples, 0.01%)</title><rect x="1048.3" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1051.35" y="463.5" ></text>
</g>
<g >
<title>hash_search (3,340,826,456 samples, 0.04%)</title><rect x="990.8" y="325" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="993.81" y="335.5" ></text>
</g>
<g >
<title>ExecModifyTable (1,098,332,378,060 samples, 11.64%)</title><rect x="264.4" y="453" width="137.4" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="267.45" y="463.5" >ExecModifyTable</text>
</g>
<g >
<title>__pick_next_task (1,896,845,424 samples, 0.02%)</title><rect x="206.5" y="405" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="209.48" y="415.5" ></text>
</g>
<g >
<title>SimpleLruReadPage (6,665,436,735 samples, 0.07%)</title><rect x="471.6" y="437" width="0.9" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="474.63" y="447.5" ></text>
</g>
<g >
<title>expand_function_arguments (836,763,207 samples, 0.01%)</title><rect x="1057.2" y="421" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1060.21" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,362,804,834 samples, 0.01%)</title><rect x="407.3" y="405" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="410.26" y="415.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,768,842,470 samples, 0.02%)</title><rect x="1143.4" y="757" width="0.3" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1146.43" y="767.5" ></text>
</g>
<g >
<title>list_nth (3,637,585,295 samples, 0.04%)</title><rect x="1156.5" y="757" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1159.52" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,263,599,923 samples, 0.05%)</title><rect x="1047.7" y="405" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1050.65" y="415.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="533" width="2.8" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="85.62" y="543.5" ></text>
</g>
<g >
<title>pull_varnos_walker (4,444,386,999 samples, 0.05%)</title><rect x="968.3" y="341" width="0.5" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="971.29" y="351.5" ></text>
</g>
<g >
<title>pick_task_fair (2,597,183,538 samples, 0.03%)</title><rect x="159.5" y="309" width="0.3" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="162.51" y="319.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (16,078,703,874 samples, 0.17%)</title><rect x="901.1" y="405" width="2.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="904.14" y="415.5" ></text>
</g>
<g >
<title>get_opfamily_member (11,215,134,483 samples, 0.12%)</title><rect x="1009.3" y="293" width="1.4" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1012.28" y="303.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,472,568,058 samples, 0.02%)</title><rect x="395.0" y="341" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="398.02" y="351.5" ></text>
</g>
<g >
<title>wipe_mem (4,005,677,561 samples, 0.04%)</title><rect x="261.6" y="405" width="0.5" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="264.57" y="415.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,136,310,343 samples, 0.03%)</title><rect x="973.6" y="341" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="976.58" y="351.5" ></text>
</g>
<g >
<title>tag_hash (2,675,415,602 samples, 0.03%)</title><rect x="237.4" y="389" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="240.41" y="399.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (3,076,016,441 samples, 0.03%)</title><rect x="1031.2" y="229" width="0.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1034.24" y="239.5" ></text>
</g>
<g >
<title>preprocess_expression (25,847,967,100 samples, 0.27%)</title><rect x="1055.2" y="485" width="3.2" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1058.19" y="495.5" ></text>
</g>
<g >
<title>ExecScan (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="453" width="2.8" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="85.62" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,268,577,577 samples, 0.02%)</title><rect x="111.0" y="741" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="114.02" y="751.5" ></text>
</g>
<g >
<title>current_time (2,348,237,672 samples, 0.02%)</title><rect x="495.8" y="341" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="498.76" y="351.5" ></text>
</g>
<g >
<title>palloc (1,358,159,681 samples, 0.01%)</title><rect x="415.2" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="418.16" y="351.5" ></text>
</g>
<g >
<title>bms_union (2,730,084,248 samples, 0.03%)</title><rect x="957.9" y="453" width="0.3" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="960.86" y="463.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (883,244,281 samples, 0.01%)</title><rect x="860.9" y="453" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="863.89" y="463.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,418,667,084 samples, 0.02%)</title><rect x="354.7" y="181" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="357.65" y="191.5" ></text>
</g>
<g >
<title>heap_update (342,205,564,461 samples, 3.63%)</title><rect x="345.5" y="373" width="42.8" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="348.55" y="383.5" >heap..</text>
</g>
<g >
<title>bms_get_singleton_member (1,592,137,533 samples, 0.02%)</title><rect x="982.1" y="421" width="0.2" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="985.09" y="431.5" ></text>
</g>
<g >
<title>add_base_clause_to_rel (6,101,691,128 samples, 0.06%)</title><rect x="981.3" y="421" width="0.8" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="984.33" y="431.5" ></text>
</g>
<g >
<title>transformExpr (104,198,851,795 samples, 1.10%)</title><rect x="843.8" y="469" width="13.1" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="846.82" y="479.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (7,218,038,079 samples, 0.08%)</title><rect x="956.0" y="357" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="958.97" y="367.5" ></text>
</g>
<g >
<title>list_insert_nth (2,878,661,285 samples, 0.03%)</title><rect x="986.1" y="389" width="0.4" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="989.14" y="399.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="501" width="1.7" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="125.45" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (17,280,117,528 samples, 0.18%)</title><rect x="1049.8" y="469" width="2.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1052.83" y="479.5" ></text>
</g>
<g >
<title>AllocSetFreeIndex (4,652,428,536 samples, 0.05%)</title><rect x="29.2" y="757" width="0.6" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="32.21" y="767.5" ></text>
</g>
<g >
<title>FreeQueryDesc (6,182,061,015 samples, 0.07%)</title><rect x="452.7" y="533" width="0.8" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="455.70" y="543.5" ></text>
</g>
<g >
<title>_bt_freestack (1,932,420,812 samples, 0.02%)</title><rect x="321.5" y="261" width="0.3" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="324.54" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,958,358,170 samples, 0.02%)</title><rect x="396.2" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="399.20" y="383.5" ></text>
</g>
<g >
<title>newNode (1,942,518,643 samples, 0.02%)</title><rect x="1118.9" y="725" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1121.87" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,733,357,629 samples, 0.02%)</title><rect x="433.5" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="436.46" y="383.5" ></text>
</g>
<g >
<title>PinBufferForBlock (34,075,111,302 samples, 0.36%)</title><rect x="94.8" y="213" width="4.2" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="97.78" y="223.5" ></text>
</g>
<g >
<title>mod_memcg_state (877,359,169 samples, 0.01%)</title><rect x="195.4" y="277" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="198.43" y="287.5" ></text>
</g>
<g >
<title>sched_tick (1,130,685,344 samples, 0.01%)</title><rect x="750.5" y="405" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="753.53" y="415.5" ></text>
</g>
<g >
<title>detoast_attr (6,754,496,291 samples, 0.07%)</title><rect x="1008.4" y="261" width="0.9" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="1011.43" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (6,482,171,240 samples, 0.07%)</title><rect x="912.1" y="437" width="0.8" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="915.08" y="447.5" ></text>
</g>
<g >
<title>GetSnapshotData (2,357,980,657 samples, 0.02%)</title><rect x="49.5" y="757" width="0.3" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="52.54" y="767.5" ></text>
</g>
<g >
<title>hash_search (3,254,680,051 samples, 0.03%)</title><rect x="948.4" y="341" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="951.41" y="351.5" ></text>
</g>
<g >
<title>ReserveXLogInsertLocation (5,707,685,725 samples, 0.06%)</title><rect x="380.4" y="309" width="0.7" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="383.41" y="319.5" ></text>
</g>
<g >
<title>try_to_block_task.constprop.0.isra.0 (48,404,692,536 samples, 0.51%)</title><rect x="165.6" y="341" width="6.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="168.61" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,049,368,021 samples, 0.01%)</title><rect x="919.3" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="922.35" y="431.5" ></text>
</g>
<g >
<title>ShowTransactionState (1,202,660,821 samples, 0.01%)</title><rect x="1077.7" y="533" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1080.66" y="543.5" ></text>
</g>
<g >
<title>BufferAlloc (20,131,167,521 samples, 0.21%)</title><rect x="298.0" y="149" width="2.5" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="300.97" y="159.5" ></text>
</g>
<g >
<title>pfree (3,123,720,234 samples, 0.03%)</title><rect x="429.0" y="405" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="432.05" y="415.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (8,351,697,495 samples, 0.09%)</title><rect x="936.9" y="213" width="1.0" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="939.89" y="223.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,105,949,671 samples, 0.01%)</title><rect x="1166.5" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1169.48" y="767.5" ></text>
</g>
<g >
<title>palloc0 (4,315,602,119 samples, 0.05%)</title><rect x="893.4" y="437" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="896.41" y="447.5" ></text>
</g>
<g >
<title>has_unique_index (1,735,540,982 samples, 0.02%)</title><rect x="1036.0" y="229" width="0.2" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="1038.97" y="239.5" ></text>
</g>
<g >
<title>create_modifytable_path (12,970,281,015 samples, 0.14%)</title><rect x="911.3" y="485" width="1.6" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="914.28" y="495.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,128,758,488 samples, 0.05%)</title><rect x="1014.7" y="261" width="0.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1017.71" y="271.5" ></text>
</g>
<g >
<title>MemoryContextTraverseNext (8,813,221,673 samples, 0.09%)</title><rect x="796.6" y="549" width="1.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="799.58" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,281,108,790 samples, 0.01%)</title><rect x="412.7" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="415.67" y="431.5" ></text>
</g>
<g >
<title>palloc (1,217,575,699 samples, 0.01%)</title><rect x="1163.2" y="741" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1166.15" y="751.5" ></text>
</g>
<g >
<title>CheckForSerializableConflictIn (1,374,138,209 samples, 0.01%)</title><rect x="348.1" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="351.11" y="367.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,054,776,683 samples, 0.01%)</title><rect x="428.9" y="373" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="431.86" y="383.5" ></text>
</g>
<g >
<title>TransactionIdSetPageStatusInternal (12,561,757,201 samples, 0.13%)</title><rect x="471.5" y="453" width="1.6" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="474.49" y="463.5" ></text>
</g>
<g >
<title>ReleaseCatCache (2,095,513,726 samples, 0.02%)</title><rect x="822.8" y="341" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="825.81" y="351.5" ></text>
</g>
<g >
<title>expr_setup_walker (2,835,113,011 samples, 0.03%)</title><rect x="430.7" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="433.65" y="367.5" ></text>
</g>
<g >
<title>LWLockRelease (1,453,997,992 samples, 0.02%)</title><rect x="508.5" y="421" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="511.47" y="431.5" ></text>
</g>
<g >
<title>transformWhereClause (116,152,316,010 samples, 1.23%)</title><rect x="842.3" y="485" width="14.6" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="845.33" y="495.5" ></text>
</g>
<g >
<title>UnregisterSnapshotNoOwner (924,273,424 samples, 0.01%)</title><rect x="262.6" y="469" width="0.1" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="265.55" y="479.5" ></text>
</g>
<g >
<title>BufferGetBlock (971,778,546 samples, 0.01%)</title><rect x="378.2" y="309" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="381.19" y="319.5" ></text>
</g>
<g >
<title>pgstat_report_xact_timestamp (972,276,427 samples, 0.01%)</title><rect x="1078.6" y="533" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="1081.62" y="543.5" ></text>
</g>
<g >
<title>addNSItemToQuery (6,614,847,161 samples, 0.07%)</title><rect x="809.8" y="469" width="0.8" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="812.80" y="479.5" ></text>
</g>
<g >
<title>__wake_up_sync_key (62,662,978,506 samples, 0.66%)</title><rect x="197.9" y="389" width="7.8" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="200.85" y="399.5" ></text>
</g>
<g >
<title>relation_open (14,823,230,610 samples, 0.16%)</title><rect x="394.0" y="373" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="397.05" y="383.5" ></text>
</g>
<g >
<title>addRTEPermissionInfo (5,671,514,976 samples, 0.06%)</title><rect x="811.5" y="453" width="0.8" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="814.54" y="463.5" ></text>
</g>
<g >
<title>assign_collations_walker (7,685,729,243 samples, 0.08%)</title><rect x="806.3" y="325" width="0.9" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="809.27" y="335.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,104,882,793 samples, 0.01%)</title><rect x="890.9" y="309" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="893.87" y="319.5" ></text>
</g>
<g >
<title>hash_search (4,323,209,690 samples, 0.05%)</title><rect x="811.0" y="421" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="813.99" y="431.5" ></text>
</g>
<g >
<title>get_row_security_policies (9,907,212,856 samples, 0.10%)</title><rect x="863.8" y="517" width="1.3" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="866.83" y="527.5" ></text>
</g>
<g >
<title>palloc0 (1,980,840,689 samples, 0.02%)</title><rect x="439.1" y="373" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="442.08" y="383.5" ></text>
</g>
<g >
<title>PageIsNew (2,623,661,081 samples, 0.03%)</title><rect x="335.9" y="197" width="0.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="338.89" y="207.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,263,413,588 samples, 0.01%)</title><rect x="1120.8" y="677" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1123.84" y="687.5" ></text>
</g>
<g >
<title>palloc (1,303,654,371 samples, 0.01%)</title><rect x="1071.7" y="453" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1074.74" y="463.5" ></text>
</g>
<g >
<title>_bt_search (8,437,718,514 samples, 0.09%)</title><rect x="123.2" y="293" width="1.0" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="126.18" y="303.5" ></text>
</g>
<g >
<title>reduce_unique_semijoins (993,350,630 samples, 0.01%)</title><rect x="1043.6" y="469" width="0.1" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1046.55" y="479.5" ></text>
</g>
<g >
<title>ProcArrayEndTransactionInternal (5,892,016,812 samples, 0.06%)</title><rect x="466.8" y="501" width="0.7" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="469.80" y="511.5" ></text>
</g>
<g >
<title>planner (2,826,087,191 samples, 0.03%)</title><rect x="124.2" y="581" width="0.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="127.24" y="591.5" ></text>
</g>
<g >
<title>palloc (1,287,818,056 samples, 0.01%)</title><rect x="871.4" y="501" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="874.35" y="511.5" ></text>
</g>
<g >
<title>VirtualXactLockTableCleanup (4,092,047,432 samples, 0.04%)</title><rect x="522.4" y="453" width="0.5" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="525.40" y="463.5" ></text>
</g>
<g >
<title>hash_search (11,104,747,293 samples, 0.12%)</title><rect x="865.9" y="437" width="1.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="868.92" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,051,064,298 samples, 0.01%)</title><rect x="813.0" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="816.01" y="431.5" ></text>
</g>
<g >
<title>palloc (2,892,858,887 samples, 0.03%)</title><rect x="992.3" y="325" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="995.30" y="335.5" ></text>
</g>
<g >
<title>ExecInterpExpr (25,246,091,352 samples, 0.27%)</title><rect x="286.0" y="293" width="3.2" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="289.03" y="303.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,239,914,350 samples, 0.02%)</title><rect x="897.0" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="899.97" y="463.5" ></text>
</g>
<g >
<title>pq_beginmessage (3,088,931,494 samples, 0.03%)</title><rect x="186.7" y="581" width="0.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="189.74" y="591.5" ></text>
</g>
<g >
<title>ExecReadyExpr (5,990,320,247 samples, 0.06%)</title><rect x="421.7" y="357" width="0.7" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="424.68" y="367.5" ></text>
</g>
<g >
<title>StartTransactionCommand (36,991,165,912 samples, 0.39%)</title><rect x="1074.1" y="565" width="4.7" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1077.13" y="575.5" ></text>
</g>
<g >
<title>AllocSetCheck (1,654,521,489 samples, 0.02%)</title><rect x="223.2" y="517" width="0.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="226.20" y="527.5" ></text>
</g>
<g >
<title>BufferAlloc (10,962,807,051 samples, 0.12%)</title><rect x="367.0" y="261" width="1.3" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="369.96" y="271.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (9,838,958,621 samples, 0.10%)</title><rect x="806.0" y="357" width="1.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="809.02" y="367.5" ></text>
</g>
<g >
<title>tag_hash (3,435,424,339 samples, 0.04%)</title><rect x="444.7" y="325" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="447.66" y="335.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,920,092,365 samples, 0.02%)</title><rect x="843.5" y="373" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="846.54" y="383.5" ></text>
</g>
<g >
<title>tag_hash (2,564,224,179 samples, 0.03%)</title><rect x="862.3" y="437" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="865.31" y="447.5" ></text>
</g>
<g >
<title>smgrnblocks (1,236,053,504 samples, 0.01%)</title><rect x="1183.4" y="757" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1186.43" y="767.5" ></text>
</g>
<g >
<title>futex_wake_mark (1,320,591,461 samples, 0.01%)</title><rect x="485.6" y="325" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="488.58" y="335.5" ></text>
</g>
<g >
<title>ExtractReplicaIdentity (2,802,632,669 samples, 0.03%)</title><rect x="46.8" y="757" width="0.3" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="49.78" y="767.5" ></text>
</g>
<g >
<title>futex_wait_setup (2,794,594,531 samples, 0.03%)</title><rect x="351.0" y="149" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="354.02" y="159.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (858,965,146 samples, 0.01%)</title><rect x="916.8" y="325" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="919.83" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="613" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1136.99" y="623.5" ></text>
</g>
<g >
<title>InitBufferTag (804,105,453 samples, 0.01%)</title><rect x="299.3" y="133" width="0.1" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="302.32" y="143.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,293,287,512 samples, 0.01%)</title><rect x="398.5" y="357" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="401.49" y="367.5" ></text>
</g>
<g >
<title>IndexNext (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="357" width="1.9" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="127.59" y="367.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (1,663,654,592 samples, 0.02%)</title><rect x="198.8" y="309" width="0.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="201.82" y="319.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (3,377,348,041 samples, 0.04%)</title><rect x="503.3" y="437" width="0.4" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="506.30" y="447.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,175,631,341 samples, 0.03%)</title><rect x="296.6" y="245" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="299.59" y="255.5" ></text>
</g>
<g >
<title>is_andclause (2,023,658,457 samples, 0.02%)</title><rect x="1151.8" y="757" width="0.3" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="1154.83" y="767.5" ></text>
</g>
<g >
<title>XLogInsert (22,623,180,134 samples, 0.24%)</title><rect x="506.5" y="485" width="2.8" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="509.48" y="495.5" ></text>
</g>
<g >
<title>RelationGetIndexAttOptions (2,539,776,563 samples, 0.03%)</title><rect x="930.5" y="405" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="933.55" y="415.5" ></text>
</g>
<g >
<title>BuildIndexInfo (12,348,862,502 samples, 0.13%)</title><rect x="392.0" y="389" width="1.5" height="15.0" fill="rgb(245,186,44)" rx="2" ry="2" />
<text  x="395.00" y="399.5" ></text>
</g>
<g >
<title>table_close (1,510,599,442 samples, 0.02%)</title><rect x="1067.1" y="485" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="1070.07" y="495.5" ></text>
</g>
<g >
<title>preprocess_rowmarks (949,485,864 samples, 0.01%)</title><rect x="1173.4" y="757" width="0.1" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1176.41" y="767.5" ></text>
</g>
<g >
<title>_copyVar (8,237,428,108 samples, 0.09%)</title><rect x="952.6" y="421" width="1.0" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="955.60" y="431.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,433,553,415 samples, 0.02%)</title><rect x="864.9" y="501" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="867.88" y="511.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (4,741,822,835 samples, 0.05%)</title><rect x="751.1" y="469" width="0.6" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="754.11" y="479.5" ></text>
</g>
<g >
<title>FreeExecutorState (101,518,613,164 samples, 1.08%)</title><rect x="249.7" y="501" width="12.7" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="252.66" y="511.5" ></text>
</g>
<g >
<title>perf_ctx_enable (3,867,902,914 samples, 0.04%)</title><rect x="161.4" y="309" width="0.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="164.42" y="319.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (985,087,900 samples, 0.01%)</title><rect x="217.0" y="533" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="219.99" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,369,923,072 samples, 0.01%)</title><rect x="855.1" y="309" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="858.13" y="319.5" ></text>
</g>
<g >
<title>tag_hash (10,950,380,825 samples, 0.12%)</title><rect x="836.8" y="325" width="1.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="839.85" y="335.5" ></text>
</g>
<g >
<title>standard_qp_callback (5,521,844,196 samples, 0.06%)</title><rect x="1044.8" y="469" width="0.7" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="1047.83" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (964,630,174 samples, 0.01%)</title><rect x="467.7" y="421" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="470.66" y="431.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (870,264,916 samples, 0.01%)</title><rect x="83.9" y="165" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="86.93" y="175.5" ></text>
</g>
<g >
<title>ExecEndNode (71,359,982,665 samples, 0.76%)</title><rect x="238.0" y="485" width="8.9" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="240.97" y="495.5" ></text>
</g>
<g >
<title>hash_bytes (2,646,927,184 samples, 0.03%)</title><rect x="399.8" y="229" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="402.80" y="239.5" ></text>
</g>
<g >
<title>PinBuffer (2,768,815,731 samples, 0.03%)</title><rect x="400.8" y="277" width="0.4" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="403.84" y="287.5" ></text>
</g>
<g >
<title>ReleaseCatCache (933,231,064 samples, 0.01%)</title><rect x="834.5" y="357" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="837.48" y="367.5" ></text>
</g>
<g >
<title>ReadBufferExtended (2,318,356,473 samples, 0.02%)</title><rect x="123.5" y="229" width="0.3" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="126.54" y="239.5" ></text>
</g>
<g >
<title>func_volatile (4,701,075,211 samples, 0.05%)</title><rect x="960.3" y="309" width="0.6" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="963.29" y="319.5" ></text>
</g>
<g >
<title>LWLockAcquire (15,399,460,476 samples, 0.16%)</title><rect x="349.6" y="309" width="2.0" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="352.63" y="319.5" ></text>
</g>
<g >
<title>new_list (2,313,054,196 samples, 0.02%)</title><rect x="944.2" y="389" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="947.19" y="399.5" ></text>
</g>
<g >
<title>_bt_check_compare (2,079,231,909 samples, 0.02%)</title><rect x="83.0" y="277" width="0.2" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="85.96" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,237,184,783 samples, 0.01%)</title><rect x="448.4" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="451.36" y="431.5" ></text>
</g>
<g >
<title>MemoryContextDelete (77,529,796,318 samples, 0.82%)</title><rect x="252.5" y="485" width="9.7" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="255.54" y="495.5" ></text>
</g>
<g >
<title>AllocSetCheck (1,521,920,550 samples, 0.02%)</title><rect x="223.7" y="485" width="0.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="226.70" y="495.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetTupleDesc (1,846,908,674 samples, 0.02%)</title><rect x="247.7" y="453" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="250.70" y="463.5" ></text>
</g>
<g >
<title>get_futex_key (1,856,960,696 samples, 0.02%)</title><rect x="351.1" y="133" width="0.3" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="354.14" y="143.5" ></text>
</g>
<g >
<title>btcanreturn (1,190,207,448 samples, 0.01%)</title><rect x="1123.7" y="757" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1126.71" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,225,420,212 samples, 0.01%)</title><rect x="272.1" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="275.13" y="367.5" ></text>
</g>
<g >
<title>get_tablespace (2,896,459,360 samples, 0.03%)</title><rect x="1016.6" y="309" width="0.4" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1019.61" y="319.5" ></text>
</g>
<g >
<title>unix_write_space (5,159,934,901 samples, 0.05%)</title><rect x="180.7" y="309" width="0.7" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="183.72" y="319.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,133,579,443 samples, 0.03%)</title><rect x="125.6" y="197" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="128.60" y="207.5" ></text>
</g>
<g >
<title>ExecInitNode (341,754,031,610 samples, 3.62%)</title><rect x="408.8" y="485" width="42.7" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="411.81" y="495.5" >Exec..</text>
</g>
<g >
<title>BufferGetBlockNumber (1,064,161,565 samples, 0.01%)</title><rect x="297.7" y="149" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="300.67" y="159.5" ></text>
</g>
<g >
<title>AfterTriggerBeginXact (1,849,911,752 samples, 0.02%)</title><rect x="1074.7" y="533" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1077.70" y="543.5" ></text>
</g>
<g >
<title>find_relation_notnullatts (4,029,086,504 samples, 0.04%)</title><rect x="938.6" y="405" width="0.5" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="941.62" y="415.5" ></text>
</g>
<g >
<title>hash_bytes (1,981,258,632 samples, 0.02%)</title><rect x="924.4" y="373" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="927.36" y="383.5" ></text>
</g>
<g >
<title>ktime_get_coarse_real_ts64_mg (1,570,050,883 samples, 0.02%)</title><rect x="495.9" y="325" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="498.85" y="335.5" ></text>
</g>
<g >
<title>list_free_private (1,934,373,407 samples, 0.02%)</title><rect x="993.9" y="325" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="996.87" y="335.5" ></text>
</g>
<g >
<title>LWLockAcquire (6,215,478,308 samples, 0.07%)</title><rect x="517.6" y="453" width="0.8" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="520.65" y="463.5" ></text>
</g>
<g >
<title>lappend (2,338,961,378 samples, 0.02%)</title><rect x="933.7" y="389" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="936.72" y="399.5" ></text>
</g>
<g >
<title>TransactionIdGetStatus (9,470,816,659 samples, 0.10%)</title><rect x="306.7" y="181" width="1.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="309.70" y="191.5" ></text>
</g>
<g >
<title>AllocSetAlloc (871,983,677 samples, 0.01%)</title><rect x="995.4" y="309" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="998.40" y="319.5" ></text>
</g>
<g >
<title>log@@GLIBC_2.29 (2,559,937,503 samples, 0.03%)</title><rect x="1015.7" y="309" width="0.4" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="1018.74" y="319.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,277,505,605 samples, 0.01%)</title><rect x="407.3" y="389" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="410.27" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerDelete (3,508,847,124 samples, 0.04%)</title><rect x="510.2" y="517" width="0.5" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="513.22" y="527.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,646,287,004 samples, 0.02%)</title><rect x="127.1" y="757" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="130.09" y="767.5" ></text>
</g>
<g >
<title>ExecProcNode (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="501" width="2.8" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="85.62" y="511.5" ></text>
</g>
<g >
<title>tag_hash (2,176,304,616 samples, 0.02%)</title><rect x="440.8" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="443.80" y="351.5" ></text>
</g>
<g >
<title>copyObjectImpl (1,004,604,663 samples, 0.01%)</title><rect x="1129.1" y="757" width="0.1" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="1132.06" y="767.5" ></text>
</g>
<g >
<title>pfree (1,526,540,608 samples, 0.02%)</title><rect x="249.2" y="421" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="252.18" y="431.5" ></text>
</g>
<g >
<title>GetBufferDescriptor (1,222,271,671 samples, 0.01%)</title><rect x="48.1" y="757" width="0.2" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="51.11" y="767.5" ></text>
</g>
<g >
<title>PageRepairFragmentation (2,161,795,325 samples, 0.02%)</title><rect x="1145.4" y="725" width="0.3" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="1148.39" y="735.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="629" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.04" y="639.5" ></text>
</g>
<g >
<title>generic_write_checks (1,756,558,891 samples, 0.02%)</title><rect x="501.1" y="373" width="0.2" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="504.12" y="383.5" ></text>
</g>
<g >
<title>lcons (5,886,266,929 samples, 0.06%)</title><rect x="1115.8" y="725" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1118.84" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,328,814,815 samples, 0.01%)</title><rect x="896.8" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="899.76" y="431.5" ></text>
</g>
<g >
<title>tas (2,510,880,214 samples, 0.03%)</title><rect x="491.9" y="437" width="0.3" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="494.91" y="447.5" ></text>
</g>
<g >
<title>_bt_checkkeys (2,079,231,909 samples, 0.02%)</title><rect x="83.0" y="293" width="0.2" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="85.96" y="303.5" ></text>
</g>
<g >
<title>[[vdso]] (1,454,861,327 samples, 0.02%)</title><rect x="1080.0" y="565" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1082.96" y="575.5" ></text>
</g>
<g >
<title>ExecCreateExprSetupSteps (8,192,677,773 samples, 0.09%)</title><rect x="430.0" y="405" width="1.0" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="432.99" y="415.5" ></text>
</g>
<g >
<title>element_alloc (9,499,706,893 samples, 0.10%)</title><rect x="1065.5" y="421" width="1.2" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="1068.54" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,113,537,261 samples, 0.01%)</title><rect x="893.8" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="896.80" y="431.5" ></text>
</g>
<g >
<title>do_futex (3,329,948,638 samples, 0.04%)</title><rect x="351.9" y="181" width="0.5" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="354.94" y="191.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (970,784,478 samples, 0.01%)</title><rect x="1037.8" y="261" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1040.79" y="271.5" ></text>
</g>
<g >
<title>palloc (3,626,800,390 samples, 0.04%)</title><rect x="1164.3" y="757" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1167.27" y="767.5" ></text>
</g>
<g >
<title>ReadyForQuery (168,351,580,218 samples, 1.78%)</title><rect x="186.3" y="597" width="21.0" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="189.29" y="607.5" ></text>
</g>
<g >
<title>LWLockWakeup (35,168,338,782 samples, 0.37%)</title><rect x="484.6" y="453" width="4.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="487.63" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,661,524,047 samples, 0.03%)</title><rect x="220.7" y="501" width="0.4" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="223.73" y="511.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,608,161,153 samples, 0.02%)</title><rect x="990.8" y="309" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="993.83" y="319.5" ></text>
</g>
<g >
<title>strlen@plt (1,328,699,547 samples, 0.01%)</title><rect x="852.3" y="357" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="855.31" y="367.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (4,451,602,919 samples, 0.05%)</title><rect x="10.9" y="741" width="0.5" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="13.88" y="751.5" ></text>
</g>
<g >
<title>eqsel_internal (803,901,079 samples, 0.01%)</title><rect x="126.6" y="293" width="0.1" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="129.56" y="303.5" ></text>
</g>
<g >
<title>ReleaseBuffer (3,301,940,035 samples, 0.03%)</title><rect x="248.7" y="437" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="251.73" y="447.5" ></text>
</g>
<g >
<title>create_projection_plan (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="485" width="0.2" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="127.24" y="495.5" ></text>
</g>
<g >
<title>pairingheap_remove (1,732,407,372 samples, 0.02%)</title><rect x="453.0" y="469" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="456.02" y="479.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (836,678,518 samples, 0.01%)</title><rect x="359.4" y="229" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="362.39" y="239.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,048,900,288 samples, 0.01%)</title><rect x="385.9" y="309" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="388.87" y="319.5" ></text>
</g>
<g >
<title>unix_destruct_scm (10,186,133,157 samples, 0.11%)</title><rect x="180.1" y="341" width="1.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="183.09" y="351.5" ></text>
</g>
<g >
<title>pg_utf8_verifystr (1,609,972,744 samples, 0.02%)</title><rect x="1170.1" y="757" width="0.2" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1173.09" y="767.5" ></text>
</g>
<g >
<title>relation_close (1,396,954,186 samples, 0.01%)</title><rect x="1067.1" y="469" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="1070.09" y="479.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (1,444,744,149 samples, 0.02%)</title><rect x="354.7" y="213" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="357.65" y="223.5" ></text>
</g>
<g >
<title>ExecutePlan (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="517" width="1.9" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="127.59" y="527.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,457,075,811 samples, 0.02%)</title><rect x="326.9" y="229" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="329.94" y="239.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (14,984,635,225 samples, 0.16%)</title><rect x="915.9" y="453" width="1.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="918.88" y="463.5" ></text>
</g>
<g >
<title>__update_idle_core (2,241,625,543 samples, 0.02%)</title><rect x="160.9" y="309" width="0.3" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="163.87" y="319.5" ></text>
</g>
<g >
<title>ExecScanFetch (24,335,462,816 samples, 0.26%)</title><rect x="279.2" y="373" width="3.0" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="282.17" y="383.5" ></text>
</g>
<g >
<title>__libc_start_call_main (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="757" width="2.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="125.45" y="767.5" ></text>
</g>
<g >
<title>[[vdso]] (1,663,511,226 samples, 0.02%)</title><rect x="110.3" y="757" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="113.32" y="767.5" ></text>
</g>
<g >
<title>AllocSetCheck (100,246,304,761 samples, 1.06%)</title><rect x="134.2" y="549" width="12.5" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="137.18" y="559.5" ></text>
</g>
<g >
<title>ReleaseBuffer (3,081,811,052 samples, 0.03%)</title><rect x="279.6" y="309" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="282.59" y="319.5" ></text>
</g>
<g >
<title>LockAcquireExtended (6,951,676,941 samples, 0.07%)</title><rect x="440.2" y="373" width="0.9" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="443.21" y="383.5" ></text>
</g>
<g >
<title>update_load_avg (1,054,897,338 samples, 0.01%)</title><rect x="204.1" y="261" width="0.1" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="207.09" y="271.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (933,711,098 samples, 0.01%)</title><rect x="327.0" y="213" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="330.00" y="223.5" ></text>
</g>
<g >
<title>BufferIsDirty (4,425,299,671 samples, 0.05%)</title><rect x="386.0" y="325" width="0.6" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="389.00" y="335.5" ></text>
</g>
<g >
<title>malloc (2,155,475,505 samples, 0.02%)</title><rect x="277.9" y="325" width="0.2" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="280.87" y="335.5" ></text>
</g>
<g >
<title>_bt_check_natts (15,000,934,313 samples, 0.16%)</title><rect x="332.4" y="213" width="1.9" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="335.44" y="223.5" ></text>
</g>
<g >
<title>palloc0 (1,866,451,494 samples, 0.02%)</title><rect x="996.4" y="309" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="999.35" y="319.5" ></text>
</g>
<g >
<title>newNode (2,519,521,506 samples, 0.03%)</title><rect x="1056.1" y="437" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1059.15" y="447.5" ></text>
</g>
<g >
<title>ExecClearTuple (12,341,404,186 samples, 0.13%)</title><rect x="247.9" y="469" width="1.6" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="250.93" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,147,617,831 samples, 0.01%)</title><rect x="1147.6" y="565" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="1150.60" y="575.5" ></text>
</g>
<g >
<title>pg_database_encoding_max_length (1,082,673,248 samples, 0.01%)</title><rect x="1114.3" y="677" width="0.1" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="1117.27" y="687.5" ></text>
</g>
<g >
<title>shmem_get_folio_gfp (11,468,854,830 samples, 0.12%)</title><rect x="498.6" y="341" width="1.5" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="501.62" y="351.5" ></text>
</g>
<g >
<title>AllocSetFree (1,144,625,573 samples, 0.01%)</title><rect x="993.9" y="293" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="996.94" y="303.5" ></text>
</g>
<g >
<title>palloc0 (6,491,886,877 samples, 0.07%)</title><rect x="404.7" y="469" width="0.8" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="407.67" y="479.5" ></text>
</g>
<g >
<title>compute_new_xmax_infomask (1,184,537,371 samples, 0.01%)</title><rect x="1128.6" y="757" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1131.61" y="767.5" ></text>
</g>
<g >
<title>op_in_opfamily (8,668,007,976 samples, 0.09%)</title><rect x="1021.4" y="309" width="1.1" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="1024.43" y="319.5" ></text>
</g>
<g >
<title>TransactionLogFetch (7,147,513,795 samples, 0.08%)</title><rect x="1147.0" y="677" width="0.9" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="1149.97" y="687.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,240,892,050 samples, 0.01%)</title><rect x="928.3" y="421" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="931.29" y="431.5" ></text>
</g>
<g >
<title>newNode (3,585,646,691 samples, 0.04%)</title><rect x="1017.3" y="341" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1020.33" y="351.5" ></text>
</g>
<g >
<title>FileSize (14,332,466,525 samples, 0.15%)</title><rect x="936.3" y="245" width="1.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="939.30" y="255.5" ></text>
</g>
<g >
<title>fdatasync (9,598,719,120 samples, 0.10%)</title><rect x="502.0" y="437" width="1.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="504.97" y="447.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,833,890,938 samples, 0.04%)</title><rect x="390.0" y="341" width="0.5" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="393.04" y="351.5" ></text>
</g>
<g >
<title>standard_ExecutorFinish (3,766,277,403 samples, 0.04%)</title><rect x="262.8" y="517" width="0.4" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="265.76" y="527.5" ></text>
</g>
<g >
<title>heap_getattr (6,280,255,602 samples, 0.07%)</title><rect x="360.7" y="341" width="0.8" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="363.67" y="351.5" ></text>
</g>
<g >
<title>ProcessClientWriteInterrupt (1,136,787,431 samples, 0.01%)</title><rect x="86.8" y="757" width="0.2" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="89.84" y="767.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,761,296,941 samples, 0.02%)</title><rect x="380.1" y="293" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="383.11" y="303.5" ></text>
</g>
<g >
<title>RelationClose (1,156,094,467 samples, 0.01%)</title><rect x="923.0" y="437" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="925.97" y="447.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (3,020,540,291 samples, 0.03%)</title><rect x="259.9" y="389" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="262.94" y="399.5" ></text>
</g>
<g >
<title>ActiveSnapshotSet (842,984,476 samples, 0.01%)</title><rect x="10.2" y="757" width="0.1" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="13.19" y="767.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,190,787,620 samples, 0.01%)</title><rect x="401.5" y="277" width="0.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="404.45" y="287.5" ></text>
</g>
<g >
<title>fetch_search_path_array (1,959,135,171 samples, 0.02%)</title><rect x="852.5" y="373" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="855.49" y="383.5" ></text>
</g>
<g >
<title>eval_const_expressions (1,610,408,406 samples, 0.02%)</title><rect x="1133.9" y="757" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1136.93" y="767.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (4,538,166,602 samples, 0.05%)</title><rect x="343.8" y="357" width="0.5" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="346.75" y="367.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,885,294,765 samples, 0.05%)</title><rect x="427.4" y="357" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="430.44" y="367.5" ></text>
</g>
<g >
<title>newNode (7,666,694,798 samples, 0.08%)</title><rect x="914.2" y="469" width="0.9" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="917.18" y="479.5" ></text>
</g>
<g >
<title>CopySnapshot (4,457,147,137 samples, 0.05%)</title><rect x="456.0" y="549" width="0.6" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="459.04" y="559.5" ></text>
</g>
<g >
<title>AllocSetAlloc (996,045,220 samples, 0.01%)</title><rect x="976.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="979.67" y="399.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (964,090,859 samples, 0.01%)</title><rect x="863.1" y="437" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="866.11" y="447.5" ></text>
</g>
<g >
<title>ReadBufferExtended (1,209,463,504 samples, 0.01%)</title><rect x="335.5" y="197" width="0.2" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="338.51" y="207.5" ></text>
</g>
<g >
<title>main (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="709" width="953.9" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="133.07" y="719.5" >main</text>
</g>
<g >
<title>downcase_identifier (11,223,720,720 samples, 0.12%)</title><rect x="1113.0" y="693" width="1.4" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1116.00" y="703.5" ></text>
</g>
<g >
<title>rseq_update_cpu_node_id (1,111,272,583 samples, 0.01%)</title><rect x="174.0" y="405" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="176.98" y="415.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (850,641,677 samples, 0.01%)</title><rect x="441.0" y="309" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="443.97" y="319.5" ></text>
</g>
<g >
<title>index_beginscan (29,568,279,427 samples, 0.31%)</title><rect x="289.7" y="325" width="3.7" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="292.65" y="335.5" ></text>
</g>
<g >
<title>LWLockReleaseClearVar (4,829,388,331 samples, 0.05%)</title><rect x="382.0" y="293" width="0.6" height="15.0" fill="rgb(253,221,52)" rx="2" ry="2" />
<text  x="384.98" y="303.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (4,753,837,963 samples, 0.05%)</title><rect x="273.5" y="373" width="0.6" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="276.55" y="383.5" ></text>
</g>
<g >
<title>palloc0 (3,510,900,795 samples, 0.04%)</title><rect x="1117.2" y="709" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1120.16" y="719.5" ></text>
</g>
<g >
<title>LockAcquireExtended (21,760,064,265 samples, 0.23%)</title><rect x="940.4" y="357" width="2.8" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="943.43" y="367.5" ></text>
</g>
<g >
<title>get_futex_key (1,652,173,185 samples, 0.02%)</title><rect x="352.1" y="149" width="0.2" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="355.08" y="159.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (2,951,311,824 samples, 0.03%)</title><rect x="1079.9" y="581" width="0.4" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1082.91" y="591.5" ></text>
</g>
<g >
<title>AllocSetCheck (126,145,432,688 samples, 1.34%)</title><rect x="12.9" y="757" width="15.8" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="15.88" y="767.5" ></text>
</g>
<g >
<title>nameeqfast (2,458,987,478 samples, 0.03%)</title><rect x="824.6" y="293" width="0.4" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="827.64" y="303.5" ></text>
</g>
<g >
<title>futex_wake (1,910,122,977 samples, 0.02%)</title><rect x="363.7" y="197" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="366.69" y="207.5" ></text>
</g>
<g >
<title>_bt_metaversion (1,715,351,059 samples, 0.02%)</title><rect x="321.8" y="261" width="0.2" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="324.79" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,434,937,213 samples, 0.02%)</title><rect x="809.3" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="812.34" y="463.5" ></text>
</g>
<g >
<title>PinBufferForBlock (3,623,504,086 samples, 0.04%)</title><rect x="84.9" y="197" width="0.5" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="87.94" y="207.5" ></text>
</g>
<g >
<title>add_other_rels_to_query (1,130,018,439 samples, 0.01%)</title><rect x="950.3" y="469" width="0.1" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="953.30" y="479.5" ></text>
</g>
<g >
<title>CreateExecutorState (13,082,374,454 samples, 0.14%)</title><rect x="403.8" y="501" width="1.7" height="15.0" fill="rgb(228,105,25)" rx="2" ry="2" />
<text  x="406.84" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (913,417,808 samples, 0.01%)</title><rect x="855.7" y="277" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="858.74" y="287.5" ></text>
</g>
<g >
<title>fdget (2,090,019,978 samples, 0.02%)</title><rect x="172.0" y="405" width="0.3" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="175.01" y="415.5" ></text>
</g>
<g >
<title>set_task_cpu (5,917,242,279 samples, 0.06%)</title><rect x="203.0" y="325" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="205.99" y="335.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,050,489,500 samples, 0.01%)</title><rect x="408.5" y="469" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="411.53" y="479.5" ></text>
</g>
<g >
<title>is_redundant_with_indexclauses (1,161,781,289 samples, 0.01%)</title><rect x="1016.4" y="309" width="0.2" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="1019.42" y="319.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (5,257,825,510 samples, 0.06%)</title><rect x="1053.9" y="437" width="0.7" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1056.92" y="447.5" ></text>
</g>
<g >
<title>examine_simple_variable (1,035,330,389 samples, 0.01%)</title><rect x="1134.3" y="757" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1137.34" y="767.5" ></text>
</g>
<g >
<title>my_log2 (1,250,426,822 samples, 0.01%)</title><rect x="1064.4" y="421" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="1067.40" y="431.5" ></text>
</g>
<g >
<title>AllocSetCheck (35,525,567,238 samples, 0.38%)</title><rect x="252.8" y="437" width="4.4" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="255.76" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_or_u32 (929,973,725 samples, 0.01%)</title><rect x="324.4" y="197" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="327.40" y="207.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="453" width="1.7" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="125.45" y="463.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (8,634,979,562 samples, 0.09%)</title><rect x="132.7" y="517" width="1.1" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="135.75" y="527.5" ></text>
</g>
<g >
<title>_int_malloc (7,472,970,946 samples, 0.08%)</title><rect x="292.0" y="213" width="1.0" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="295.04" y="223.5" ></text>
</g>
<g >
<title>heapam_estimate_rel_size (1,734,917,393 samples, 0.02%)</title><rect x="1149.3" y="757" width="0.2" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1152.26" y="767.5" ></text>
</g>
<g >
<title>LockTagHashCode (3,037,862,860 samples, 0.03%)</title><rect x="354.9" y="277" width="0.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="357.95" y="287.5" ></text>
</g>
<g >
<title>RangeVarGetRelidExtended (79,727,208,207 samples, 0.84%)</title><rect x="817.8" y="421" width="9.9" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="820.76" y="431.5" ></text>
</g>
<g >
<title>lappend (971,583,917 samples, 0.01%)</title><rect x="425.3" y="357" width="0.1" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="428.32" y="367.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (2,949,452,680 samples, 0.03%)</title><rect x="851.0" y="325" width="0.4" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="854.01" y="335.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (856,748,572 samples, 0.01%)</title><rect x="951.7" y="373" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="954.70" y="383.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="453" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1161.36" y="463.5" ></text>
</g>
<g >
<title>BufTableHashCode (2,783,129,438 samples, 0.03%)</title><rect x="99.1" y="181" width="0.3" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="102.09" y="191.5" ></text>
</g>
<g >
<title>pull_varnos (10,096,254,828 samples, 0.11%)</title><rect x="974.0" y="405" width="1.2" height="15.0" fill="rgb(229,112,26)" rx="2" ry="2" />
<text  x="976.98" y="415.5" ></text>
</g>
<g >
<title>ScanKeyEntryInitialize (1,382,390,890 samples, 0.01%)</title><rect x="92.9" y="757" width="0.1" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="95.86" y="767.5" ></text>
</g>
<g >
<title>CreateExprContext (10,341,751,928 samples, 0.11%)</title><rect x="269.1" y="405" width="1.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="272.06" y="415.5" ></text>
</g>
<g >
<title>PushActiveSnapshot (8,966,978,825 samples, 0.10%)</title><rect x="455.9" y="581" width="1.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="458.85" y="591.5" ></text>
</g>
<g >
<title>get_expr_width (9,398,703,902 samples, 0.10%)</title><rect x="1047.0" y="469" width="1.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="1050.01" y="479.5" ></text>
</g>
<g >
<title>palloc (846,759,695 samples, 0.01%)</title><rect x="1018.7" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1021.73" y="351.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (2,106,573,335 samples, 0.02%)</title><rect x="521.0" y="389" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="523.95" y="399.5" ></text>
</g>
<g >
<title>BufferIsPermanent (1,046,425,900 samples, 0.01%)</title><rect x="1146.5" y="677" width="0.1" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="1149.52" y="687.5" ></text>
</g>
<g >
<title>get_timeout_active (867,326,558 samples, 0.01%)</title><rect x="1140.9" y="757" width="0.1" height="15.0" fill="rgb(254,225,54)" rx="2" ry="2" />
<text  x="1143.91" y="767.5" ></text>
</g>
<g >
<title>ExecutorRun (1,119,437,817,530 samples, 11.86%)</title><rect x="263.2" y="533" width="140.0" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="266.23" y="543.5" >ExecutorRun</text>
</g>
<g >
<title>do_syscall_64 (5,949,844,606 samples, 0.06%)</title><rect x="502.4" y="405" width="0.7" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="505.39" y="415.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableFullXid (2,745,504,067 samples, 0.03%)</title><rect x="303.5" y="213" width="0.3" height="15.0" fill="rgb(232,128,30)" rx="2" ry="2" />
<text  x="306.49" y="223.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,317,334,040 samples, 0.01%)</title><rect x="299.6" y="85" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="302.63" y="95.5" ></text>
</g>
<g >
<title>CheckValidResultRel (10,425,466,962 samples, 0.11%)</title><rect x="411.2" y="453" width="1.3" height="15.0" fill="rgb(213,41,9)" rx="2" ry="2" />
<text  x="414.17" y="463.5" ></text>
</g>
<g >
<title>table_index_fetch_end (2,633,458,872 samples, 0.03%)</title><rect x="246.5" y="405" width="0.3" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="249.47" y="415.5" ></text>
</g>
<g >
<title>fill_val (1,613,589,929 samples, 0.02%)</title><rect x="391.2" y="341" width="0.2" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="394.18" y="351.5" ></text>
</g>
<g >
<title>heap_page_prune_opt (18,147,718,835 samples, 0.19%)</title><rect x="308.8" y="261" width="2.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="311.83" y="271.5" ></text>
</g>
<g >
<title>sched_balance_update_blocked_averages (1,277,417,898 samples, 0.01%)</title><rect x="750.9" y="453" width="0.2" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="753.91" y="463.5" ></text>
</g>
<g >
<title>wipe_mem (920,414,441 samples, 0.01%)</title><rect x="1189.7" y="757" width="0.1" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="1192.72" y="767.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,131,194,266 samples, 0.01%)</title><rect x="845.1" y="373" width="0.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="848.14" y="383.5" ></text>
</g>
<g >
<title>avc_has_perm (1,915,821,729 samples, 0.02%)</title><rect x="191.4" y="389" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="194.43" y="399.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,419,531,450 samples, 0.02%)</title><rect x="1117.4" y="677" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1120.37" y="687.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (3,840,584,719 samples, 0.04%)</title><rect x="1013.3" y="293" width="0.5" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1016.30" y="303.5" ></text>
</g>
<g >
<title>WaitEventSetWait (187,191,614,834 samples, 1.98%)</title><rect x="151.7" y="517" width="23.4" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="154.68" y="527.5" >W..</text>
</g>
<g >
<title>__x64_sys_fdatasync (4,018,137,428 samples, 0.04%)</title><rect x="502.5" y="389" width="0.5" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="505.49" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,783,678,206 samples, 0.04%)</title><rect x="803.0" y="357" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="806.02" y="367.5" ></text>
</g>
<g >
<title>update_load_avg (4,505,515,508 samples, 0.05%)</title><rect x="481.0" y="213" width="0.6" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="483.99" y="223.5" ></text>
</g>
<g >
<title>_bt_fix_scankey_strategy (837,088,535 samples, 0.01%)</title><rect x="128.3" y="757" width="0.1" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="131.31" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (20,147,732,696 samples, 0.21%)</title><rect x="356.0" y="261" width="2.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="359.04" y="271.5" ></text>
</g>
<g >
<title>perf_event_context_sched_out (9,257,666,902 samples, 0.10%)</title><rect x="162.4" y="309" width="1.1" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="165.39" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (939,661,627 samples, 0.01%)</title><rect x="213.1" y="549" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="216.09" y="559.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (2,899,408,097 samples, 0.03%)</title><rect x="175.8" y="485" width="0.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="178.83" y="495.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="693" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1136.99" y="703.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (1,109,776,569 samples, 0.01%)</title><rect x="239.6" y="389" width="0.2" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="242.64" y="399.5" ></text>
</g>
<g >
<title>unix_stream_recvmsg (51,633,510,269 samples, 0.55%)</title><rect x="177.9" y="405" width="6.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="180.89" y="415.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (972,274,918 samples, 0.01%)</title><rect x="846.5" y="357" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="849.48" y="367.5" ></text>
</g>
<g >
<title>list_make1_impl (4,645,379,596 samples, 0.05%)</title><rect x="1115.1" y="741" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1118.13" y="751.5" ></text>
</g>
<g >
<title>pairingheap_add (885,865,570 samples, 0.01%)</title><rect x="1164.1" y="757" width="0.1" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1167.11" y="767.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (77,118,402,757 samples, 0.82%)</title><rect x="252.6" y="469" width="9.6" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="255.59" y="479.5" ></text>
</g>
<g >
<title>palloc0 (1,574,989,621 samples, 0.02%)</title><rect x="839.0" y="325" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="842.05" y="335.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (4,007,373,464 samples, 0.04%)</title><rect x="255.5" y="421" width="0.5" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="258.48" y="431.5" ></text>
</g>
<g >
<title>newNode (5,250,551,231 samples, 0.06%)</title><rect x="1120.4" y="725" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1123.39" y="735.5" ></text>
</g>
<g >
<title>MarkBufferDirtyHint (5,668,473,203 samples, 0.06%)</title><rect x="305.8" y="197" width="0.7" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="308.77" y="207.5" ></text>
</g>
<g >
<title>ExecProcNode (1,319,622,605 samples, 0.01%)</title><rect x="401.8" y="453" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="404.78" y="463.5" ></text>
</g>
<g >
<title>pgstat_report_activity (6,167,338,887 samples, 0.07%)</title><rect x="1079.5" y="597" width="0.8" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1082.53" y="607.5" ></text>
</g>
<g >
<title>wake_up_q (19,690,026,464 samples, 0.21%)</title><rect x="486.3" y="325" width="2.4" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="489.26" y="335.5" ></text>
</g>
<g >
<title>secure_raw_write (145,435,166,955 samples, 1.54%)</title><rect x="189.1" y="517" width="18.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="192.06" y="527.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="229" width="4.3" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="97.67" y="239.5" ></text>
</g>
<g >
<title>clauselist_selectivity_ext (76,528,221,821 samples, 0.81%)</title><rect x="1028.6" y="373" width="9.5" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="1031.56" y="383.5" ></text>
</g>
<g >
<title>newNode (4,455,698,483 samples, 0.05%)</title><rect x="967.6" y="373" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="970.63" y="383.5" ></text>
</g>
<g >
<title>table_index_fetch_begin (2,292,925,609 samples, 0.02%)</title><rect x="293.1" y="309" width="0.3" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="296.06" y="319.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (2,621,296,821 samples, 0.03%)</title><rect x="432.4" y="389" width="0.4" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="435.44" y="399.5" ></text>
</g>
<g >
<title>set_rel_width (1,355,712,340 samples, 0.01%)</title><rect x="1182.2" y="757" width="0.1" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="1185.18" y="767.5" ></text>
</g>
<g >
<title>castNodeImpl (843,077,691 samples, 0.01%)</title><rect x="906.5" y="501" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="909.48" y="511.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,740,372,251 samples, 0.05%)</title><rect x="901.5" y="389" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="904.49" y="399.5" ></text>
</g>
<g >
<title>rseq_get_rseq_cs.isra.0 (3,906,132,072 samples, 0.04%)</title><rect x="173.5" y="389" width="0.5" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="176.49" y="399.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (3,251,622,179 samples, 0.03%)</title><rect x="1085.3" y="757" width="0.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1088.30" y="767.5" ></text>
</g>
<g >
<title>attnameAttNum (938,767,713 samples, 0.01%)</title><rect x="1086.8" y="757" width="0.1" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="1089.82" y="767.5" ></text>
</g>
<g >
<title>__update_blocked_fair (969,898,211 samples, 0.01%)</title><rect x="750.9" y="437" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="753.92" y="447.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (845,038,045 samples, 0.01%)</title><rect x="445.6" y="309" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="448.62" y="319.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,909,580,848 samples, 0.04%)</title><rect x="407.5" y="389" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="410.54" y="399.5" ></text>
</g>
<g >
<title>ProcessQuery (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="613" width="2.8" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="85.62" y="623.5" ></text>
</g>
<g >
<title>pfree (2,220,128,022 samples, 0.02%)</title><rect x="1073.3" y="565" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1076.30" y="575.5" ></text>
</g>
<g >
<title>IsToastNamespace (860,625,379 samples, 0.01%)</title><rect x="407.1" y="389" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="410.11" y="399.5" ></text>
</g>
<g >
<title>ReadBufferExtended (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="245" width="0.1" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="1161.26" y="255.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (7,552,074,846 samples, 0.08%)</title><rect x="889.5" y="293" width="1.0" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="892.55" y="303.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,714,732,467 samples, 0.02%)</title><rect x="400.5" y="277" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="403.50" y="287.5" ></text>
</g>
<g >
<title>ExecPushExprSetupSteps (7,001,543,793 samples, 0.07%)</title><rect x="271.4" y="405" width="0.9" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="274.41" y="415.5" ></text>
</g>
<g >
<title>BlockIdSet (1,015,604,371 samples, 0.01%)</title><rect x="33.9" y="757" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="36.93" y="767.5" ></text>
</g>
<g >
<title>ReadBuffer_common (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="261" width="2.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="102.04" y="271.5" ></text>
</g>
<g >
<title>btint4cmp (3,729,197,953 samples, 0.04%)</title><rect x="316.6" y="213" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="319.55" y="223.5" ></text>
</g>
<g >
<title>TransactionIdCommitTree (28,016,595,904 samples, 0.30%)</title><rect x="469.6" y="501" width="3.5" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="472.59" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,393,567,840 samples, 0.01%)</title><rect x="355.4" y="261" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="358.38" y="271.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (1,487,937,497 samples, 0.02%)</title><rect x="10.0" y="757" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="13.01" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (48,608,861,890 samples, 0.52%)</title><rect x="240.2" y="373" width="6.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="243.24" y="383.5" ></text>
</g>
<g >
<title>palloc0 (1,675,151,368 samples, 0.02%)</title><rect x="980.6" y="421" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="983.64" y="431.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (965,583,189 samples, 0.01%)</title><rect x="325.5" y="165" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="328.54" y="175.5" ></text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (9,924,319,046 samples, 0.11%)</title><rect x="974.0" y="389" width="1.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="977.00" y="399.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,889,398,438 samples, 0.03%)</title><rect x="401.4" y="293" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="404.36" y="303.5" ></text>
</g>
<g >
<title>hash_search (18,762,485,346 samples, 0.20%)</title><rect x="849.0" y="373" width="2.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="852.04" y="383.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (1,217,297,839 samples, 0.01%)</title><rect x="124.1" y="261" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="127.08" y="271.5" ></text>
</g>
<g >
<title>heap_attisnull (895,601,475 samples, 0.01%)</title><rect x="392.4" y="357" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="395.42" y="367.5" ></text>
</g>
<g >
<title>bms_add_member (2,460,382,664 samples, 0.03%)</title><rect x="855.6" y="325" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="858.56" y="335.5" ></text>
</g>
<g >
<title>avc_lookup (1,481,978,142 samples, 0.02%)</title><rect x="191.5" y="357" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="194.48" y="367.5" ></text>
</g>
<g >
<title>_bt_binsrch (2,871,252,156 samples, 0.03%)</title><rect x="123.2" y="277" width="0.3" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="126.18" y="287.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableXid (832,734,647 samples, 0.01%)</title><rect x="1148.7" y="693" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1151.67" y="703.5" ></text>
</g>
<g >
<title>AllocSetAlloc (946,585,443 samples, 0.01%)</title><rect x="853.7" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="856.74" y="351.5" ></text>
</g>
<g >
<title>ReadBuffer (1,478,991,388 samples, 0.02%)</title><rect x="87.3" y="757" width="0.2" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="90.31" y="767.5" ></text>
</g>
<g >
<title>_bt_binsrch (42,339,002,253 samples, 0.45%)</title><rect x="329.6" y="245" width="5.3" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="332.61" y="255.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,268,536,024 samples, 0.01%)</title><rect x="503.6" y="421" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="506.57" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (862,599,286 samples, 0.01%)</title><rect x="1038.7" y="309" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1041.71" y="319.5" ></text>
</g>
<g >
<title>palloc (14,163,942,256 samples, 0.15%)</title><rect x="291.3" y="277" width="1.8" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="294.29" y="287.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (2,800,759,791 samples, 0.03%)</title><rect x="133.5" y="469" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="136.46" y="479.5" ></text>
</g>
<g >
<title>heapam_tuple_update (349,585,314,403 samples, 3.70%)</title><rect x="344.6" y="389" width="43.7" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="347.64" y="399.5" >heap..</text>
</g>
<g >
<title>GetPrivateRefCount (937,165,422 samples, 0.01%)</title><rect x="305.6" y="181" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="308.60" y="191.5" ></text>
</g>
<g >
<title>MemoryContextStrdup (2,460,527,838 samples, 0.03%)</title><rect x="921.4" y="421" width="0.3" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="924.42" y="431.5" ></text>
</g>
<g >
<title>GetXLogBuffer (1,132,238,669 samples, 0.01%)</title><rect x="507.2" y="437" width="0.1" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="510.19" y="447.5" ></text>
</g>
<g >
<title>clauselist_selectivity_ext (803,901,079 samples, 0.01%)</title><rect x="126.6" y="389" width="0.1" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="129.56" y="399.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,256,797,002 samples, 0.02%)</title><rect x="832.5" y="453" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="835.52" y="463.5" ></text>
</g>
<g >
<title>select_task_rq_fair (2,213,046,869 samples, 0.02%)</title><rect x="488.0" y="277" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="491.00" y="287.5" ></text>
</g>
<g >
<title>do_futex (1,996,115,647 samples, 0.02%)</title><rect x="363.7" y="213" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="366.67" y="223.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (1,950,874,371 samples, 0.02%)</title><rect x="817.5" y="421" width="0.3" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="820.52" y="431.5" ></text>
</g>
<g >
<title>palloc (2,202,174,919 samples, 0.02%)</title><rect x="1115.4" y="709" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1118.41" y="719.5" ></text>
</g>
<g >
<title>exec_simple_query (81,441,377,880 samples, 0.86%)</title><rect x="94.7" y="677" width="10.2" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="97.67" y="687.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (894,126,186 samples, 0.01%)</title><rect x="91.6" y="757" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="94.63" y="767.5" ></text>
</g>
<g >
<title>ReservePrivateRefCountEntry (1,075,851,368 samples, 0.01%)</title><rect x="401.2" y="277" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="404.18" y="287.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,428,737,271 samples, 0.02%)</title><rect x="100.4" y="133" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="103.37" y="143.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,001,651,420 samples, 0.04%)</title><rect x="430.5" y="373" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="433.51" y="383.5" ></text>
</g>
<g >
<title>get_relation_notnullatts (967,745,817 samples, 0.01%)</title><rect x="1140.2" y="757" width="0.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1143.24" y="767.5" ></text>
</g>
<g >
<title>RelationGetIndexList (3,750,808,496 samples, 0.04%)</title><rect x="393.5" y="389" width="0.5" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="396.55" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,296,167,945 samples, 0.01%)</title><rect x="279.7" y="261" width="0.2" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="282.70" y="271.5" ></text>
</g>
<g >
<title>AssignTransactionId (89,546,196,413 samples, 0.95%)</title><rect x="348.3" y="341" width="11.2" height="15.0" fill="rgb(205,2,0)" rx="2" ry="2" />
<text  x="351.33" y="351.5" ></text>
</g>
<g >
<title>index_open (15,046,326,716 samples, 0.16%)</title><rect x="394.0" y="389" width="1.9" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="397.03" y="399.5" ></text>
</g>
<g >
<title>uint32_hash (1,516,137,612 samples, 0.02%)</title><rect x="925.0" y="405" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="928.04" y="415.5" ></text>
</g>
<g >
<title>SlruSelectLRUPage (3,468,742,584 samples, 0.04%)</title><rect x="471.9" y="421" width="0.4" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="474.88" y="431.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,194,236,097 samples, 0.01%)</title><rect x="215.7" y="549" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="218.70" y="559.5" ></text>
</g>
<g >
<title>gup_fast_fallback (2,733,460,336 samples, 0.03%)</title><rect x="485.9" y="309" width="0.3" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="488.85" y="319.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (856,200,212 samples, 0.01%)</title><rect x="236.2" y="421" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="239.23" y="431.5" ></text>
</g>
<g >
<title>bms_next_member (2,248,014,667 samples, 0.02%)</title><rect x="980.9" y="453" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="983.91" y="463.5" ></text>
</g>
<g >
<title>TupleDescAttr (1,007,336,152 samples, 0.01%)</title><rect x="115.6" y="741" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="118.62" y="751.5" ></text>
</g>
<g >
<title>get_quals_from_indexclauses (4,976,930,533 samples, 0.05%)</title><rect x="1012.7" y="293" width="0.6" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1015.68" y="303.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,448,398,293 samples, 0.05%)</title><rect x="1038.8" y="325" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1041.82" y="335.5" ></text>
</g>
<g >
<title>_bt_lockbuf (1,932,277,437 samples, 0.02%)</title><rect x="126.0" y="229" width="0.2" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="129.00" y="239.5" ></text>
</g>
<g >
<title>palloc0 (1,773,285,099 samples, 0.02%)</title><rect x="1118.9" y="709" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1121.89" y="719.5" ></text>
</g>
<g >
<title>new_list (1,709,550,661 samples, 0.02%)</title><rect x="1019.5" y="325" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1022.54" y="335.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,653,468,180 samples, 0.03%)</title><rect x="836.5" y="325" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="839.52" y="335.5" ></text>
</g>
<g >
<title>_bt_compare (2,392,388,877 samples, 0.03%)</title><rect x="127.9" y="757" width="0.3" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="130.87" y="767.5" ></text>
</g>
<g >
<title>tag_hash (11,665,778,961 samples, 0.12%)</title><rect x="849.9" y="357" width="1.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="852.93" y="367.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop (3,310,649,206 samples, 0.04%)</title><rect x="1055.4" y="469" width="0.5" height="15.0" fill="rgb(243,175,41)" rx="2" ry="2" />
<text  x="1058.45" y="479.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,590,872,548 samples, 0.02%)</title><rect x="423.0" y="341" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="425.95" y="351.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (4,960,745,595 samples, 0.05%)</title><rect x="95.5" y="165" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="98.47" y="175.5" ></text>
</g>
<g >
<title>tts_buffer_heap_getsomeattrs (7,907,523,754 samples, 0.08%)</title><rect x="288.2" y="245" width="1.0" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="291.17" y="255.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,959,284,489 samples, 0.02%)</title><rect x="520.0" y="405" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="522.99" y="415.5" ></text>
</g>
<g >
<title>BufferAlloc (17,544,117,270 samples, 0.19%)</title><rect x="99.0" y="197" width="2.2" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="102.05" y="207.5" ></text>
</g>
<g >
<title>pfree (1,331,036,800 samples, 0.01%)</title><rect x="966.8" y="357" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="969.80" y="367.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,087,128,597 samples, 0.01%)</title><rect x="522.6" y="421" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="525.59" y="431.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (950,851,619 samples, 0.01%)</title><rect x="923.0" y="421" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="925.99" y="431.5" ></text>
</g>
<g >
<title>new_list (1,920,796,439 samples, 0.02%)</title><rect x="956.6" y="293" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="959.62" y="303.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (2,574,810,915 samples, 0.03%)</title><rect x="1046.2" y="389" width="0.3" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1049.17" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerDelete (5,282,358,386 samples, 0.06%)</title><rect x="224.1" y="565" width="0.6" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="227.07" y="575.5" ></text>
</g>
<g >
<title>shmem_write_end (8,477,282,857 samples, 0.09%)</title><rect x="500.1" y="357" width="1.0" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="503.06" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="389" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="88.40" y="399.5" ></text>
</g>
<g >
<title>fdatasync@plt (931,926,685 samples, 0.01%)</title><rect x="503.2" y="437" width="0.1" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="506.17" y="447.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,610,429,450 samples, 0.02%)</title><rect x="1035.3" y="213" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1038.29" y="223.5" ></text>
</g>
<g >
<title>uint32_hash (1,238,130,706 samples, 0.01%)</title><rect x="949.1" y="341" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="952.15" y="351.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (7,314,667,134 samples, 0.08%)</title><rect x="750.8" y="533" width="1.0" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="753.84" y="543.5" ></text>
</g>
<g >
<title>fix_scan_expr (29,731,814,530 samples, 0.32%)</title><rect x="900.1" y="469" width="3.7" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="903.08" y="479.5" ></text>
</g>
<g >
<title>DynaHashAlloc (3,779,440,835 samples, 0.04%)</title><rect x="1061.4" y="453" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1064.41" y="463.5" ></text>
</g>
<g >
<title>exec_rt_fetch (907,791,528 samples, 0.01%)</title><rect x="443.9" y="421" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="446.86" y="431.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,038,398,311 samples, 0.01%)</title><rect x="398.7" y="309" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="401.66" y="319.5" ></text>
</g>
<g >
<title>list_make1_impl (1,055,791,948 samples, 0.01%)</title><rect x="1156.4" y="757" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1159.35" y="767.5" ></text>
</g>
<g >
<title>futex_hash (858,918,125 samples, 0.01%)</title><rect x="485.5" y="325" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="488.46" y="335.5" ></text>
</g>
<g >
<title>assign_query_collations_walker (2,069,711,771 samples, 0.02%)</title><rect x="1086.4" y="757" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1089.40" y="767.5" ></text>
</g>
<g >
<title>ExecShutdownNode_walker (7,282,247,651 samples, 0.08%)</title><rect x="402.1" y="469" width="1.0" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="405.14" y="479.5" ></text>
</g>
<g >
<title>finalize_plan (28,660,398,970 samples, 0.30%)</title><rect x="878.2" y="485" width="3.6" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="881.21" y="495.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,276,100,836 samples, 0.01%)</title><rect x="354.4" y="261" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="357.38" y="271.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,381,620,981 samples, 0.01%)</title><rect x="924.2" y="389" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="927.17" y="399.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (1,431,571,239 samples, 0.02%)</title><rect x="1064.1" y="421" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1067.08" y="431.5" ></text>
</g>
<g >
<title>LWLockWakeup (1,605,006,433 samples, 0.02%)</title><rect x="354.6" y="245" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="357.64" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,602,953,622 samples, 0.03%)</title><rect x="885.5" y="373" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="888.45" y="383.5" ></text>
</g>
<g >
<title>GETSTRUCT (1,746,490,308 samples, 0.02%)</title><rect x="47.8" y="757" width="0.3" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="50.83" y="767.5" ></text>
</g>
<g >
<title>ExecTypeFromTLInternal (2,211,433,668 samples, 0.02%)</title><rect x="448.6" y="421" width="0.3" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="451.61" y="431.5" ></text>
</g>
<g >
<title>jit_compile_expr (979,193,897 samples, 0.01%)</title><rect x="1153.2" y="757" width="0.1" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="1156.20" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,851,429,287 samples, 0.05%)</title><rect x="1042.3" y="325" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1045.33" y="335.5" ></text>
</g>
<g >
<title>TupleDescAttr (2,307,441,903 samples, 0.02%)</title><rect x="107.1" y="757" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="110.14" y="767.5" ></text>
</g>
<g >
<title>ExecInitInterpreter (838,228,521 samples, 0.01%)</title><rect x="43.4" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="46.42" y="767.5" ></text>
</g>
<g >
<title>newNode (5,480,968,631 samples, 0.06%)</title><rect x="808.8" y="485" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="811.84" y="495.5" ></text>
</g>
<g >
<title>bms_is_valid_set (3,732,284,698 samples, 0.04%)</title><rect x="1122.6" y="757" width="0.4" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="1125.56" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,364,524,616 samples, 0.01%)</title><rect x="944.3" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="947.29" y="367.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,220,014,564 samples, 0.02%)</title><rect x="880.8" y="405" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="883.83" y="415.5" ></text>
</g>
<g >
<title>bms_union (876,268,247 samples, 0.01%)</title><rect x="878.1" y="485" width="0.1" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="881.10" y="495.5" ></text>
</g>
<g >
<title>pgstat_count_slru_blocks_hit (994,852,419 samples, 0.01%)</title><rect x="1171.0" y="757" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1174.05" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,608,629,525 samples, 0.05%)</title><rect x="881.2" y="389" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="884.21" y="399.5" ></text>
</g>
<g >
<title>lappend (2,835,150,788 samples, 0.03%)</title><rect x="1015.4" y="309" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1018.38" y="319.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="501" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="88.40" y="511.5" ></text>
</g>
<g >
<title>set_base_rel_sizes (132,372,590,481 samples, 1.40%)</title><rect x="1026.4" y="453" width="16.5" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1029.38" y="463.5" ></text>
</g>
<g >
<title>pg_class_aclcheck_ext (7,120,313,581 samples, 0.08%)</title><rect x="1034.4" y="181" width="0.8" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1037.35" y="191.5" ></text>
</g>
<g >
<title>PredicateLockPage (1,123,777,488 samples, 0.01%)</title><rect x="86.3" y="757" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="89.26" y="767.5" ></text>
</g>
<g >
<title>subquery_planner (2,495,599,539 samples, 0.03%)</title><rect x="126.5" y="533" width="0.3" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="129.52" y="543.5" ></text>
</g>
<g >
<title>index_getattr (2,079,231,909 samples, 0.02%)</title><rect x="83.0" y="261" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="85.96" y="271.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="277" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1161.26" y="287.5" ></text>
</g>
<g >
<title>base_yyparse (276,883,454,929 samples, 2.93%)</title><rect x="1087.0" y="757" width="34.7" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1090.04" y="767.5" >ba..</text>
</g>
<g >
<title>ProcReleaseLocks (113,991,473,671 samples, 1.21%)</title><rect x="511.1" y="485" width="14.3" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="514.11" y="495.5" ></text>
</g>
<g >
<title>create_scan_plan (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="453" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="127.24" y="463.5" ></text>
</g>
<g >
<title>newNode (6,141,599,429 samples, 0.07%)</title><rect x="442.0" y="421" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="444.97" y="431.5" ></text>
</g>
<g >
<title>lappend (848,213,615 samples, 0.01%)</title><rect x="920.9" y="437" width="0.1" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="923.87" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,672,220,930 samples, 0.03%)</title><rect x="490.1" y="437" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="493.08" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (842,740,396 samples, 0.01%)</title><rect x="1078.5" y="469" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="1081.51" y="479.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (8,344,125,969 samples, 0.09%)</title><rect x="825.0" y="309" width="1.0" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="827.95" y="319.5" ></text>
</g>
<g >
<title>palloc0 (5,087,508,768 samples, 0.05%)</title><rect x="953.0" y="389" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="955.99" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (800,913,797 samples, 0.01%)</title><rect x="518.6" y="421" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="521.62" y="431.5" ></text>
</g>
<g >
<title>verify_compact_attribute (926,263,447 samples, 0.01%)</title><rect x="122.0" y="741" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="125.00" y="751.5" ></text>
</g>
<g >
<title>estimate_rel_size (29,767,137,381 samples, 0.32%)</title><rect x="934.9" y="405" width="3.7" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="937.89" y="415.5" ></text>
</g>
<g >
<title>pull_varattnos_walker (993,460,154 samples, 0.01%)</title><rect x="997.7" y="277" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="1000.70" y="287.5" ></text>
</g>
<g >
<title>__futex_wait (908,539,829 samples, 0.01%)</title><rect x="381.8" y="149" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="384.79" y="159.5" ></text>
</g>
<g >
<title>__futex_wait (851,906,660 samples, 0.01%)</title><rect x="467.7" y="341" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="470.67" y="351.5" ></text>
</g>
<g >
<title>make_oper_cache_key (3,291,616,283 samples, 0.03%)</title><rect x="838.2" y="357" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="841.22" y="367.5" ></text>
</g>
<g >
<title>deconstruct_recurse (14,954,502,213 samples, 0.16%)</title><rect x="975.9" y="437" width="1.8" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="978.88" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,240,278,128 samples, 0.01%)</title><rect x="354.0" y="245" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="357.00" y="255.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="309" width="2.3" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="102.04" y="319.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple_internal (2,651,629,702 samples, 0.03%)</title><rect x="268.5" y="277" width="0.3" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="271.46" y="287.5" ></text>
</g>
<g >
<title>_bt_moveright (1,338,761,308 samples, 0.01%)</title><rect x="123.9" y="277" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="126.91" y="287.5" ></text>
</g>
<g >
<title>pg_class_aclcheck (7,342,704,253 samples, 0.08%)</title><rect x="1034.3" y="197" width="1.0" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="1037.34" y="207.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (829,949,249 samples, 0.01%)</title><rect x="1046.0" y="389" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1049.00" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,575,505,720 samples, 0.02%)</title><rect x="349.9" y="277" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="352.87" y="287.5" ></text>
</g>
<g >
<title>palloc (1,294,312,677 samples, 0.01%)</title><rect x="910.9" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="913.92" y="447.5" ></text>
</g>
<g >
<title>grouping_planner (1,163,868,766 samples, 0.01%)</title><rect x="126.5" y="517" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="129.52" y="527.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (65,725,706,555 samples, 0.70%)</title><rect x="493.5" y="453" width="8.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="496.48" y="463.5" ></text>
</g>
<g >
<title>_bt_getroot (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="309" width="1.5" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="86.22" y="319.5" ></text>
</g>
<g >
<title>Int32GetDatum (2,658,017,377 samples, 0.03%)</title><rect x="53.2" y="757" width="0.4" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="56.24" y="767.5" ></text>
</g>
<g >
<title>palloc (988,325,388 samples, 0.01%)</title><rect x="897.4" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="900.45" y="447.5" ></text>
</g>
<g >
<title>LockBuffer (9,960,534,486 samples, 0.11%)</title><rect x="362.8" y="357" width="1.2" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="365.76" y="367.5" ></text>
</g>
<g >
<title>palloc (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="597" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1136.99" y="607.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,723,123,476 samples, 0.02%)</title><rect x="827.2" y="277" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="830.23" y="287.5" ></text>
</g>
<g >
<title>palloc (1,136,757,662 samples, 0.01%)</title><rect x="970.0" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="973.03" y="351.5" ></text>
</g>
<g >
<title>_bt_compare (37,371,334,094 samples, 0.40%)</title><rect x="330.2" y="229" width="4.7" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="333.21" y="239.5" ></text>
</g>
<g >
<title>ResourceOwnerReleaseAll (1,546,545,202 samples, 0.02%)</title><rect x="225.9" y="533" width="0.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="228.91" y="543.5" ></text>
</g>
<g >
<title>btgettreeheight (1,664,194,611 samples, 0.02%)</title><rect x="933.2" y="405" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="936.23" y="415.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,206,496,747 samples, 0.04%)</title><rect x="846.8" y="373" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="849.82" y="383.5" ></text>
</g>
<g >
<title>ResourceOwnerCreate (13,526,179,886 samples, 0.14%)</title><rect x="1075.7" y="517" width="1.7" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="1078.71" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,476,730,229 samples, 0.03%)</title><rect x="951.6" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="954.55" y="399.5" ></text>
</g>
<g >
<title>futex_wait (966,647,202 samples, 0.01%)</title><rect x="381.8" y="165" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="384.78" y="175.5" ></text>
</g>
<g >
<title>ExecPushExprSetupSteps (3,280,995,992 samples, 0.03%)</title><rect x="430.0" y="389" width="0.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="433.03" y="399.5" ></text>
</g>
<g >
<title>schedule (3,549,766,079 samples, 0.04%)</title><rect x="206.4" y="437" width="0.5" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="209.45" y="447.5" ></text>
</g>
<g >
<title>make_pathkeys_for_sortclauses (1,686,844,792 samples, 0.02%)</title><rect x="1159.6" y="757" width="0.2" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1162.57" y="767.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,600,736,131 samples, 0.02%)</title><rect x="490.6" y="373" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="493.55" y="383.5" ></text>
</g>
<g >
<title>list_make1_impl (2,319,213,702 samples, 0.02%)</title><rect x="1020.6" y="309" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1023.64" y="319.5" ></text>
</g>
<g >
<title>IndexNext (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="405" width="2.8" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="85.62" y="415.5" ></text>
</g>
<g >
<title>BufferDescriptorGetBuffer (1,041,397,737 samples, 0.01%)</title><rect x="100.7" y="165" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="103.72" y="175.5" ></text>
</g>
<g >
<title>XLogBeginInsert (1,239,691,500 samples, 0.01%)</title><rect x="377.9" y="341" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="380.94" y="351.5" ></text>
</g>
<g >
<title>printtup_create_DR (3,741,212,985 samples, 0.04%)</title><rect x="210.4" y="565" width="0.4" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="213.35" y="575.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (13,361,081,150 samples, 0.14%)</title><rect x="880.1" y="453" width="1.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="883.12" y="463.5" ></text>
</g>
<g >
<title>smgrDoPendingSyncs (1,412,207,320 samples, 0.01%)</title><rect x="526.6" y="517" width="0.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="529.55" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,340,820,401 samples, 0.01%)</title><rect x="1015.6" y="261" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1018.55" y="271.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,751,436,568 samples, 0.02%)</title><rect x="1078.2" y="501" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1081.19" y="511.5" ></text>
</g>
<g >
<title>tts_buffer_heap_getsomeattrs (3,503,971,899 samples, 0.04%)</title><rect x="268.4" y="309" width="0.4" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="271.35" y="319.5" ></text>
</g>
<g >
<title>subquery_planner (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="565" width="0.1" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="1161.36" y="575.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableXid (4,211,136,227 samples, 0.04%)</title><rect x="303.3" y="229" width="0.5" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="306.31" y="239.5" ></text>
</g>
<g >
<title>[[vdso]] (850,539,316 samples, 0.01%)</title><rect x="1072.6" y="549" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1075.56" y="559.5" ></text>
</g>
<g >
<title>table_close (3,059,349,232 samples, 0.03%)</title><rect x="865.2" y="517" width="0.4" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="868.20" y="527.5" ></text>
</g>
<g >
<title>[[vdso]] (1,148,090,673 samples, 0.01%)</title><rect x="207.7" y="565" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="210.66" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,987,046,298 samples, 0.03%)</title><rect x="918.4" y="437" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="921.39" y="447.5" ></text>
</g>
<g >
<title>add_path (5,704,025,124 samples, 0.06%)</title><rect x="988.8" y="389" width="0.7" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="991.80" y="399.5" ></text>
</g>
<g >
<title>hash_search (4,355,378,771 samples, 0.05%)</title><rect x="445.9" y="373" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="448.91" y="383.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (4,537,248,481 samples, 0.05%)</title><rect x="351.8" y="261" width="0.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="354.83" y="271.5" ></text>
</g>
<g >
<title>internal_flush_buffer (152,201,954,535 samples, 1.61%)</title><rect x="188.2" y="549" width="19.0" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="191.22" y="559.5" ></text>
</g>
<g >
<title>LockBufHdr (3,047,772,666 samples, 0.03%)</title><rect x="324.1" y="213" width="0.4" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="327.13" y="223.5" ></text>
</g>
<g >
<title>LWLockRelease (1,385,394,656 samples, 0.01%)</title><rect x="340.0" y="197" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="342.99" y="207.5" ></text>
</g>
<g >
<title>list_free (1,729,633,917 samples, 0.02%)</title><rect x="954.6" y="453" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="957.58" y="463.5" ></text>
</g>
<g >
<title>newNode (3,281,955,634 samples, 0.03%)</title><rect x="919.5" y="469" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="922.52" y="479.5" ></text>
</g>
<g >
<title>pg_atomic_read_membarrier_u64 (1,551,381,125 samples, 0.02%)</title><rect x="491.2" y="469" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="494.20" y="479.5" ></text>
</g>
<g >
<title>SearchSysCache3 (5,595,411,272 samples, 0.06%)</title><rect x="427.4" y="389" width="0.6" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="430.35" y="399.5" ></text>
</g>
<g >
<title>list_delete_nth_cell (2,680,357,056 samples, 0.03%)</title><rect x="993.8" y="357" width="0.3" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="996.80" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,100,375,785 samples, 0.01%)</title><rect x="986.3" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="989.34" y="335.5" ></text>
</g>
<g >
<title>IndexScanEnd (3,279,992,881 samples, 0.03%)</title><rect x="239.2" y="405" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="242.20" y="415.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,918,311,880 samples, 0.07%)</title><rect x="1039.4" y="341" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1042.38" y="351.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,019,232,232 samples, 0.01%)</title><rect x="325.3" y="181" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="328.35" y="191.5" ></text>
</g>
<g >
<title>palloc (1,592,931,793 samples, 0.02%)</title><rect x="964.2" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="967.18" y="351.5" ></text>
</g>
<g >
<title>select_idle_core.isra.0 (11,182,597,917 samples, 0.12%)</title><rect x="201.4" y="261" width="1.4" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="204.42" y="271.5" ></text>
</g>
<g >
<title>DataChecksumsEnabled (2,935,052,750 samples, 0.03%)</title><rect x="323.6" y="213" width="0.4" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="326.64" y="223.5" ></text>
</g>
<g >
<title>do_syscall_64 (3,689,814,070 samples, 0.04%)</title><rect x="351.9" y="213" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="354.91" y="223.5" ></text>
</g>
<g >
<title>put_prev_entity (877,931,202 samples, 0.01%)</title><rect x="159.8" y="309" width="0.1" height="15.0" fill="rgb(211,28,6)" rx="2" ry="2" />
<text  x="162.84" y="319.5" ></text>
</g>
<g >
<title>palloc0 (2,940,364,769 samples, 0.03%)</title><rect x="814.4" y="405" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="817.37" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,998,168,038 samples, 0.02%)</title><rect x="395.5" y="325" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="398.47" y="335.5" ></text>
</g>
<g >
<title>fastgetattr (1,371,052,444 samples, 0.01%)</title><rect x="1136.7" y="757" width="0.2" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="1139.72" y="767.5" ></text>
</g>
<g >
<title>sentinel_ok (2,564,052,540 samples, 0.03%)</title><rect x="256.9" y="421" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="259.88" y="431.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,676,568,176 samples, 0.04%)</title><rect x="466.2" y="485" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="469.16" y="495.5" ></text>
</g>
<g >
<title>list_make1_impl (4,890,816,803 samples, 0.05%)</title><rect x="1119.3" y="725" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1122.32" y="735.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (9,019,532,849 samples, 0.10%)</title><rect x="806.1" y="341" width="1.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="809.13" y="351.5" ></text>
</g>
<g >
<title>hash_bytes (6,558,860,655 samples, 0.07%)</title><rect x="866.5" y="405" width="0.8" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="869.49" y="415.5" ></text>
</g>
<g >
<title>main (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="725" width="2.2" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="127.59" y="735.5" ></text>
</g>
<g >
<title>do_futex (27,845,629,230 samples, 0.30%)</title><rect x="485.2" y="357" width="3.5" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="488.24" y="367.5" ></text>
</g>
<g >
<title>ExecClearTuple (2,327,258,615 samples, 0.02%)</title><rect x="282.9" y="341" width="0.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="285.91" y="351.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (872,259,354 samples, 0.01%)</title><rect x="1057.4" y="405" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1060.42" y="415.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (907,974,538 samples, 0.01%)</title><rect x="300.1" y="117" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="303.07" y="127.5" ></text>
</g>
<g >
<title>get_op_opfamily_properties (7,761,315,600 samples, 0.08%)</title><rect x="427.1" y="405" width="1.0" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="430.08" y="415.5" ></text>
</g>
<g >
<title>bms_copy (2,381,169,399 samples, 0.03%)</title><rect x="451.9" y="485" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="454.88" y="495.5" ></text>
</g>
<g >
<title>__update_load_avg_cfs_rq (984,135,320 samples, 0.01%)</title><rect x="481.2" y="197" width="0.1" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="484.21" y="207.5" ></text>
</g>
<g >
<title>query_planner (961,833,405,145 samples, 10.19%)</title><rect x="925.3" y="485" width="120.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="928.25" y="495.5" >query_planner</text>
</g>
<g >
<title>PageGetItemId (1,071,970,479 samples, 0.01%)</title><rect x="310.9" y="229" width="0.1" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="313.86" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (856,571,127 samples, 0.01%)</title><rect x="970.4" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="973.44" y="335.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (3,378,654,146 samples, 0.04%)</title><rect x="98.6" y="181" width="0.4" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="101.62" y="191.5" ></text>
</g>
<g >
<title>contain_volatile_functions (1,222,656,770 samples, 0.01%)</title><rect x="1020.4" y="309" width="0.2" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1023.44" y="319.5" ></text>
</g>
<g >
<title>LockRelationOid (1,075,123,651 samples, 0.01%)</title><rect x="58.1" y="757" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="61.15" y="767.5" ></text>
</g>
<g >
<title>clauselist_selectivity_ext (1,530,807,497 samples, 0.02%)</title><rect x="1012.5" y="277" width="0.2" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="1015.47" y="287.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="645" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1187.99" y="655.5" ></text>
</g>
<g >
<title>build_simple_rel (189,316,362,518 samples, 2.01%)</title><rect x="926.6" y="437" width="23.7" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="929.61" y="447.5" >b..</text>
</g>
<g >
<title>index_close (1,950,748,231 samples, 0.02%)</title><rect x="939.9" y="405" width="0.2" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="942.86" y="415.5" ></text>
</g>
<g >
<title>hash_bytes (10,860,989,399 samples, 0.12%)</title><rect x="836.9" y="309" width="1.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="839.86" y="319.5" ></text>
</g>
<g >
<title>bms_del_member (2,334,263,620 samples, 0.02%)</title><rect x="877.7" y="485" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="880.72" y="495.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="597" width="6.7" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="97.67" y="607.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (1,623,008,936 samples, 0.02%)</title><rect x="1138.6" y="757" width="0.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="1141.58" y="767.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,585,398,720 samples, 0.02%)</title><rect x="454.2" y="501" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="457.24" y="511.5" ></text>
</g>
<g >
<title>MakeTupleTableSlot (6,824,826,572 samples, 0.07%)</title><rect x="438.5" y="389" width="0.8" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="441.48" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,116,783,469 samples, 0.01%)</title><rect x="451.7" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="454.72" y="463.5" ></text>
</g>
<g >
<title>lcons (1,858,215,054 samples, 0.02%)</title><rect x="269.7" y="373" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="272.67" y="383.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,190,560,038 samples, 0.01%)</title><rect x="1066.8" y="437" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1069.80" y="447.5" ></text>
</g>
<g >
<title>pgstat_report_query_id (1,708,106,463 samples, 0.02%)</title><rect x="1073.0" y="581" width="0.3" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="1076.05" y="591.5" ></text>
</g>
<g >
<title>planstate_tree_walker_impl (1,863,503,745 samples, 0.02%)</title><rect x="1172.1" y="757" width="0.2" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="1175.07" y="767.5" ></text>
</g>
<g >
<title>hash_bytes (2,465,836,987 samples, 0.03%)</title><rect x="394.7" y="293" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="397.70" y="303.5" ></text>
</g>
<g >
<title>table_close (1,078,599,306 samples, 0.01%)</title><rect x="1185.7" y="757" width="0.1" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="1188.69" y="767.5" ></text>
</g>
<g >
<title>hash_search (2,680,434,915 samples, 0.03%)</title><rect x="1016.6" y="293" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1019.63" y="303.5" ></text>
</g>
<g >
<title>kfree (8,441,838,636 samples, 0.09%)</title><rect x="179.0" y="341" width="1.1" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="182.00" y="351.5" ></text>
</g>
<g >
<title>expr_setup_walker (10,809,319,063 samples, 0.11%)</title><rect x="417.4" y="341" width="1.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="420.40" y="351.5" ></text>
</g>
<g >
<title>security_socket_getpeersec_dgram (3,575,334,822 samples, 0.04%)</title><rect x="192.4" y="405" width="0.5" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="195.41" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,428,737,271 samples, 0.02%)</title><rect x="100.4" y="149" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="103.37" y="159.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,674,839,267 samples, 0.02%)</title><rect x="490.6" y="389" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="493.55" y="399.5" ></text>
</g>
<g >
<title>_int_free_merge_chunk (1,168,402,540 samples, 0.01%)</title><rect x="261.4" y="389" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="264.41" y="399.5" ></text>
</g>
<g >
<title>AtStart_Memory (1,393,747,281 samples, 0.01%)</title><rect x="1075.4" y="533" width="0.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1078.44" y="543.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,015,926,600 samples, 0.01%)</title><rect x="427.6" y="341" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="430.60" y="351.5" ></text>
</g>
<g >
<title>AllocSetReset (7,311,524,652 samples, 0.08%)</title><rect x="459.7" y="469" width="0.9" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="462.73" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (936,733,870 samples, 0.01%)</title><rect x="104.4" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="107.44" y="399.5" ></text>
</g>
<g >
<title>transformExprRecurse (32,455,041,936 samples, 0.34%)</title><rect x="852.8" y="421" width="4.0" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="855.79" y="431.5" ></text>
</g>
<g >
<title>clamp_row_est (1,148,722,465 samples, 0.01%)</title><rect x="1028.4" y="389" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1031.40" y="399.5" ></text>
</g>
<g >
<title>CopyIndexAttOptions (2,059,321,917 samples, 0.02%)</title><rect x="930.6" y="389" width="0.3" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="933.60" y="399.5" ></text>
</g>
<g >
<title>ForEachLWLockHeldByMe (1,290,984,487 samples, 0.01%)</title><rect x="47.2" y="757" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="50.23" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,400,659,554 samples, 0.01%)</title><rect x="1013.4" y="245" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1016.40" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (916,665,754 samples, 0.01%)</title><rect x="1017.7" y="309" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1020.66" y="319.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (4,687,558,936 samples, 0.05%)</title><rect x="956.3" y="325" width="0.6" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="959.28" y="335.5" ></text>
</g>
<g >
<title>PortalDrop (38,938,871,355 samples, 0.41%)</title><rect x="222.6" y="581" width="4.9" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="225.61" y="591.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,210,716,317 samples, 0.01%)</title><rect x="381.6" y="261" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="384.58" y="271.5" ></text>
</g>
<g >
<title>BuildQueryCompletionString (8,492,612,849 samples, 0.09%)</title><rect x="215.3" y="565" width="1.1" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="218.32" y="575.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (963,191,835 samples, 0.01%)</title><rect x="339.4" y="197" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="342.38" y="207.5" ></text>
</g>
<g >
<title>psi_account_irqtime (5,863,374,427 samples, 0.06%)</title><rect x="163.5" y="341" width="0.8" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="166.54" y="351.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberBuffer (960,156,260 samples, 0.01%)</title><rect x="401.0" y="261" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="404.03" y="271.5" ></text>
</g>
<g >
<title>fix_indexqual_references (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="453" width="0.1" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="88.40" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_add_u64_impl (1,253,075,728 samples, 0.01%)</title><rect x="491.2" y="437" width="0.2" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="494.24" y="447.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,072,609,482 samples, 0.02%)</title><rect x="56.1" y="757" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="59.07" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,420,520,067 samples, 0.02%)</title><rect x="860.1" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="863.05" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,889,814,044 samples, 0.02%)</title><rect x="430.8" y="325" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="433.77" y="335.5" ></text>
</g>
<g >
<title>cost_qual_eval_node (9,796,172,573 samples, 0.10%)</title><rect x="1045.8" y="469" width="1.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1048.78" y="479.5" ></text>
</g>
<g >
<title>_bt_first (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="309" width="1.7" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="125.45" y="319.5" ></text>
</g>
<g >
<title>new_list (1,692,220,231 samples, 0.02%)</title><rect x="1071.7" y="469" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1074.71" y="479.5" ></text>
</g>
<g >
<title>exprType (1,563,540,087 samples, 0.02%)</title><rect x="1035.5" y="229" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1038.51" y="239.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,345,134,265 samples, 0.06%)</title><rect x="102.9" y="453" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="105.88" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,995,939,757 samples, 0.02%)</title><rect x="470.0" y="421" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="472.97" y="431.5" ></text>
</g>
<g >
<title>transformUpdateStmt (446,762,155,623 samples, 4.73%)</title><rect x="801.0" y="501" width="55.9" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="804.00" y="511.5" >trans..</text>
</g>
<g >
<title>ExecInitModifyTable (1,683,100,516 samples, 0.02%)</title><rect x="43.5" y="757" width="0.2" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="46.52" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,311,383,769 samples, 0.01%)</title><rect x="848.0" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="850.97" y="383.5" ></text>
</g>
<g >
<title>ExecScanExtended (476,065,121,368 samples, 5.04%)</title><rect x="282.5" y="373" width="59.5" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="285.52" y="383.5" >ExecSc..</text>
</g>
<g >
<title>hash_search (8,398,575,009 samples, 0.09%)</title><rect x="211.6" y="549" width="1.0" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="214.58" y="559.5" ></text>
</g>
<g >
<title>ReleaseSysCache (2,258,987,847 samples, 0.02%)</title><rect x="822.8" y="357" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="825.79" y="367.5" ></text>
</g>
<g >
<title>var_eq_const (3,045,953,760 samples, 0.03%)</title><rect x="1036.6" y="261" width="0.3" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="1039.56" y="271.5" ></text>
</g>
<g >
<title>SearchSysCache1 (2,884,395,725 samples, 0.03%)</title><rect x="94.0" y="757" width="0.3" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="96.96" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,269,954,888 samples, 0.01%)</title><rect x="1068.8" y="421" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1071.82" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (850,182,298 samples, 0.01%)</title><rect x="466.3" y="453" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="469.26" y="463.5" ></text>
</g>
<g >
<title>palloc0 (2,081,516,487 samples, 0.02%)</title><rect x="835.7" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="838.72" y="367.5" ></text>
</g>
<g >
<title>fix_indexqual_operand (5,300,462,761 samples, 0.06%)</title><rect x="888.0" y="357" width="0.6" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="890.96" y="367.5" ></text>
</g>
<g >
<title>pg_plan_queries (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="629" width="0.1" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="1161.36" y="639.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (970,511,808 samples, 0.01%)</title><rect x="364.8" y="341" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="367.81" y="351.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="437" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1161.36" y="447.5" ></text>
</g>
<g >
<title>palloc (915,626,998 samples, 0.01%)</title><rect x="835.5" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="838.51" y="351.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,601,740,751 samples, 0.04%)</title><rect x="1038.9" y="293" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1041.92" y="303.5" ></text>
</g>
<g >
<title>index_close (2,047,197,195 samples, 0.02%)</title><rect x="238.7" y="421" width="0.3" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="241.71" y="431.5" ></text>
</g>
<g >
<title>XactLogCommitRecord (28,080,547,488 samples, 0.30%)</title><rect x="505.9" y="501" width="3.5" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="508.94" y="511.5" ></text>
</g>
<g >
<title>pgstat_get_xact_stack_level (3,812,175,367 samples, 0.04%)</title><rect x="387.9" y="309" width="0.4" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="390.86" y="319.5" ></text>
</g>
<g >
<title>BufTableHashCode (2,739,684,696 samples, 0.03%)</title><rect x="367.0" y="245" width="0.4" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="370.04" y="255.5" ></text>
</g>
<g >
<title>MemoryContextCheck (2,167,076,535,946 samples, 22.96%)</title><rect x="526.9" y="565" width="271.0" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="529.93" y="575.5" >MemoryContextCheck</text>
</g>
<g >
<title>tag_hash (2,614,930,713 samples, 0.03%)</title><rect x="298.1" y="101" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="301.07" y="111.5" ></text>
</g>
<g >
<title>new_list (2,032,057,681 samples, 0.02%)</title><rect x="890.0" y="261" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="892.98" y="271.5" ></text>
</g>
<g >
<title>enforce_generic_type_consistency (6,165,073,777 samples, 0.07%)</title><rect x="845.3" y="405" width="0.8" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="848.28" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (4,436,750,604 samples, 0.05%)</title><rect x="821.1" y="357" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="824.07" y="367.5" ></text>
</g>
<g >
<title>list_copy (2,830,383,190 samples, 0.03%)</title><rect x="977.9" y="421" width="0.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="980.88" y="431.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,035,197,623 samples, 0.01%)</title><rect x="1147.6" y="533" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="1150.60" y="543.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (2,585,677,031 samples, 0.03%)</title><rect x="960.6" y="261" width="0.3" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="963.55" y="271.5" ></text>
</g>
<g >
<title>_raw_spin_lock (2,104,757,967 samples, 0.02%)</title><rect x="192.1" y="405" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="195.13" y="415.5" ></text>
</g>
<g >
<title>palloc0 (2,164,085,012 samples, 0.02%)</title><rect x="833.8" y="405" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="836.78" y="415.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (1,444,744,149 samples, 0.02%)</title><rect x="354.7" y="229" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="357.65" y="239.5" ></text>
</g>
<g >
<title>_bt_checkpage (2,983,733,097 samples, 0.03%)</title><rect x="338.9" y="229" width="0.4" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="341.91" y="239.5" ></text>
</g>
<g >
<title>ttwu_do_activate (10,751,743,106 samples, 0.11%)</title><rect x="203.7" y="325" width="1.4" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="206.73" y="335.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (1,100,385,657 samples, 0.01%)</title><rect x="125.9" y="181" width="0.1" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="128.85" y="191.5" ></text>
</g>
<g >
<title>_bt_search (17,384,689,419 samples, 0.18%)</title><rect x="83.2" y="325" width="2.2" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="86.22" y="335.5" ></text>
</g>
<g >
<title>recomputeNamespacePath (1,143,544,274 samples, 0.01%)</title><rect x="1175.1" y="757" width="0.2" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="1178.11" y="767.5" ></text>
</g>
<g >
<title>PostgresMain (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="661" width="0.6" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="1160.94" y="671.5" ></text>
</g>
<g >
<title>AllocSetFree (2,123,615,478 samples, 0.02%)</title><rect x="510.4" y="485" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="513.38" y="495.5" ></text>
</g>
<g >
<title>exec_simple_query (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="629" width="2.1" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="125.45" y="639.5" ></text>
</g>
<g >
<title>ReadBuffer (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="293" width="4.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="97.67" y="303.5" ></text>
</g>
<g >
<title>__x64_sys_recvfrom (64,335,606,086 samples, 0.68%)</title><rect x="176.3" y="453" width="8.0" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="179.30" y="463.5" ></text>
</g>
<g >
<title>wake_up_q (1,397,850,454 samples, 0.01%)</title><rect x="382.2" y="117" width="0.2" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="385.24" y="127.5" ></text>
</g>
<g >
<title>__memcg_slab_free_hook (4,560,315,636 samples, 0.05%)</title><rect x="179.2" y="325" width="0.5" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="182.17" y="335.5" ></text>
</g>
<g >
<title>__GI___strlcpy (867,194,032 samples, 0.01%)</title><rect x="214.5" y="533" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="217.50" y="543.5" ></text>
</g>
<g >
<title>index_open (1,651,370,432 samples, 0.02%)</title><rect x="1150.5" y="757" width="0.2" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="1153.50" y="767.5" ></text>
</g>
<g >
<title>x64_sys_call (953,800,130 samples, 0.01%)</title><rect x="206.9" y="453" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="209.89" y="463.5" ></text>
</g>
<g >
<title>PageGetItem (1,702,281,161 samples, 0.02%)</title><rect x="332.1" y="213" width="0.2" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="335.06" y="223.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,383,762,429 samples, 0.03%)</title><rect x="414.7" y="373" width="0.3" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="417.71" y="383.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (857,285,167 samples, 0.01%)</title><rect x="94.7" y="197" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="97.67" y="207.5" ></text>
</g>
<g >
<title>bms_union (2,912,437,966 samples, 0.03%)</title><rect x="967.1" y="373" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="970.10" y="383.5" ></text>
</g>
<g >
<title>index_getattr (1,338,761,308 samples, 0.01%)</title><rect x="123.9" y="245" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="126.91" y="255.5" ></text>
</g>
<g >
<title>_equalList (1,404,439,808 samples, 0.01%)</title><rect x="913.2" y="469" width="0.2" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="916.20" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,111,658,522 samples, 0.01%)</title><rect x="375.0" y="309" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="378.00" y="319.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (4,736,985,633 samples, 0.05%)</title><rect x="212.6" y="565" width="0.6" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="215.63" y="575.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,191,066,282 samples, 0.01%)</title><rect x="1083.5" y="565" width="0.2" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1086.53" y="575.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (3,922,983,179 samples, 0.04%)</title><rect x="420.2" y="309" width="0.5" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="423.22" y="319.5" ></text>
</g>
<g >
<title>pg_comp_crc32c_sse42 (1,227,892,045 samples, 0.01%)</title><rect x="509.1" y="453" width="0.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="512.12" y="463.5" ></text>
</g>
<g >
<title>IncrTupleDescRefCount (1,761,002,776 samples, 0.02%)</title><rect x="276.2" y="373" width="0.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="279.24" y="383.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,614,756,229 samples, 0.04%)</title><rect x="836.0" y="341" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="839.04" y="351.5" ></text>
</g>
<g >
<title>SimpleLruReadPage_ReadOnly (6,302,699,396 samples, 0.07%)</title><rect x="307.1" y="165" width="0.8" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="310.08" y="175.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (893,061,517 samples, 0.01%)</title><rect x="93.5" y="741" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="96.45" y="751.5" ></text>
</g>
<g >
<title>lcons (980,634,836 samples, 0.01%)</title><rect x="1154.0" y="757" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1157.04" y="767.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (1,297,883,686 samples, 0.01%)</title><rect x="351.2" y="85" width="0.2" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="354.21" y="95.5" ></text>
</g>
<g >
<title>make_indexscan (5,843,998,432 samples, 0.06%)</title><rect x="891.3" y="389" width="0.7" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="894.30" y="399.5" ></text>
</g>
<g >
<title>PinBuffer (4,461,308,387 samples, 0.05%)</title><rect x="100.7" y="181" width="0.5" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="103.66" y="191.5" ></text>
</g>
<g >
<title>futex_wait (940,728,870 samples, 0.01%)</title><rect x="363.4" y="213" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="366.40" y="223.5" ></text>
</g>
<g >
<title>[unknown] (1,744,030,300 samples, 0.02%)</title><rect x="174.8" y="501" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="177.77" y="511.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (19,716,156,553 samples, 0.21%)</title><rect x="337.7" y="245" width="2.5" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="340.71" y="255.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (911,385,890 samples, 0.01%)</title><rect x="386.6" y="309" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="389.62" y="319.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (1,141,927,528 samples, 0.01%)</title><rect x="1072.5" y="565" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1075.54" y="575.5" ></text>
</g>
<g >
<title>LWLockAcquire (5,154,291,814 samples, 0.05%)</title><rect x="231.8" y="517" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="234.79" y="527.5" ></text>
</g>
<g >
<title>bms_is_subset (942,335,630 samples, 0.01%)</title><rect x="1122.4" y="757" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="1125.44" y="767.5" ></text>
</g>
<g >
<title>index_getattr (15,447,927,373 samples, 0.16%)</title><rect x="319.6" y="229" width="1.9" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="322.57" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,710,668,874 samples, 0.03%)</title><rect x="1165.3" y="741" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1168.32" y="751.5" ></text>
</g>
<g >
<title>BufTableHashCode (2,912,430,237 samples, 0.03%)</title><rect x="399.8" y="277" width="0.3" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="402.76" y="287.5" ></text>
</g>
<g >
<title>uint32_hash (1,394,273,079 samples, 0.01%)</title><rect x="830.8" y="373" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="833.80" y="383.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (1,118,585,686,514 samples, 11.85%)</title><rect x="263.3" y="517" width="139.9" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="266.34" y="527.5" >standard_Executor..</text>
</g>
<g >
<title>MemoryContextAlloc (1,515,949,906 samples, 0.02%)</title><rect x="1061.9" y="453" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="1064.88" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,288,349,899 samples, 0.02%)</title><rect x="1018.3" y="309" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1021.27" y="319.5" ></text>
</g>
<g >
<title>pgstat_clear_snapshot (3,143,943,269 samples, 0.03%)</title><rect x="463.1" y="501" width="0.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="466.06" y="511.5" ></text>
</g>
<g >
<title>pairingheap_add (915,611,902 samples, 0.01%)</title><rect x="234.3" y="485" width="0.1" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="237.33" y="495.5" ></text>
</g>
<g >
<title>__strlen_avx2 (939,077,941 samples, 0.01%)</title><rect x="1083.2" y="581" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1086.17" y="591.5" ></text>
</g>
<g >
<title>new_list (1,974,289,059 samples, 0.02%)</title><rect x="1013.1" y="261" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1016.05" y="271.5" ></text>
</g>
<g >
<title>fmgr_isbuiltin (2,110,556,920 samples, 0.02%)</title><rect x="420.4" y="293" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="423.44" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,457,591,375 samples, 0.03%)</title><rect x="890.7" y="325" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="893.75" y="335.5" ></text>
</g>
<g >
<title>uint32_hash (1,076,239,091 samples, 0.01%)</title><rect x="1188.9" y="757" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1191.88" y="767.5" ></text>
</g>
<g >
<title>preprocess_expression (886,882,643 samples, 0.01%)</title><rect x="124.5" y="517" width="0.1" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="127.48" y="527.5" ></text>
</g>
<g >
<title>pq_getbyte (282,007,372,295 samples, 2.99%)</title><rect x="149.6" y="565" width="35.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="152.60" y="575.5" >pq..</text>
</g>
<g >
<title>AllocSetReset (119,472,898,641 samples, 1.27%)</title><rect x="133.9" y="565" width="15.0" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="136.94" y="575.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="229" width="2.3" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="102.04" y="239.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (28,925,189,547 samples, 0.31%)</title><rect x="485.2" y="405" width="3.6" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="488.18" y="415.5" ></text>
</g>
<g >
<title>LockRelease (10,840,555,824 samples, 0.11%)</title><rect x="236.4" y="421" width="1.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="239.40" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,461,592,818 samples, 0.02%)</title><rect x="518.0" y="405" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="520.97" y="415.5" ></text>
</g>
<g >
<title>int2hashfast (843,400,675 samples, 0.01%)</title><rect x="1033.8" y="149" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1036.76" y="159.5" ></text>
</g>
<g >
<title>noop_dirty_folio (808,015,570 samples, 0.01%)</title><rect x="501.0" y="341" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="504.01" y="351.5" ></text>
</g>
<g >
<title>lappend (1,414,302,413 samples, 0.01%)</title><rect x="278.3" y="405" width="0.1" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="281.26" y="415.5" ></text>
</g>
<g >
<title>LockHeldByMe (10,394,066,677 samples, 0.11%)</title><rect x="947.5" y="357" width="1.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="950.52" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,754,767,386 samples, 0.05%)</title><rect x="407.4" y="421" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="410.43" y="431.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,034,311,388 samples, 0.01%)</title><rect x="939.0" y="357" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="941.99" y="367.5" ></text>
</g>
<g >
<title>_raw_spin_lock (1,552,783,294 samples, 0.02%)</title><rect x="178.7" y="373" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="181.65" y="383.5" ></text>
</g>
<g >
<title>palloc0 (4,587,429,299 samples, 0.05%)</title><rect x="1120.5" y="709" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1123.47" y="719.5" ></text>
</g>
<g >
<title>_bt_fix_scankey_strategy (1,262,015,434 samples, 0.01%)</title><rect x="322.6" y="245" width="0.2" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="325.61" y="255.5" ></text>
</g>
<g >
<title>get_typcollation (5,758,015,249 samples, 0.06%)</title><rect x="803.5" y="373" width="0.7" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="806.49" y="383.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,624,955,694 samples, 0.03%)</title><rect x="998.1" y="261" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1001.05" y="271.5" ></text>
</g>
<g >
<title>pstrdup (4,019,711,436 samples, 0.04%)</title><rect x="814.7" y="437" width="0.5" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="817.75" y="447.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (12,547,369,533 samples, 0.13%)</title><rect x="141.9" y="533" width="1.5" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="144.87" y="543.5" ></text>
</g>
<g >
<title>BufTableLookup (1,965,648,619 samples, 0.02%)</title><rect x="367.4" y="245" width="0.2" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="370.38" y="255.5" ></text>
</g>
<g >
<title>BTreeTupleIsPosting (1,165,727,160 samples, 0.01%)</title><rect x="33.6" y="757" width="0.2" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="36.61" y="767.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (905,502,345 samples, 0.01%)</title><rect x="1123.5" y="741" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="1126.54" y="751.5" ></text>
</g>
<g >
<title>PinBufferForBlock (23,767,394,231 samples, 0.25%)</title><rect x="297.8" y="165" width="3.0" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="300.83" y="175.5" ></text>
</g>
<g >
<title>bms_equal (1,303,276,072 samples, 0.01%)</title><rect x="957.7" y="453" width="0.2" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="960.70" y="463.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (2,612,856,083 samples, 0.03%)</title><rect x="868.7" y="437" width="0.4" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="871.74" y="447.5" ></text>
</g>
<g >
<title>new_list (3,010,854,653 samples, 0.03%)</title><rect x="810.0" y="437" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="813.04" y="447.5" ></text>
</g>
<g >
<title>_int_free_merge_chunk (3,561,750,487 samples, 0.04%)</title><rect x="147.5" y="533" width="0.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="150.45" y="543.5" ></text>
</g>
<g >
<title>PostmasterMain (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="725" width="2.1" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="125.45" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (3,017,083,035 samples, 0.03%)</title><rect x="953.2" y="373" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="956.24" y="383.5" ></text>
</g>
<g >
<title>create_projection_plan (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="517" width="0.1" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="88.40" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,043,775,765 samples, 0.01%)</title><rect x="933.9" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="936.86" y="351.5" ></text>
</g>
<g >
<title>HeapTupleHeaderGetXmin (4,745,048,062 samples, 0.05%)</title><rect x="302.5" y="245" width="0.6" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="305.55" y="255.5" ></text>
</g>
<g >
<title>markVarForSelectPriv (2,090,735,871 samples, 0.02%)</title><rect x="840.3" y="325" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="843.28" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,199,942,234 samples, 0.01%)</title><rect x="1135.3" y="757" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1138.31" y="767.5" ></text>
</g>
<g >
<title>__x64_sys_futex (28,105,731,095 samples, 0.30%)</title><rect x="485.2" y="373" width="3.5" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="488.21" y="383.5" ></text>
</g>
<g >
<title>tas (2,550,364,376 samples, 0.03%)</title><rect x="515.1" y="421" width="0.3" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="518.12" y="431.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,243,776,084 samples, 0.03%)</title><rect x="125.6" y="213" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="128.59" y="223.5" ></text>
</g>
<g >
<title>ProcessQuery (1,761,991,283,524 samples, 18.67%)</title><rect x="233.2" y="549" width="220.4" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="236.25" y="559.5" >ProcessQuery</text>
</g>
<g >
<title>make_const (3,443,711,668 samples, 0.04%)</title><rect x="838.8" y="373" width="0.4" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="841.81" y="383.5" ></text>
</g>
<g >
<title>PostgresMain (955,971,428 samples, 0.01%)</title><rect x="82.5" y="757" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="85.50" y="767.5" ></text>
</g>
<g >
<title>LWLockQueueSelf (3,032,544,077 samples, 0.03%)</title><rect x="474.5" y="469" width="0.4" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="477.51" y="479.5" ></text>
</g>
<g >
<title>RelationBuildPublicationDesc (1,188,371,041 samples, 0.01%)</title><rect x="88.3" y="757" width="0.2" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="91.32" y="767.5" ></text>
</g>
<g >
<title>new_list (1,338,755,933 samples, 0.01%)</title><rect x="1018.7" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1021.68" y="367.5" ></text>
</g>
<g >
<title>__memcg_slab_post_alloc_hook (7,462,625,029 samples, 0.08%)</title><rect x="196.5" y="341" width="1.0" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="199.54" y="351.5" ></text>
</g>
<g >
<title>new_list (1,867,466,416 samples, 0.02%)</title><rect x="922.5" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="925.54" y="447.5" ></text>
</g>
<g >
<title>examine_variable (37,881,588,981 samples, 0.40%)</title><rect x="1031.6" y="245" width="4.8" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="1034.64" y="255.5" ></text>
</g>
<g >
<title>oper (21,202,343,544 samples, 0.22%)</title><rect x="836.0" y="373" width="2.6" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="838.98" y="383.5" ></text>
</g>
<g >
<title>socket_flush (153,748,549,775 samples, 1.63%)</title><rect x="188.1" y="581" width="19.2" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="191.11" y="591.5" ></text>
</g>
<g >
<title>eqsel (1,640,839,172 samples, 0.02%)</title><rect x="1133.5" y="757" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1136.53" y="767.5" ></text>
</g>
<g >
<title>security_socket_recvmsg (5,086,780,310 samples, 0.05%)</title><rect x="177.3" y="405" width="0.6" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="180.25" y="415.5" ></text>
</g>
<g >
<title>newNode (2,514,912,872 samples, 0.03%)</title><rect x="847.8" y="405" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="850.82" y="415.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (2,012,395,515 samples, 0.02%)</title><rect x="822.8" y="325" width="0.3" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="825.82" y="335.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (4,784,563,854 samples, 0.05%)</title><rect x="124.6" y="277" width="0.6" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="127.59" y="287.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (951,266,807 samples, 0.01%)</title><rect x="799.3" y="485" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="802.29" y="495.5" ></text>
</g>
<g >
<title>ExecScanFetch (954,083,181 samples, 0.01%)</title><rect x="342.0" y="373" width="0.2" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="345.05" y="383.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="325" width="2.3" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="102.04" y="335.5" ></text>
</g>
<g >
<title>bsearch (11,925,768,231 samples, 0.13%)</title><rect x="284.4" y="261" width="1.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="287.35" y="271.5" ></text>
</g>
<g >
<title>_mdnblocks (8,379,072,786 samples, 0.09%)</title><rect x="932.1" y="357" width="1.1" height="15.0" fill="rgb(217,58,14)" rx="2" ry="2" />
<text  x="935.12" y="367.5" ></text>
</g>
<g >
<title>ReadBufferExtended (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="261" width="1.5" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="86.22" y="271.5" ></text>
</g>
<g >
<title>markVarForSelectPriv (6,001,477,509 samples, 0.06%)</title><rect x="855.4" y="357" width="0.7" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="858.37" y="367.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,329,600,988 samples, 0.01%)</title><rect x="1147.6" y="597" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1150.58" y="607.5" ></text>
</g>
<g >
<title>add_row_identity_columns (13,479,153,927 samples, 0.14%)</title><rect x="920.6" y="469" width="1.7" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="923.60" y="479.5" ></text>
</g>
<g >
<title>table_openrv_extended (109,699,979,041 samples, 1.16%)</title><rect x="817.3" y="453" width="13.8" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="820.34" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,110,589,920 samples, 0.01%)</title><rect x="881.6" y="341" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="884.65" y="351.5" ></text>
</g>
<g >
<title>bms_add_member (2,455,004,040 samples, 0.03%)</title><rect x="1059.7" y="469" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1062.67" y="479.5" ></text>
</g>
<g >
<title>create_modifytable_plan (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="517" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="127.24" y="527.5" ></text>
</g>
<g >
<title>do_futex (1,792,791,505 samples, 0.02%)</title><rect x="382.2" y="149" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="385.19" y="159.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (799,899,706 samples, 0.01%)</title><rect x="110.7" y="741" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="113.69" y="751.5" ></text>
</g>
<g >
<title>_bt_checkpage (4,355,496,815 samples, 0.05%)</title><rect x="335.7" y="213" width="0.5" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="338.67" y="223.5" ></text>
</g>
<g >
<title>transformExpr (54,217,444,891 samples, 0.57%)</title><rect x="834.1" y="437" width="6.7" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="837.06" y="447.5" ></text>
</g>
<g >
<title>bms_add_member (4,370,609,951 samples, 0.05%)</title><rect x="976.0" y="421" width="0.6" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="979.04" y="431.5" ></text>
</g>
<g >
<title>ExecCheckOneRelPerms (10,962,028,603 samples, 0.12%)</title><rect x="406.7" y="469" width="1.4" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="409.69" y="479.5" ></text>
</g>
<g >
<title>add_base_rels_to_query (191,800,894,697 samples, 2.03%)</title><rect x="926.3" y="469" width="24.0" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="929.31" y="479.5" >a..</text>
</g>
<g >
<title>XLogBytePosToRecPtr (1,197,859,909 samples, 0.01%)</title><rect x="380.6" y="293" width="0.2" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="383.63" y="303.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,515,433,987 samples, 0.05%)</title><rect x="803.6" y="341" width="0.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="806.65" y="351.5" ></text>
</g>
<g >
<title>newNode (3,480,104,745 samples, 0.04%)</title><rect x="1116.7" y="709" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1119.65" y="719.5" ></text>
</g>
<g >
<title>AllocSetAlloc (823,654,814 samples, 0.01%)</title><rect x="970.1" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="973.07" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,072,480,914 samples, 0.01%)</title><rect x="276.3" y="341" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="279.32" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,100,437,583 samples, 0.01%)</title><rect x="882.1" y="469" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="885.07" y="479.5" ></text>
</g>
<g >
<title>heap_prune_chain (5,090,719,240 samples, 0.05%)</title><rect x="1145.8" y="725" width="0.6" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1148.80" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (13,334,878,159 samples, 0.14%)</title><rect x="291.4" y="261" width="1.6" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="294.38" y="271.5" ></text>
</g>
<g >
<title>ttwu_queue_wakelist (3,396,767,320 samples, 0.04%)</title><rect x="205.1" y="325" width="0.4" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="208.08" y="335.5" ></text>
</g>
<g >
<title>AllocSetFree (1,160,705,047 samples, 0.01%)</title><rect x="1001.6" y="261" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1004.57" y="271.5" ></text>
</g>
<g >
<title>transformTargetList (63,503,310,725 samples, 0.67%)</title><rect x="832.9" y="469" width="7.9" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="835.90" y="479.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (840,831,470 samples, 0.01%)</title><rect x="341.6" y="293" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="344.59" y="303.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,078,494,568 samples, 0.02%)</title><rect x="362.3" y="325" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="365.31" y="335.5" ></text>
</g>
<g >
<title>list_make1_impl (2,446,408,950 samples, 0.03%)</title><rect x="994.2" y="341" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="997.15" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (3,500,020,984 samples, 0.04%)</title><rect x="369.8" y="309" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="372.77" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (963,590,620 samples, 0.01%)</title><rect x="890.1" y="229" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="893.11" y="239.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,097,949,517 samples, 0.04%)</title><rect x="964.8" y="357" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="967.83" y="367.5" ></text>
</g>
<g >
<title>bms_add_members (2,241,343,566 samples, 0.02%)</title><rect x="975.6" y="437" width="0.3" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="978.60" y="447.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (2,079,231,909 samples, 0.02%)</title><rect x="83.0" y="245" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="85.96" y="255.5" ></text>
</g>
<g >
<title>_bt_first (1,000,886,773 samples, 0.01%)</title><rect x="128.2" y="757" width="0.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="131.18" y="767.5" ></text>
</g>
<g >
<title>table_close (1,764,884,996 samples, 0.02%)</title><rect x="860.8" y="517" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="863.79" y="527.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,534,523,026 samples, 0.03%)</title><rect x="1010.3" y="229" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1013.25" y="239.5" ></text>
</g>
<g >
<title>restriction_is_always_false (1,074,553,496 samples, 0.01%)</title><rect x="981.9" y="405" width="0.1" height="15.0" fill="rgb(238,151,36)" rx="2" ry="2" />
<text  x="984.88" y="415.5" ></text>
</g>
<g >
<title>tag_hash (2,817,887,288 samples, 0.03%)</title><rect x="518.8" y="421" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="521.79" y="431.5" ></text>
</g>
<g >
<title>lappend (2,225,948,189 samples, 0.02%)</title><rect x="833.3" y="453" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="836.27" y="463.5" ></text>
</g>
<g >
<title>AllocSetReset (3,978,513,415 samples, 0.04%)</title><rect x="223.4" y="501" width="0.5" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="226.45" y="511.5" ></text>
</g>
<g >
<title>try_grab_folio_fast (1,316,078,721 samples, 0.01%)</title><rect x="483.2" y="229" width="0.2" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="486.25" y="239.5" ></text>
</g>
<g >
<title>newNode (4,449,532,778 samples, 0.05%)</title><rect x="944.8" y="405" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="947.78" y="415.5" ></text>
</g>
<g >
<title>set_grouped_rel_pathlist (1,063,626,410 samples, 0.01%)</title><rect x="1181.4" y="757" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1184.41" y="767.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (2,836,526,667 samples, 0.03%)</title><rect x="901.7" y="373" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="904.72" y="383.5" ></text>
</g>
<g >
<title>get_relation_statistics (2,467,205,671 samples, 0.03%)</title><rect x="939.2" y="405" width="0.3" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="942.21" y="415.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="533" width="3.4" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="104.45" y="543.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,452,444,236 samples, 0.02%)</title><rect x="953.4" y="357" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="956.37" y="367.5" ></text>
</g>
<g >
<title>XLogInsert (835,729,793 samples, 0.01%)</title><rect x="1145.7" y="725" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="1148.66" y="735.5" ></text>
</g>
<g >
<title>ExecFindJunkAttributeInTlist (4,696,656,430 samples, 0.05%)</title><rect x="412.9" y="453" width="0.6" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="415.93" y="463.5" ></text>
</g>
<g >
<title>assign_collations_walker (12,550,840,051 samples, 0.13%)</title><rect x="802.7" y="389" width="1.5" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="805.66" y="399.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,125,108,793 samples, 0.01%)</title><rect x="1062.2" y="453" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1065.20" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,778,995,805 samples, 0.02%)</title><rect x="1167.3" y="757" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1170.32" y="767.5" ></text>
</g>
<g >
<title>bms_add_members (3,419,422,218 samples, 0.04%)</title><rect x="878.7" y="469" width="0.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="881.72" y="479.5" ></text>
</g>
<g >
<title>match_clause_to_indexcol (22,178,605,524 samples, 0.24%)</title><rect x="1019.8" y="341" width="2.7" height="15.0" fill="rgb(232,128,30)" rx="2" ry="2" />
<text  x="1022.76" y="351.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,116,617,332 samples, 0.01%)</title><rect x="363.4" y="261" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="366.39" y="271.5" ></text>
</g>
<g >
<title>index_endscan (62,615,411,642 samples, 0.66%)</title><rect x="239.0" y="421" width="7.8" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="241.97" y="431.5" ></text>
</g>
<g >
<title>ExecInitExprRec (23,376,274,234 samples, 0.25%)</title><rect x="418.7" y="357" width="3.0" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="421.75" y="367.5" ></text>
</g>
<g >
<title>ExecIndexScan (1,421,941,776 samples, 0.02%)</title><rect x="43.0" y="757" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="45.96" y="767.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (9,584,892,437 samples, 0.10%)</title><rect x="250.1" y="453" width="1.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="253.11" y="463.5" ></text>
</g>
<g >
<title>order_qual_clauses (1,356,639,565 samples, 0.01%)</title><rect x="892.0" y="389" width="0.2" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="895.03" y="399.5" ></text>
</g>
<g >
<title>pfree (1,374,314,605 samples, 0.01%)</title><rect x="994.5" y="357" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="997.46" y="367.5" ></text>
</g>
<g >
<title>tag_hash (3,579,278,038 samples, 0.04%)</title><rect x="948.0" y="309" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="950.95" y="319.5" ></text>
</g>
<g >
<title>table_close (1,535,344,026 samples, 0.02%)</title><rect x="922.9" y="469" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="925.92" y="479.5" ></text>
</g>
<g >
<title>bms_add_members (2,775,940,946 samples, 0.03%)</title><rect x="877.3" y="485" width="0.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="880.31" y="495.5" ></text>
</g>
<g >
<title>ExecScanFetch (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="405" width="0.5" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1160.94" y="415.5" ></text>
</g>
<g >
<title>vruntime_eligible (1,229,357,175 samples, 0.01%)</title><rect x="171.4" y="277" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="174.43" y="287.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (979,646,950 samples, 0.01%)</title><rect x="347.6" y="357" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="350.61" y="367.5" ></text>
</g>
<g >
<title>InjectionPointRun (3,409,486,832 samples, 0.04%)</title><rect x="362.2" y="357" width="0.4" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="365.16" y="367.5" ></text>
</g>
<g >
<title>_start (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="757" width="953.9" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="133.07" y="767.5" >_start</text>
</g>
<g >
<title>BackendStartup (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="709" width="0.6" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1160.94" y="719.5" ></text>
</g>
<g >
<title>set_cheapest (6,002,329,311 samples, 0.06%)</title><rect x="985.1" y="421" width="0.7" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="988.09" y="431.5" ></text>
</g>
<g >
<title>eval_const_expressions (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="725" width="0.3" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1187.99" y="735.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (842,079,426 samples, 0.01%)</title><rect x="126.0" y="197" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="129.00" y="207.5" ></text>
</g>
<g >
<title>hash_bytes (3,662,157,416 samples, 0.04%)</title><rect x="867.6" y="421" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="870.63" y="431.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (3,849,736,508 samples, 0.04%)</title><rect x="261.6" y="389" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="264.59" y="399.5" ></text>
</g>
<g >
<title>pg_plan_queries (2,495,599,539 samples, 0.03%)</title><rect x="126.5" y="597" width="0.3" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="129.52" y="607.5" ></text>
</g>
<g >
<title>palloc0 (2,072,461,251 samples, 0.02%)</title><rect x="118.1" y="741" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="121.14" y="751.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,795,923,128 samples, 0.02%)</title><rect x="221.1" y="533" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="224.15" y="543.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (29,181,461,865 samples, 0.31%)</title><rect x="297.2" y="261" width="3.7" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="300.20" y="271.5" ></text>
</g>
<g >
<title>bms_next_member (1,954,007,809 samples, 0.02%)</title><rect x="360.1" y="341" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="363.08" y="351.5" ></text>
</g>
<g >
<title>__libc_recv (813,248,969 samples, 0.01%)</title><rect x="122.4" y="757" width="0.1" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="125.35" y="767.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (14,067,847,285 samples, 0.15%)</title><rect x="888.7" y="341" width="1.8" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="891.74" y="351.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,893,093,801 samples, 0.03%)</title><rect x="269.3" y="373" width="0.3" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="272.29" y="383.5" ></text>
</g>
<g >
<title>PushActiveSnapshot (2,790,595,953 samples, 0.03%)</title><rect x="454.1" y="533" width="0.3" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="457.09" y="543.5" ></text>
</g>
<g >
<title>_bt_moveright (8,153,263,592 samples, 0.09%)</title><rect x="336.7" y="245" width="1.0" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="339.69" y="255.5" ></text>
</g>
<g >
<title>pg_leftmost_one_pos64 (991,266,965 samples, 0.01%)</title><rect x="1169.1" y="757" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1172.12" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache3 (8,271,685,263 samples, 0.09%)</title><rect x="1033.0" y="197" width="1.1" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1036.03" y="207.5" ></text>
</g>
<g >
<title>BufferAlloc (1,170,278,593 samples, 0.01%)</title><rect x="34.6" y="757" width="0.1" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="37.58" y="767.5" ></text>
</g>
<g >
<title>DeconstructQualifiedName (2,039,846,816 samples, 0.02%)</title><rect x="851.8" y="373" width="0.2" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="854.79" y="383.5" ></text>
</g>
<g >
<title>IsTidEqualClause (1,120,760,728 samples, 0.01%)</title><rect x="54.3" y="757" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="57.27" y="767.5" ></text>
</g>
<g >
<title>inode_needs_update_time.part.0 (2,889,942,210 samples, 0.03%)</title><rect x="495.7" y="357" width="0.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="498.69" y="367.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,198,847,170 samples, 0.01%)</title><rect x="846.5" y="389" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="849.46" y="399.5" ></text>
</g>
<g >
<title>hash_bytes (2,649,272,489 samples, 0.03%)</title><rect x="95.1" y="133" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="98.12" y="143.5" ></text>
</g>
<g >
<title>AtEOXact_Buffers (3,038,626,343 samples, 0.03%)</title><rect x="461.1" y="517" width="0.4" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="464.15" y="527.5" ></text>
</g>
<g >
<title>colNameToVar (19,505,756,175 samples, 0.21%)</title><rect x="854.2" y="389" width="2.5" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="857.25" y="399.5" ></text>
</g>
<g >
<title>transformColumnRef (23,771,471,386 samples, 0.25%)</title><rect x="853.9" y="405" width="2.9" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="856.88" y="415.5" ></text>
</g>
<g >
<title>_bt_next (10,278,226,071 samples, 0.11%)</title><rect x="280.4" y="293" width="1.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="283.44" y="303.5" ></text>
</g>
<g >
<title>PageGetItemId (3,507,611,952 samples, 0.04%)</title><rect x="80.2" y="757" width="0.4" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="83.16" y="767.5" ></text>
</g>
<g >
<title>MemoryContextAllocExtended (1,303,191,673 samples, 0.01%)</title><rect x="1064.7" y="405" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1067.71" y="415.5" ></text>
</g>
<g >
<title>new_list (1,347,212,180 samples, 0.01%)</title><rect x="896.4" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="899.42" y="447.5" ></text>
</g>
<g >
<title>sched_balance_newidle (3,666,456,715 samples, 0.04%)</title><rect x="159.9" y="309" width="0.5" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="162.95" y="319.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (1,447,545,201 samples, 0.02%)</title><rect x="1170.9" y="757" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="1173.86" y="767.5" ></text>
</g>
<g >
<title>newNode (2,645,324,402 samples, 0.03%)</title><rect x="811.9" y="437" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="814.91" y="447.5" ></text>
</g>
<g >
<title>lappend (2,970,968,260 samples, 0.03%)</title><rect x="815.2" y="453" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="818.25" y="463.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,878,902,493 samples, 0.03%)</title><rect x="1033.6" y="165" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1036.58" y="175.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (835,763,000 samples, 0.01%)</title><rect x="399.5" y="293" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="402.46" y="303.5" ></text>
</g>
<g >
<title>create_plan_recurse (51,371,126,200 samples, 0.54%)</title><rect x="885.8" y="437" width="6.4" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="888.81" y="447.5" ></text>
</g>
<g >
<title>ReadBuffer (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="293" width="2.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="102.04" y="303.5" ></text>
</g>
<g >
<title>propagate_entity_load_avg (1,291,196,798 samples, 0.01%)</title><rect x="171.2" y="261" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="174.16" y="271.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,668,131,060 samples, 0.05%)</title><rect x="1042.4" y="309" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1045.35" y="319.5" ></text>
</g>
<g >
<title>LWLockWakeup (2,368,863,812 samples, 0.03%)</title><rect x="470.3" y="421" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="473.28" y="431.5" ></text>
</g>
<g >
<title>list_copy (2,637,465,642 samples, 0.03%)</title><rect x="899.4" y="469" width="0.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="902.38" y="479.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (1,689,915,162 samples, 0.02%)</title><rect x="266.4" y="389" width="0.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="269.37" y="399.5" ></text>
</g>
<g >
<title>epoll_wait (171,042,344,225 samples, 1.81%)</title><rect x="153.4" y="485" width="21.4" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="156.38" y="495.5" >e..</text>
</g>
<g >
<title>ExecShutdownNode_walker (3,620,728,284 samples, 0.04%)</title><rect x="402.5" y="437" width="0.4" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="405.48" y="447.5" ></text>
</g>
<g >
<title>_find_next_bit (2,037,552,904 samples, 0.02%)</title><rect x="201.9" y="245" width="0.3" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="204.92" y="255.5" ></text>
</g>
<g >
<title>ExecutePlan (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="565" width="2.8" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="85.62" y="575.5" ></text>
</g>
<g >
<title>GetCurrentTransactionId (90,124,061,275 samples, 0.95%)</title><rect x="348.3" y="357" width="11.3" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="351.28" y="367.5" ></text>
</g>
<g >
<title>skb_release_head_state (10,423,271,009 samples, 0.11%)</title><rect x="180.1" y="357" width="1.3" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="183.06" y="367.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="533" width="1.9" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="127.59" y="543.5" ></text>
</g>
<g >
<title>__futex_wait (925,058,534 samples, 0.01%)</title><rect x="296.8" y="101" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="299.85" y="111.5" ></text>
</g>
<g >
<title>__futex_hash (832,486,541 samples, 0.01%)</title><rect x="482.3" y="293" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="485.29" y="303.5" ></text>
</g>
<g >
<title>datumIsEqual (1,164,474,894 samples, 0.01%)</title><rect x="1130.8" y="757" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1133.77" y="767.5" ></text>
</g>
<g >
<title>bms_copy (2,439,436,943 samples, 0.03%)</title><rect x="967.1" y="357" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="970.11" y="367.5" ></text>
</g>
<g >
<title>palloc0 (3,648,667,279 samples, 0.04%)</title><rect x="891.6" y="357" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="894.57" y="367.5" ></text>
</g>
<g >
<title>SetHintBits (3,621,908,865 samples, 0.04%)</title><rect x="1146.5" y="693" width="0.5" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1149.52" y="703.5" ></text>
</g>
<g >
<title>bms_copy (3,425,241,920 samples, 0.04%)</title><rect x="879.5" y="453" width="0.4" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="882.45" y="463.5" ></text>
</g>
<g >
<title>init_htab (10,759,939,116 samples, 0.11%)</title><rect x="1063.7" y="453" width="1.4" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1066.74" y="463.5" ></text>
</g>
<g >
<title>_raw_spin_unlock_irq (872,704,541 samples, 0.01%)</title><rect x="156.0" y="373" width="0.1" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="159.01" y="383.5" ></text>
</g>
<g >
<title>balance_dirty_pages_ratelimited_flags (2,270,378,856 samples, 0.02%)</title><rect x="496.3" y="357" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="499.28" y="367.5" ></text>
</g>
<g >
<title>_bt_search (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="309" width="0.1" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="1161.26" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,585,770,857 samples, 0.02%)</title><rect x="340.3" y="229" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="343.26" y="239.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,433,017,387 samples, 0.02%)</title><rect x="400.5" y="261" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="403.53" y="271.5" ></text>
</g>
<g >
<title>clauselist_selectivity (803,901,079 samples, 0.01%)</title><rect x="126.6" y="405" width="0.1" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="129.56" y="415.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (1,016,382,047 samples, 0.01%)</title><rect x="382.3" y="37" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="385.25" y="47.5" ></text>
</g>
<g >
<title>finalize_primnode (5,406,581,071 samples, 0.06%)</title><rect x="881.1" y="405" width="0.7" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="884.11" y="415.5" ></text>
</g>
<g >
<title>psi_group_change (6,389,213,685 samples, 0.07%)</title><rect x="164.6" y="325" width="0.8" height="15.0" fill="rgb(226,101,24)" rx="2" ry="2" />
<text  x="167.59" y="335.5" ></text>
</g>
<g >
<title>lappend (2,549,506,231 samples, 0.03%)</title><rect x="1013.0" y="277" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1015.98" y="287.5" ></text>
</g>
<g >
<title>PageValidateSpecialPointer (1,053,678,242 samples, 0.01%)</title><rect x="81.2" y="757" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="84.17" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (833,108,571 samples, 0.01%)</title><rect x="364.5" y="309" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="367.47" y="319.5" ></text>
</g>
<g >
<title>list_copy (3,701,829,405 samples, 0.04%)</title><rect x="931.1" y="389" width="0.5" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="934.13" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCache (929,251,302 samples, 0.01%)</title><rect x="973.4" y="357" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="976.41" y="367.5" ></text>
</g>
<g >
<title>_bt_compare (6,445,443,762 samples, 0.07%)</title><rect x="125.2" y="245" width="0.8" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="128.19" y="255.5" ></text>
</g>
<g >
<title>update_entity_lag (2,996,225,688 samples, 0.03%)</title><rect x="169.4" y="277" width="0.4" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="172.41" y="287.5" ></text>
</g>
<g >
<title>make_eq_member (6,059,993,427 samples, 0.06%)</title><rect x="970.6" y="373" width="0.7" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="973.57" y="383.5" ></text>
</g>
<g >
<title>bms_copy (1,707,657,308 samples, 0.02%)</title><rect x="882.0" y="501" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="884.99" y="511.5" ></text>
</g>
<g >
<title>__wake_up_common (2,462,322,841 samples, 0.03%)</title><rect x="181.0" y="277" width="0.3" height="15.0" fill="rgb(248,197,47)" rx="2" ry="2" />
<text  x="184.02" y="287.5" ></text>
</g>
<g >
<title>palloc (2,307,150,765 samples, 0.02%)</title><rect x="1079.1" y="565" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1082.15" y="575.5" ></text>
</g>
<g >
<title>AtEOXact_Aio (834,395,689 samples, 0.01%)</title><rect x="30.7" y="757" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="33.75" y="767.5" ></text>
</g>
<g >
<title>ExecInitIndexScan (995,157,116 samples, 0.01%)</title><rect x="43.3" y="757" width="0.1" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="46.29" y="767.5" ></text>
</g>
<g >
<title>get_opmethod_canorder (941,543,698 samples, 0.01%)</title><rect x="963.7" y="373" width="0.1" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="966.73" y="383.5" ></text>
</g>
<g >
<title>new_list (1,348,908,445 samples, 0.01%)</title><rect x="977.2" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="980.21" y="415.5" ></text>
</g>
<g >
<title>alloc_skb_with_frags (28,718,092,470 samples, 0.30%)</title><rect x="193.9" y="389" width="3.6" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="196.92" y="399.5" ></text>
</g>
<g >
<title>newNode (4,692,271,083 samples, 0.05%)</title><rect x="893.4" y="453" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="896.36" y="463.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,832,400,306 samples, 0.03%)</title><rect x="404.2" y="485" width="0.4" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="407.24" y="495.5" ></text>
</g>
<g >
<title>set_sentinel (1,892,021,518 samples, 0.02%)</title><rect x="1182.3" y="757" width="0.3" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="1185.35" y="767.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (1,038,291,033 samples, 0.01%)</title><rect x="333.2" y="181" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="336.22" y="191.5" ></text>
</g>
<g >
<title>pfree (50,233,350,431 samples, 0.53%)</title><rect x="240.2" y="389" width="6.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="243.16" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCache (958,125,529 samples, 0.01%)</title><rect x="964.6" y="357" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="967.59" y="367.5" ></text>
</g>
<g >
<title>available_idle_cpu (1,006,977,122 samples, 0.01%)</title><rect x="200.6" y="277" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="203.56" y="287.5" ></text>
</g>
<g >
<title>ReadBuffer_common (2,294,684,148 samples, 0.02%)</title><rect x="123.5" y="213" width="0.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="126.54" y="223.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (4,378,938,923 samples, 0.05%)</title><rect x="12.0" y="741" width="0.6" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="15.02" y="751.5" ></text>
</g>
<g >
<title>_bt_getroot (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="325" width="4.3" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="97.67" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,006,464,559 samples, 0.01%)</title><rect x="1059.8" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1062.83" y="431.5" ></text>
</g>
<g >
<title>file_remove_privs_flags (1,086,661,258 samples, 0.01%)</title><rect x="495.4" y="373" width="0.2" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="498.43" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,693,747,635 samples, 0.02%)</title><rect x="1079.2" y="549" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1082.23" y="559.5" ></text>
</g>
<g >
<title>record_times (2,604,745,673 samples, 0.03%)</title><rect x="163.9" y="325" width="0.3" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="166.87" y="335.5" ></text>
</g>
<g >
<title>postmaster_child_launch (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="677" width="2.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="125.45" y="687.5" ></text>
</g>
<g >
<title>set_base_rel_sizes (803,901,079 samples, 0.01%)</title><rect x="126.6" y="469" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="129.56" y="479.5" ></text>
</g>
<g >
<title>index_open (32,097,238,189 samples, 0.34%)</title><rect x="940.1" y="405" width="4.0" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="943.10" y="415.5" ></text>
</g>
<g >
<title>list_free (1,748,588,045 samples, 0.02%)</title><rect x="251.9" y="421" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="254.87" y="431.5" ></text>
</g>
<g >
<title>_bt_search (10,604,333,254 samples, 0.11%)</title><rect x="125.2" y="277" width="1.3" height="15.0" fill="rgb(234,133,31)" rx="2" ry="2" />
<text  x="128.19" y="287.5" ></text>
</g>
<g >
<title>index_getnext_slot (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="405" width="6.6" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="97.67" y="415.5" ></text>
</g>
<g >
<title>XLogRecPtrToBytePos (1,279,777,161 samples, 0.01%)</title><rect x="380.8" y="293" width="0.1" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="383.78" y="303.5" ></text>
</g>
<g >
<title>LockTagHashCode (4,256,535,457 samples, 0.05%)</title><rect x="820.2" y="373" width="0.5" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="823.17" y="383.5" ></text>
</g>
<g >
<title>hash_search (4,045,493,379 samples, 0.04%)</title><rect x="443.4" y="389" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="446.35" y="399.5" ></text>
</g>
<g >
<title>newNode (4,075,369,666 samples, 0.04%)</title><rect x="1117.1" y="725" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1120.09" y="735.5" ></text>
</g>
<g >
<title>get_timeout_active (1,194,799,849 samples, 0.01%)</title><rect x="797.9" y="549" width="0.2" height="15.0" fill="rgb(254,225,54)" rx="2" ry="2" />
<text  x="800.94" y="559.5" ></text>
</g>
<g >
<title>shmem_file_write_iter (48,801,298,488 samples, 0.52%)</title><rect x="495.3" y="389" width="6.1" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="498.29" y="399.5" ></text>
</g>
<g >
<title>do_syscall_64 (964,630,174 samples, 0.01%)</title><rect x="467.7" y="405" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="470.66" y="415.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,050,953,049 samples, 0.01%)</title><rect x="860.9" y="469" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="863.87" y="479.5" ></text>
</g>
<g >
<title>index_other_operands_eval_cost (4,061,864,277 samples, 0.04%)</title><rect x="1013.8" y="293" width="0.5" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="1016.78" y="303.5" ></text>
</g>
<g >
<title>copyObjectImpl (11,801,582,555 samples, 0.13%)</title><rect x="952.2" y="437" width="1.5" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="955.20" y="447.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (8,004,512,788 samples, 0.08%)</title><rect x="889.5" y="309" width="1.0" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="892.50" y="319.5" ></text>
</g>
<g >
<title>futex_wait (1,600,736,131 samples, 0.02%)</title><rect x="490.6" y="341" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="493.55" y="351.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,611,855,632 samples, 0.02%)</title><rect x="268.6" y="261" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="271.56" y="271.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (7,053,214,840 samples, 0.07%)</title><rect x="322.0" y="261" width="0.9" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="325.01" y="271.5" ></text>
</g>
<g >
<title>lappend (2,542,735,187 samples, 0.03%)</title><rect x="869.2" y="533" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="872.15" y="543.5" ></text>
</g>
<g >
<title>subquery_planner (2,948,854,979 samples, 0.03%)</title><rect x="1184.9" y="757" width="0.4" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="1187.92" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,869,021,804 samples, 0.02%)</title><rect x="296.4" y="245" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="299.35" y="255.5" ></text>
</g>
<g >
<title>ExecScan (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="469" width="6.6" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="97.67" y="479.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,690,084,868 samples, 0.02%)</title><rect x="822.0" y="389" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="825.04" y="399.5" ></text>
</g>
<g >
<title>bms_copy (1,936,166,773 samples, 0.02%)</title><rect x="878.8" y="453" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="881.79" y="463.5" ></text>
</g>
<g >
<title>_bt_lockbuf (3,611,842,618 samples, 0.04%)</title><rect x="336.2" y="213" width="0.5" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="339.22" y="223.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,443,244,912 samples, 0.03%)</title><rect x="444.3" y="325" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="447.35" y="335.5" ></text>
</g>
<g >
<title>bsearch (6,440,795,777 samples, 0.07%)</title><rect x="266.9" y="325" width="0.8" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="269.87" y="335.5" ></text>
</g>
<g >
<title>new_list (1,611,130,333 samples, 0.02%)</title><rect x="897.7" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="900.67" y="463.5" ></text>
</g>
<g >
<title>get_restriction_variable (803,901,079 samples, 0.01%)</title><rect x="126.6" y="277" width="0.1" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="129.56" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,021,382,914 samples, 0.01%)</title><rect x="1054.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1057.70" y="399.5" ></text>
</g>
<g >
<title>palloc0 (4,527,202,154 samples, 0.05%)</title><rect x="799.6" y="533" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="802.55" y="543.5" ></text>
</g>
<g >
<title>PortalRunMulti (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="581" width="1.9" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="127.59" y="591.5" ></text>
</g>
<g >
<title>transformAssignedExpr (11,172,551,588 samples, 0.12%)</title><rect x="840.9" y="453" width="1.4" height="15.0" fill="rgb(241,170,40)" rx="2" ry="2" />
<text  x="843.93" y="463.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (12,193,958,647 samples, 0.13%)</title><rect x="861.1" y="485" width="1.5" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="864.11" y="495.5" ></text>
</g>
<g >
<title>try_to_wake_up (1,004,809,256 samples, 0.01%)</title><rect x="354.7" y="101" width="0.1" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="357.71" y="111.5" ></text>
</g>
<g >
<title>IsTransactionOrTransactionBlock (1,128,014,306 samples, 0.01%)</title><rect x="54.5" y="757" width="0.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="57.49" y="767.5" ></text>
</g>
<g >
<title>ExecUpdatePrologue (61,790,980,876 samples, 0.65%)</title><rect x="388.7" y="421" width="7.8" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="391.74" y="431.5" ></text>
</g>
<g >
<title>ExecTypeFromTL (36,603,844,297 samples, 0.39%)</title><rect x="433.7" y="405" width="4.6" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="436.73" y="415.5" ></text>
</g>
<g >
<title>eval_const_expressions (16,356,130,295 samples, 0.17%)</title><rect x="1055.9" y="469" width="2.0" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1058.86" y="479.5" ></text>
</g>
<g >
<title>hash_search (6,024,055,885 samples, 0.06%)</title><rect x="867.3" y="453" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="870.34" y="463.5" ></text>
</g>
<g >
<title>hash_bytes (2,176,304,616 samples, 0.02%)</title><rect x="440.8" y="325" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="443.80" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (951,620,768 samples, 0.01%)</title><rect x="910.0" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="912.98" y="431.5" ></text>
</g>
<g >
<title>add_tabstat_xact_level (7,987,082,596 samples, 0.08%)</title><rect x="387.3" y="325" width="1.0" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="390.34" y="335.5" ></text>
</g>
<g >
<title>finalize_plan (1,348,135,316 samples, 0.01%)</title><rect x="1137.2" y="757" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="1140.21" y="767.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (2,295,493,124 samples, 0.02%)</title><rect x="814.8" y="405" width="0.3" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="817.85" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,816,924,155 samples, 0.02%)</title><rect x="862.9" y="453" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="865.87" y="463.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="597" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1187.99" y="607.5" ></text>
</g>
<g >
<title>tts_buffer_heap_init (860,864,497 samples, 0.01%)</title><rect x="1188.3" y="757" width="0.1" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="1191.28" y="767.5" ></text>
</g>
<g >
<title>ExecIndexScan (30,411,646,289 samples, 0.32%)</title><rect x="278.6" y="421" width="3.8" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="281.59" y="431.5" ></text>
</g>
<g >
<title>palloc (1,507,764,954 samples, 0.02%)</title><rect x="860.0" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="863.04" y="479.5" ></text>
</g>
<g >
<title>analyze_requires_snapshot (812,530,130 samples, 0.01%)</title><rect x="457.1" y="581" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="460.05" y="591.5" ></text>
</g>
<g >
<title>exit_to_user_mode_loop (3,605,761,980 samples, 0.04%)</title><rect x="483.6" y="389" width="0.4" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="486.58" y="399.5" ></text>
</g>
<g >
<title>do_epoll_wait (143,870,570,715 samples, 1.52%)</title><rect x="154.3" y="421" width="18.0" height="15.0" fill="rgb(211,31,7)" rx="2" ry="2" />
<text  x="157.29" y="431.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (2,015,623,714 samples, 0.02%)</title><rect x="185.8" y="533" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="188.78" y="543.5" ></text>
</g>
<g >
<title>pg_qsort (1,294,125,140 samples, 0.01%)</title><rect x="1169.6" y="757" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="1172.57" y="767.5" ></text>
</g>
<g >
<title>pfree (2,489,491,219 samples, 0.03%)</title><rect x="187.2" y="565" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="190.22" y="575.5" ></text>
</g>
<g >
<title>palloc0 (6,325,597,044 samples, 0.07%)</title><rect x="949.4" y="405" width="0.8" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="952.45" y="415.5" ></text>
</g>
<g >
<title>AllocSetFree (1,313,927,560 samples, 0.01%)</title><rect x="321.6" y="229" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="324.60" y="239.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,050,140,203 samples, 0.04%)</title><rect x="846.8" y="357" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="849.84" y="367.5" ></text>
</g>
<g >
<title>core_yyensure_buffer_stack (2,694,396,288 samples, 0.03%)</title><rect x="871.0" y="501" width="0.3" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="873.98" y="511.5" ></text>
</g>
<g >
<title>wipe_mem (2,851,019,751 samples, 0.03%)</title><rect x="133.5" y="485" width="0.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="136.46" y="495.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,145,363,902 samples, 0.03%)</title><rect x="1046.6" y="421" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1049.59" y="431.5" ></text>
</g>
<g >
<title>PortalDefineQuery (1,601,466,573 samples, 0.02%)</title><rect x="222.4" y="581" width="0.2" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="225.41" y="591.5" ></text>
</g>
<g >
<title>_bt_drop_lock_and_maybe_pin (19,745,523,586 samples, 0.21%)</title><rect x="323.2" y="245" width="2.5" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="326.20" y="255.5" ></text>
</g>
<g >
<title>remove_useless_joins (923,791,639 samples, 0.01%)</title><rect x="1175.9" y="757" width="0.1" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="1178.85" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,287,061,791 samples, 0.02%)</title><rect x="441.5" y="357" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="444.46" y="367.5" ></text>
</g>
<g >
<title>select_task_rq (2,471,554,979 samples, 0.03%)</title><rect x="488.0" y="293" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="490.97" y="303.5" ></text>
</g>
<g >
<title>hash_search (8,377,145,025 samples, 0.09%)</title><rect x="829.9" y="389" width="1.1" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="832.93" y="399.5" ></text>
</g>
<g >
<title>tag_hash (2,344,320,464 samples, 0.02%)</title><rect x="942.9" y="325" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="945.86" y="335.5" ></text>
</g>
<g >
<title>pick_task_fair (936,406,081 samples, 0.01%)</title><rect x="206.5" y="373" width="0.1" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="209.51" y="383.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (897,745,432 samples, 0.01%)</title><rect x="872.7" y="501" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="875.72" y="511.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (1,977,147,596 samples, 0.02%)</title><rect x="470.3" y="389" width="0.3" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="473.31" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,252,972,481 samples, 0.01%)</title><rect x="1061.9" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1064.91" y="447.5" ></text>
</g>
<g >
<title>bms_is_valid_set (822,272,665 samples, 0.01%)</title><rect x="376.2" y="341" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="379.20" y="351.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (6,331,336,411 samples, 0.07%)</title><rect x="1041.3" y="325" width="0.8" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1044.27" y="335.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (918,288,973 samples, 0.01%)</title><rect x="126.7" y="405" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="129.71" y="415.5" ></text>
</g>
<g >
<title>ExecTypeFromTL (2,464,904,726 samples, 0.03%)</title><rect x="448.6" y="437" width="0.3" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="451.58" y="447.5" ></text>
</g>
<g >
<title>internal_putbytes (2,061,149,292 samples, 0.02%)</title><rect x="187.6" y="549" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="190.63" y="559.5" ></text>
</g>
<g >
<title>WALInsertLockAcquire (6,526,216,529 samples, 0.07%)</title><rect x="381.1" y="309" width="0.8" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="384.12" y="319.5" ></text>
</g>
<g >
<title>get_hash_entry (1,089,570,486 samples, 0.01%)</title><rect x="214.6" y="533" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="217.61" y="543.5" ></text>
</g>
<g >
<title>main (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="741" width="2.1" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="125.45" y="751.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,091,204,463 samples, 0.01%)</title><rect x="508.5" y="405" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="511.52" y="415.5" ></text>
</g>
<g >
<title>_bt_readpage (20,300,675,888 samples, 0.22%)</title><rect x="325.7" y="245" width="2.5" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="328.67" y="255.5" ></text>
</g>
<g >
<title>TidRangeQualFromRestrictInfoList (3,515,562,238 samples, 0.04%)</title><rect x="1025.8" y="389" width="0.5" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="1028.83" y="399.5" ></text>
</g>
<g >
<title>prune_freeze_plan (16,815,267,368 samples, 0.18%)</title><rect x="1145.8" y="741" width="2.1" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="1148.79" y="751.5" ></text>
</g>
<g >
<title>_bt_readpage (2,568,695,668 samples, 0.03%)</title><rect x="1157.9" y="293" width="0.4" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1160.94" y="303.5" ></text>
</g>
<g >
<title>eval_const_expressions (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="517" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1161.36" y="527.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (852,879,021 samples, 0.01%)</title><rect x="1014.1" y="261" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1017.13" y="271.5" ></text>
</g>
<g >
<title>is_redundant_with_indexclauses (821,962,795 samples, 0.01%)</title><rect x="1152.9" y="757" width="0.1" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="1155.85" y="767.5" ></text>
</g>
<g >
<title>check_lock_if_inplace_updateable_rel (930,634,504 samples, 0.01%)</title><rect x="376.3" y="357" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="379.30" y="367.5" ></text>
</g>
<g >
<title>TransactionIdGetStatus (846,240,897 samples, 0.01%)</title><rect x="106.5" y="757" width="0.1" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="109.45" y="767.5" ></text>
</g>
<g >
<title>palloc (1,712,086,258 samples, 0.02%)</title><rect x="1015.5" y="277" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1018.51" y="287.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (1,147,327,467 samples, 0.01%)</title><rect x="1052.9" y="357" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1055.94" y="367.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,253,333,426 samples, 0.01%)</title><rect x="338.0" y="213" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="340.99" y="223.5" ></text>
</g>
<g >
<title>HeapCheckForSerializableConflictOut (1,231,795,487 samples, 0.01%)</title><rect x="302.3" y="245" width="0.2" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="305.35" y="255.5" ></text>
</g>
<g >
<title>get_typavgwidth (6,254,676,316 samples, 0.07%)</title><rect x="1047.4" y="453" width="0.8" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="1050.40" y="463.5" ></text>
</g>
<g >
<title>create_lateral_join_info (1,134,356,096 samples, 0.01%)</title><rect x="957.0" y="469" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="959.98" y="479.5" ></text>
</g>
<g >
<title>ExecInitScanTupleSlot (10,608,699,820 samples, 0.11%)</title><rect x="438.3" y="421" width="1.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="441.31" y="431.5" ></text>
</g>
<g >
<title>lappend (2,153,691,108 samples, 0.02%)</title><rect x="972.3" y="389" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="975.30" y="399.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (4,238,255,368 samples, 0.04%)</title><rect x="1059.1" y="469" width="0.6" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1062.12" y="479.5" ></text>
</g>
<g >
<title>pg_plan_query (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="645" width="3.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="104.45" y="655.5" ></text>
</g>
<g >
<title>GetNewTransactionId (1,043,943,199 samples, 0.01%)</title><rect x="48.8" y="757" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="51.78" y="767.5" ></text>
</g>
<g >
<title>int4pl (1,445,057,981 samples, 0.02%)</title><rect x="287.8" y="277" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="290.78" y="287.5" ></text>
</g>
<g >
<title>ExecInitResultTypeTL (2,853,079,797 samples, 0.03%)</title><rect x="448.5" y="453" width="0.4" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="451.53" y="463.5" ></text>
</g>
<g >
<title>WaitEventSetWaitBlock (177,914,013,986 samples, 1.89%)</title><rect x="152.5" y="501" width="22.3" height="15.0" fill="rgb(250,211,50)" rx="2" ry="2" />
<text  x="155.53" y="511.5" >W..</text>
</g>
<g >
<title>UnregisterSnapshotFromOwner (3,360,522,681 samples, 0.04%)</title><rect x="452.8" y="501" width="0.4" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="455.83" y="511.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (1,503,171,958 samples, 0.02%)</title><rect x="33.4" y="757" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="36.42" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (1,898,458,741 samples, 0.02%)</title><rect x="1055.6" y="421" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1058.63" y="431.5" ></text>
</g>
<g >
<title>get_tablespace (6,875,272,800 samples, 0.07%)</title><rect x="1022.9" y="357" width="0.9" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1025.93" y="367.5" ></text>
</g>
<g >
<title>palloc0 (2,565,935,392 samples, 0.03%)</title><rect x="855.0" y="325" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="857.99" y="335.5" ></text>
</g>
<g >
<title>tag_hash (2,598,569,904 samples, 0.03%)</title><rect x="829.2" y="357" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="832.24" y="367.5" ></text>
</g>
<g >
<title>hash_bytes (2,158,166,009 samples, 0.02%)</title><rect x="227.0" y="533" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="229.97" y="543.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,426,309,603 samples, 0.06%)</title><rect x="807.7" y="309" width="0.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="810.69" y="319.5" ></text>
</g>
<g >
<title>deconstruct_jointree (1,328,444,012 samples, 0.01%)</title><rect x="1131.1" y="757" width="0.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="1134.12" y="767.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,138,590,205 samples, 0.01%)</title><rect x="1081.2" y="581" width="0.2" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1084.24" y="591.5" ></text>
</g>
<g >
<title>LWLockRelease (1,323,806,609 samples, 0.01%)</title><rect x="96.7" y="181" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="99.74" y="191.5" ></text>
</g>
<g >
<title>lappend (2,502,057,927 samples, 0.03%)</title><rect x="1019.4" y="341" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1022.45" y="351.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,304,156,389 samples, 0.04%)</title><rect x="960.5" y="293" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="963.47" y="303.5" ></text>
</g>
<g >
<title>create_plan (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="581" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="88.40" y="591.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,716,047,301 samples, 0.05%)</title><rect x="220.5" y="549" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="223.51" y="559.5" ></text>
</g>
<g >
<title>create_tidscan_paths (15,329,392,259 samples, 0.16%)</title><rect x="1024.4" y="405" width="1.9" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="1027.36" y="415.5" ></text>
</g>
<g >
<title>TrackNewBufferPin (1,226,963,335 samples, 0.01%)</title><rect x="97.7" y="165" width="0.1" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="100.67" y="175.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,266,527,733 samples, 0.01%)</title><rect x="1000.1" y="309" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1003.08" y="319.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,483,413,931 samples, 0.02%)</title><rect x="896.1" y="469" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="899.13" y="479.5" ></text>
</g>
<g >
<title>ReadBuffer_common (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="245" width="1.5" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="86.22" y="255.5" ></text>
</g>
<g >
<title>newNode (2,682,736,135 samples, 0.03%)</title><rect x="934.6" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="937.55" y="383.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (66,459,520,487 samples, 0.70%)</title><rect x="176.2" y="485" width="8.3" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="179.19" y="495.5" ></text>
</g>
<g >
<title>updateTargetListEntry (11,881,316,486 samples, 0.13%)</title><rect x="840.8" y="469" width="1.5" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="843.84" y="479.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (868,575,976 samples, 0.01%)</title><rect x="1055.8" y="405" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1058.75" y="415.5" ></text>
</g>
<g >
<title>btendscan (53,325,494,917 samples, 0.57%)</title><rect x="239.8" y="405" width="6.6" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="242.78" y="415.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="581" width="2.8" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="85.62" y="591.5" ></text>
</g>
<g >
<title>build_base_rel_tlists (51,490,199,832 samples, 0.55%)</title><rect x="950.5" y="469" width="6.5" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="953.54" y="479.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,529,243,091 samples, 0.02%)</title><rect x="272.8" y="405" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="275.76" y="415.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (4,956,481,461 samples, 0.05%)</title><rect x="1039.6" y="309" width="0.6" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1042.62" y="319.5" ></text>
</g>
<g >
<title>MemoryContextSetParent (1,327,973,706 samples, 0.01%)</title><rect x="251.1" y="437" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="254.14" y="447.5" ></text>
</g>
<g >
<title>SearchCatCache1 (5,325,745,657 samples, 0.06%)</title><rect x="848.3" y="373" width="0.7" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="851.34" y="383.5" ></text>
</g>
<g >
<title>heapam_tuple_update (1,480,747,967 samples, 0.02%)</title><rect x="1149.9" y="757" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1152.89" y="767.5" ></text>
</g>
<g >
<title>enqueue_task_fair (8,302,851,312 samples, 0.09%)</title><rect x="203.8" y="293" width="1.0" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="206.77" y="303.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,072,232,131 samples, 0.01%)</title><rect x="1000.1" y="277" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1003.11" y="287.5" ></text>
</g>
<g >
<title>newNode (4,835,491,553 samples, 0.05%)</title><rect x="392.9" y="357" width="0.6" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="395.94" y="367.5" ></text>
</g>
<g >
<title>new_list (1,823,131,288 samples, 0.02%)</title><rect x="433.0" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="435.95" y="399.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="197" width="0.1" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="1161.26" y="207.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="501" width="3.4" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="104.45" y="511.5" ></text>
</g>
<g >
<title>RestrictInfoIsTidQual (854,064,580 samples, 0.01%)</title><rect x="92.2" y="757" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="95.19" y="767.5" ></text>
</g>
<g >
<title>bms_add_member (2,589,805,574 samples, 0.03%)</title><rect x="974.7" y="309" width="0.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="977.74" y="319.5" ></text>
</g>
<g >
<title>newNode (11,270,101,472 samples, 0.12%)</title><rect x="1161.5" y="757" width="1.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1164.52" y="767.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (2,699,887,698 samples, 0.03%)</title><rect x="417.0" y="325" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="420.05" y="335.5" ></text>
</g>
<g >
<title>available_idle_cpu (5,115,141,020 samples, 0.05%)</title><rect x="202.2" y="245" width="0.6" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="205.18" y="255.5" ></text>
</g>
<g >
<title>palloc (1,285,443,939 samples, 0.01%)</title><rect x="877.5" y="453" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="880.45" y="463.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,952,517,856 samples, 0.05%)</title><rect x="964.7" y="373" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="967.72" y="383.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,653,179,243 samples, 0.02%)</title><rect x="467.6" y="485" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="470.58" y="495.5" ></text>
</g>
<g >
<title>relation_open (27,901,231,774 samples, 0.30%)</title><rect x="865.6" y="501" width="3.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="868.61" y="511.5" ></text>
</g>
<g >
<title>cost_seqscan (8,466,733,926 samples, 0.09%)</title><rect x="1022.7" y="389" width="1.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1025.73" y="399.5" ></text>
</g>
<g >
<title>ReadBuffer (2,318,356,473 samples, 0.02%)</title><rect x="123.5" y="245" width="0.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="126.54" y="255.5" ></text>
</g>
<g >
<title>hash_search (6,017,165,153 samples, 0.06%)</title><rect x="947.6" y="325" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="950.65" y="335.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (11,118,486,749 samples, 0.12%)</title><rect x="960.1" y="357" width="1.3" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="963.05" y="367.5" ></text>
</g>
<g >
<title>pgstat_count_io_op_time (7,054,542,594 samples, 0.07%)</title><rect x="504.1" y="469" width="0.8" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="507.07" y="479.5" ></text>
</g>
<g >
<title>BlockIdGetBlockNumber (1,000,701,191 samples, 0.01%)</title><rect x="33.8" y="757" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="36.81" y="767.5" ></text>
</g>
<g >
<title>list_concat_copy (5,897,847,809 samples, 0.06%)</title><rect x="992.0" y="373" width="0.7" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="994.98" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (3,375,151,209 samples, 0.04%)</title><rect x="923.7" y="357" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="926.69" y="367.5" ></text>
</g>
<g >
<title>AllocSetFree (1,660,331,763 samples, 0.02%)</title><rect x="1073.3" y="549" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1076.32" y="559.5" ></text>
</g>
<g >
<title>CheckForSerializableConflictOutNeeded (837,238,861 samples, 0.01%)</title><rect x="38.4" y="757" width="0.2" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="41.45" y="767.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (2,416,707,241 samples, 0.03%)</title><rect x="504.6" y="437" width="0.3" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="507.64" y="447.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,836,151,002 samples, 0.02%)</title><rect x="867.1" y="389" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="870.08" y="399.5" ></text>
</g>
<g >
<title>int4hashfast (1,122,872,629 samples, 0.01%)</title><rect x="1010.4" y="213" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1013.40" y="223.5" ></text>
</g>
<g >
<title>hash_bytes (2,700,772,419 samples, 0.03%)</title><rect x="518.8" y="405" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="521.81" y="415.5" ></text>
</g>
<g >
<title>palloc (1,188,299,097 samples, 0.01%)</title><rect x="1054.7" y="405" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1057.68" y="415.5" ></text>
</g>
<g >
<title>makeTargetEntry (3,061,531,521 samples, 0.03%)</title><rect x="921.0" y="437" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="924.01" y="447.5" ></text>
</g>
<g >
<title>BufferGetPage (1,470,006,073 samples, 0.02%)</title><rect x="370.6" y="341" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="373.58" y="351.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,212,365,903 samples, 0.01%)</title><rect x="1144.3" y="741" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1147.26" y="751.5" ></text>
</g>
<g >
<title>fetch_upper_rel (986,828,449 samples, 0.01%)</title><rect x="894.0" y="517" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="896.96" y="527.5" ></text>
</g>
<g >
<title>transformUpdateTargetList (86,434,145,877 samples, 0.92%)</title><rect x="831.5" y="485" width="10.8" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="834.52" y="495.5" ></text>
</g>
<g >
<title>ReleaseSysCache (840,479,820 samples, 0.01%)</title><rect x="427.2" y="389" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="430.25" y="399.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,437,356,697 samples, 0.05%)</title><rect x="103.0" y="437" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="105.99" y="447.5" ></text>
</g>
<g >
<title>ExecScanFetch (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="437" width="6.6" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="97.67" y="447.5" ></text>
</g>
<g >
<title>slot_getsomeattrs (4,896,985,485 samples, 0.05%)</title><rect x="343.7" y="373" width="0.6" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="346.71" y="383.5" ></text>
</g>
<g >
<title>LWLockConflictsWithVar (4,168,215,345 samples, 0.04%)</title><rect x="489.9" y="453" width="0.5" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="492.90" y="463.5" ></text>
</g>
<g >
<title>LWLockReleaseClearVar (879,348,778 samples, 0.01%)</title><rect x="57.0" y="757" width="0.1" height="15.0" fill="rgb(253,221,52)" rx="2" ry="2" />
<text  x="60.02" y="767.5" ></text>
</g>
<g >
<title>new_list (4,235,642,377 samples, 0.04%)</title><rect x="1116.0" y="709" width="0.6" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1119.05" y="719.5" ></text>
</g>
<g >
<title>list_nth_cell (851,466,178 samples, 0.01%)</title><rect x="888.6" y="357" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="891.63" y="367.5" ></text>
</g>
<g >
<title>palloc (1,288,164,753 samples, 0.01%)</title><rect x="994.3" y="309" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="997.29" y="319.5" ></text>
</g>
<g >
<title>FileSize (8,006,597,601 samples, 0.08%)</title><rect x="932.2" y="341" width="1.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="935.16" y="351.5" ></text>
</g>
<g >
<title>table_block_relation_estimate_size (27,359,105,218 samples, 0.29%)</title><rect x="935.2" y="357" width="3.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="938.19" y="367.5" ></text>
</g>
<g >
<title>index_getnext_slot (810,372,740 samples, 0.01%)</title><rect x="1150.3" y="757" width="0.1" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="1153.31" y="767.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (11,936,751,764 samples, 0.13%)</title><rect x="1053.3" y="453" width="1.5" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1056.35" y="463.5" ></text>
</g>
<g >
<title>hash_bytes (2,512,916,668 samples, 0.03%)</title><rect x="862.3" y="421" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="865.32" y="431.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,847,035,702 samples, 0.06%)</title><rect x="519.5" y="421" width="0.7" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="522.51" y="431.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (2,142,913,540 samples, 0.02%)</title><rect x="231.4" y="517" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="234.37" y="527.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (3,106,076,506 samples, 0.03%)</title><rect x="1055.5" y="453" width="0.4" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1058.47" y="463.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="357" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="127.24" y="367.5" ></text>
</g>
<g >
<title>planstate_tree_walker_impl (5,903,045,148 samples, 0.06%)</title><rect x="402.3" y="453" width="0.8" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="405.31" y="463.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (1,095,644,757 samples, 0.01%)</title><rect x="435.5" y="309" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="438.46" y="319.5" ></text>
</g>
<g >
<title>call_function_single_prep_ipi (1,110,602,642 samples, 0.01%)</title><rect x="205.3" y="293" width="0.1" height="15.0" fill="rgb(213,41,9)" rx="2" ry="2" />
<text  x="208.26" y="303.5" ></text>
</g>
<g >
<title>lappend (842,827,026 samples, 0.01%)</title><rect x="956.9" y="357" width="0.1" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="959.87" y="367.5" ></text>
</g>
<g >
<title>set_ps_display_with_len (3,549,094,500 samples, 0.04%)</title><rect x="1073.6" y="581" width="0.4" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="1076.60" y="591.5" ></text>
</g>
<g >
<title>newNode (2,326,943,211 samples, 0.02%)</title><rect x="934.1" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="937.12" y="383.5" ></text>
</g>
<g >
<title>btcost_correlation (75,376,536,871 samples, 0.80%)</title><rect x="1001.3" y="309" width="9.4" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="1004.28" y="319.5" ></text>
</g>
<g >
<title>hash_search (3,739,237,843 samples, 0.04%)</title><rect x="441.4" y="373" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="444.43" y="383.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,835,753,011 samples, 0.02%)</title><rect x="427.7" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="430.73" y="351.5" ></text>
</g>
<g >
<title>RecordTransactionCommit (337,812,104,264 samples, 3.58%)</title><rect x="468.0" y="517" width="42.2" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="470.98" y="527.5" >Rec..</text>
</g>
<g >
<title>pfree (1,548,621,376 samples, 0.02%)</title><rect x="995.8" y="341" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="998.75" y="351.5" ></text>
</g>
<g >
<title>table_close (1,115,538,672 samples, 0.01%)</title><rect x="947.3" y="405" width="0.1" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="950.28" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (9,557,540,302 samples, 0.10%)</title><rect x="277.0" y="357" width="1.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="280.04" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="597" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.04" y="607.5" ></text>
</g>
<g >
<title>match_foreign_keys_to_quals (967,086,773 samples, 0.01%)</title><rect x="1160.4" y="757" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1163.37" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,104,601,623 samples, 0.03%)</title><rect x="1046.1" y="421" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1049.10" y="431.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="565" width="0.5" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="1160.94" y="575.5" ></text>
</g>
<g >
<title>UnregisterSnapshot (3,805,040,164 samples, 0.04%)</title><rect x="452.8" y="517" width="0.5" height="15.0" fill="rgb(212,33,7)" rx="2" ry="2" />
<text  x="455.78" y="527.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,801,963,844 samples, 0.02%)</title><rect x="339.5" y="197" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="342.50" y="207.5" ></text>
</g>
<g >
<title>preprocess_function_rtes (1,129,744,187 samples, 0.01%)</title><rect x="1173.2" y="757" width="0.2" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="1176.21" y="767.5" ></text>
</g>
<g >
<title>palloc0 (1,545,372,171 samples, 0.02%)</title><rect x="832.6" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="835.61" y="447.5" ></text>
</g>
<g >
<title>__put_user_nocheck_4 (1,848,327,248 samples, 0.02%)</title><rect x="155.6" y="373" width="0.2" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="158.56" y="383.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (3,134,921,548 samples, 0.03%)</title><rect x="1016.6" y="325" width="0.4" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1019.58" y="335.5" ></text>
</g>
<g >
<title>folio_mark_accessed (868,658,241 samples, 0.01%)</title><rect x="499.9" y="325" width="0.2" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="502.95" y="335.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,211,019,665 samples, 0.01%)</title><rect x="897.3" y="469" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="900.25" y="479.5" ></text>
</g>
<g >
<title>futex_wait (953,486,843 samples, 0.01%)</title><rect x="398.7" y="245" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="401.67" y="255.5" ></text>
</g>
<g >
<title>__check_object_size.part.0 (5,615,521,597 samples, 0.06%)</title><rect x="192.9" y="389" width="0.7" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="195.94" y="399.5" ></text>
</g>
<g >
<title>BackendMain (81,441,377,880 samples, 0.86%)</title><rect x="94.7" y="709" width="10.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="97.67" y="719.5" ></text>
</g>
<g >
<title>InitPlan (372,718,195,831 samples, 3.95%)</title><rect x="405.6" y="501" width="46.6" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="408.59" y="511.5" >Init..</text>
</g>
<g >
<title>hash_bytes (2,435,394,107 samples, 0.03%)</title><rect x="237.4" y="373" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="240.43" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,066,912,046 samples, 0.01%)</title><rect x="871.2" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="874.18" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (928,196,930 samples, 0.01%)</title><rect x="220.6" y="517" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="223.60" y="527.5" ></text>
</g>
<g >
<title>index_getnext_tid (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="373" width="2.8" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="85.62" y="383.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (8,156,076,739 samples, 0.09%)</title><rect x="1033.0" y="181" width="1.1" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1036.05" y="191.5" ></text>
</g>
<g >
<title>BufferGetPage (1,336,423,447 samples, 0.01%)</title><rect x="323.5" y="213" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="326.45" y="223.5" ></text>
</g>
<g >
<title>get_rightop (1,142,077,380 samples, 0.01%)</title><rect x="972.1" y="389" width="0.2" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="975.13" y="399.5" ></text>
</g>
<g >
<title>ObjectIdGetDatum (4,365,395,117 samples, 0.05%)</title><rect x="78.9" y="757" width="0.6" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="81.93" y="767.5" ></text>
</g>
<g >
<title>DynaHashAlloc (1,561,120,995 samples, 0.02%)</title><rect x="1064.7" y="421" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1067.68" y="431.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (30,909,648,913 samples, 0.33%)</title><rect x="485.0" y="421" width="3.8" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="487.96" y="431.5" ></text>
</g>
<g >
<title>list_make2_impl (2,554,422,013 samples, 0.03%)</title><rect x="847.3" y="405" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="850.35" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,716,178,557 samples, 0.02%)</title><rect x="1115.5" y="693" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1118.46" y="703.5" ></text>
</g>
<g >
<title>lappend_int (2,534,631,004 samples, 0.03%)</title><rect x="432.9" y="405" width="0.3" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="435.88" y="415.5" ></text>
</g>
<g >
<title>InitBufferTag (837,808,102 samples, 0.01%)</title><rect x="96.1" y="181" width="0.1" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="99.10" y="191.5" ></text>
</g>
<g >
<title>uint32_hash (2,788,799,258 samples, 0.03%)</title><rect x="868.7" y="453" width="0.4" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="871.72" y="463.5" ></text>
</g>
<g >
<title>get_hash_value (2,711,089,561 samples, 0.03%)</title><rect x="367.0" y="229" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="370.04" y="239.5" ></text>
</g>
<g >
<title>reconsider_outer_join_clauses (3,309,817,196 samples, 0.04%)</title><rect x="1043.1" y="469" width="0.5" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="1046.14" y="479.5" ></text>
</g>
<g >
<title>relation_close (1,665,535,155 samples, 0.02%)</title><rect x="235.5" y="453" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="238.46" y="463.5" ></text>
</g>
<g >
<title>fix_expr_common (1,986,780,965 samples, 0.02%)</title><rect x="903.2" y="405" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="906.15" y="415.5" ></text>
</g>
<g >
<title>list_insert_nth (2,576,417,759 samples, 0.03%)</title><rect x="994.1" y="357" width="0.4" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="997.14" y="367.5" ></text>
</g>
<g >
<title>try_to_wake_up (1,080,306,982 samples, 0.01%)</title><rect x="363.8" y="165" width="0.1" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="366.79" y="175.5" ></text>
</g>
<g >
<title>PreCommit_CheckForSerializationFailure (1,030,966,680 samples, 0.01%)</title><rect x="85.6" y="757" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="88.64" y="767.5" ></text>
</g>
<g >
<title>scanner_init (22,907,829,967 samples, 0.24%)</title><rect x="870.0" y="549" width="2.9" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="873.01" y="559.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (1,879,507,580 samples, 0.02%)</title><rect x="352.6" y="277" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="355.55" y="287.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (968,784,187 samples, 0.01%)</title><rect x="421.3" y="293" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="424.34" y="303.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,761,640,540 samples, 0.07%)</title><rect x="974.4" y="341" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="977.39" y="351.5" ></text>
</g>
<g >
<title>_bt_compare (1,617,278,417 samples, 0.02%)</title><rect x="126.2" y="245" width="0.2" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="129.24" y="255.5" ></text>
</g>
<g >
<title>add_function_cost (6,418,545,119 samples, 0.07%)</title><rect x="1038.6" y="341" width="0.8" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1041.57" y="351.5" ></text>
</g>
<g >
<title>index_can_return (2,683,056,664 samples, 0.03%)</title><rect x="939.5" y="405" width="0.4" height="15.0" fill="rgb(205,4,0)" rx="2" ry="2" />
<text  x="942.53" y="415.5" ></text>
</g>
<g >
<title>HeapTupleIsSurelyDead (5,482,298,111 samples, 0.06%)</title><rect x="303.2" y="245" width="0.7" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="306.20" y="255.5" ></text>
</g>
<g >
<title>tts_virtual_clear (980,604,066 samples, 0.01%)</title><rect x="1188.7" y="757" width="0.1" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="1191.66" y="767.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (2,684,539,826 samples, 0.03%)</title><rect x="256.0" y="421" width="0.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="258.98" y="431.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (3,274,418,285 samples, 0.03%)</title><rect x="902.6" y="341" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="905.55" y="351.5" ></text>
</g>
<g >
<title>namestrcpy (3,920,078,234 samples, 0.04%)</title><rect x="436.7" y="357" width="0.5" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="439.68" y="367.5" ></text>
</g>
<g >
<title>exprSetInputCollation (835,015,022 samples, 0.01%)</title><rect x="805.9" y="357" width="0.1" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="808.87" y="367.5" ></text>
</g>
<g >
<title>recomputeNamespacePath (926,993,718 samples, 0.01%)</title><rect x="827.6" y="389" width="0.1" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="830.59" y="399.5" ></text>
</g>
<g >
<title>BackendMain (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="629" width="953.9" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="133.07" y="639.5" >BackendMain</text>
</g>
<g >
<title>exprTypmod (1,680,665,955 samples, 0.02%)</title><rect x="438.1" y="373" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="441.08" y="383.5" ></text>
</g>
<g >
<title>CreatePortal (34,070,659,826 samples, 0.36%)</title><rect x="210.8" y="581" width="4.3" height="15.0" fill="rgb(219,66,16)" rx="2" ry="2" />
<text  x="213.82" y="591.5" ></text>
</g>
<g >
<title>relation_open (19,692,526,449 samples, 0.21%)</title><rect x="444.0" y="405" width="2.5" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="447.01" y="415.5" ></text>
</g>
<g >
<title>SearchCatCache3 (5,471,464,671 samples, 0.06%)</title><rect x="1014.7" y="277" width="0.7" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1017.67" y="287.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,503,672,321 samples, 0.04%)</title><rect x="835.0" y="341" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="837.99" y="351.5" ></text>
</g>
<g >
<title>CommitTransaction (1,004,974,376 samples, 0.01%)</title><rect x="38.8" y="757" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="41.77" y="767.5" ></text>
</g>
<g >
<title>verify_compact_attribute (8,397,232,289 samples, 0.09%)</title><rect x="273.1" y="389" width="1.0" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="276.09" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,506,375,855 samples, 0.02%)</title><rect x="820.0" y="373" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="822.98" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,019,819,570 samples, 0.01%)</title><rect x="466.2" y="469" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="469.23" y="479.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (9,468,881,089 samples, 0.10%)</title><rect x="1045.8" y="453" width="1.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1048.82" y="463.5" ></text>
</g>
<g >
<title>create_scan_plan (50,437,577,736 samples, 0.53%)</title><rect x="885.9" y="421" width="6.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="888.93" y="431.5" ></text>
</g>
<g >
<title>IsTidEqualClause (3,677,219,367 samples, 0.04%)</title><rect x="1025.2" y="357" width="0.5" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1028.24" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,390,942,130 samples, 0.01%)</title><rect x="124.2" y="293" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="127.24" y="303.5" ></text>
</g>
<g >
<title>hash_seq_init (2,007,854,009 samples, 0.02%)</title><rect x="523.4" y="453" width="0.2" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="526.37" y="463.5" ></text>
</g>
<g >
<title>LockHeldByMe (11,641,646,446 samples, 0.12%)</title><rect x="861.2" y="469" width="1.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="864.18" y="479.5" ></text>
</g>
<g >
<title>palloc (1,506,189,487 samples, 0.02%)</title><rect x="452.0" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="454.99" y="479.5" ></text>
</g>
<g >
<title>XLogBytePosToEndRecPtr (968,222,142 samples, 0.01%)</title><rect x="490.8" y="469" width="0.1" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="493.79" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (968,121,387 samples, 0.01%)</title><rect x="274.3" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="277.34" y="367.5" ></text>
</g>
<g >
<title>bms_add_member (2,403,467,265 samples, 0.03%)</title><rect x="408.1" y="469" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="411.06" y="479.5" ></text>
</g>
<g >
<title>AllocSetCheck (2,386,732,888 samples, 0.03%)</title><rect x="77.3" y="741" width="0.3" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="80.28" y="751.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (4,634,768,417 samples, 0.05%)</title><rect x="932.5" y="309" width="0.6" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="935.53" y="319.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="325" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="88.40" y="335.5" ></text>
</g>
<g >
<title>unix_stream_read_generic (49,750,773,934 samples, 0.53%)</title><rect x="178.1" y="389" width="6.2" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="181.12" y="399.5" ></text>
</g>
<g >
<title>put_prev_task_fair (827,407,020 samples, 0.01%)</title><rect x="477.1" y="261" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="480.14" y="271.5" ></text>
</g>
<g >
<title>AllocSetFree (2,517,013,599 samples, 0.03%)</title><rect x="429.1" y="389" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="432.09" y="399.5" ></text>
</g>
<g >
<title>BufferAlloc (13,195,088,607 samples, 0.14%)</title><rect x="399.7" y="293" width="1.6" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="402.68" y="303.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,485,697,547 samples, 0.02%)</title><rect x="820.7" y="373" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="823.70" y="383.5" ></text>
</g>
<g >
<title>palloc (2,724,367,955 samples, 0.03%)</title><rect x="428.7" y="405" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="431.71" y="415.5" ></text>
</g>
<g >
<title>pg_class_aclmask_ext (9,525,828,964 samples, 0.10%)</title><rect x="406.9" y="437" width="1.1" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="409.86" y="447.5" ></text>
</g>
<g >
<title>pfree (1,844,427,138 samples, 0.02%)</title><rect x="227.3" y="565" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="230.25" y="575.5" ></text>
</g>
<g >
<title>set_plan_refs (32,890,934,484 samples, 0.35%)</title><rect x="899.7" y="485" width="4.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="902.71" y="495.5" ></text>
</g>
<g >
<title>pfree (5,074,295,802 samples, 0.05%)</title><rect x="375.4" y="341" width="0.7" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="378.42" y="351.5" ></text>
</g>
<g >
<title>LockHeldByMe (18,853,764,403 samples, 0.20%)</title><rect x="865.7" y="469" width="2.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="868.75" y="479.5" ></text>
</g>
<g >
<title>ScanKeywordLookup (19,995,714,700 samples, 0.21%)</title><rect x="1110.3" y="709" width="2.5" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="1113.34" y="719.5" ></text>
</g>
<g >
<title>update_entity_lag (1,898,250,031 samples, 0.02%)</title><rect x="480.8" y="213" width="0.2" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="483.76" y="223.5" ></text>
</g>
<g >
<title>pg_ulltoa_n (2,665,029,078 samples, 0.03%)</title><rect x="216.0" y="549" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="219.03" y="559.5" ></text>
</g>
<g >
<title>addRangeTableEntryForRelation (51,773,958,363 samples, 0.55%)</title><rect x="810.6" y="469" width="6.5" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="813.62" y="479.5" ></text>
</g>
<g >
<title>heap_attr_equals (2,769,518,479 samples, 0.03%)</title><rect x="360.3" y="341" width="0.4" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="363.33" y="351.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (957,718,935 samples, 0.01%)</title><rect x="339.9" y="197" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="342.87" y="207.5" ></text>
</g>
<g >
<title>parse_analyze_fixedparams (467,821,077,692 samples, 4.96%)</title><rect x="798.4" y="565" width="58.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="801.38" y="575.5" >parse_..</text>
</g>
<g >
<title>transformColumnRef (12,658,801,562 samples, 0.13%)</title><rect x="839.2" y="373" width="1.6" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="842.24" y="383.5" ></text>
</g>
<g >
<title>new_list (1,340,551,491 samples, 0.01%)</title><rect x="972.6" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="975.62" y="383.5" ></text>
</g>
<g >
<title>lappend (2,423,450,409 samples, 0.03%)</title><rect x="1057.5" y="405" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1060.53" y="415.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,162,250,303 samples, 0.01%)</title><rect x="302.1" y="229" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="305.10" y="239.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64_impl (1,616,952,486 samples, 0.02%)</title><rect x="505.4" y="469" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="508.44" y="479.5" ></text>
</g>
<g >
<title>pfree (1,443,273,435 samples, 0.02%)</title><rect x="944.6" y="373" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="947.58" y="383.5" ></text>
</g>
<g >
<title>hash_search (4,182,426,571 samples, 0.04%)</title><rect x="862.1" y="453" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="865.11" y="463.5" ></text>
</g>
<g >
<title>palloc0 (4,055,598,685 samples, 0.04%)</title><rect x="1121.1" y="725" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1124.12" y="735.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,098,251,836 samples, 0.01%)</title><rect x="846.5" y="373" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="849.46" y="383.5" ></text>
</g>
<g >
<title>cost_qual_eval (17,179,399,645 samples, 0.18%)</title><rect x="1038.1" y="389" width="2.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="1041.13" y="399.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,075,132,847 samples, 0.01%)</title><rect x="296.8" y="213" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="299.84" y="223.5" ></text>
</g>
<g >
<title>palloc0 (1,955,876,135 samples, 0.02%)</title><rect x="954.3" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="957.32" y="447.5" ></text>
</g>
<g >
<title>tag_hash (3,460,716,433 samples, 0.04%)</title><rect x="923.7" y="373" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="926.68" y="383.5" ></text>
</g>
<g >
<title>ExecInitFunc (17,088,975,546 samples, 0.18%)</title><rect x="419.4" y="341" width="2.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="422.37" y="351.5" ></text>
</g>
<g >
<title>newNode (6,860,903,007 samples, 0.07%)</title><rect x="949.4" y="421" width="0.8" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="952.38" y="431.5" ></text>
</g>
<g >
<title>palloc (1,102,137,313 samples, 0.01%)</title><rect x="896.4" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="899.45" y="431.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,411,572,167 samples, 0.05%)</title><rect x="960.9" y="341" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="963.89" y="351.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="309" width="0.7" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="87.72" y="319.5" ></text>
</g>
<g >
<title>hash_initial_lookup (992,096,081 samples, 0.01%)</title><rect x="444.5" y="309" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="447.53" y="319.5" ></text>
</g>
<g >
<title>palloc (1,454,288,519 samples, 0.02%)</title><rect x="375.0" y="325" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="377.96" y="335.5" ></text>
</g>
<g >
<title>ReadBufferExtended (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="261" width="0.7" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="87.72" y="271.5" ></text>
</g>
<g >
<title>StartReadBuffer (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="213" width="0.1" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="1161.26" y="223.5" ></text>
</g>
<g >
<title>newNode (2,271,515,298 samples, 0.02%)</title><rect x="853.6" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="856.59" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (1,954,419,081 samples, 0.02%)</title><rect x="212.4" y="517" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="215.39" y="527.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32_impl (1,471,284,667 samples, 0.02%)</title><rect x="352.6" y="245" width="0.2" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="355.60" y="255.5" ></text>
</g>
<g >
<title>LockHeldByMe (10,831,507,295 samples, 0.11%)</title><rect x="923.3" y="421" width="1.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="926.25" y="431.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,918,099,958 samples, 0.02%)</title><rect x="490.5" y="453" width="0.3" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="493.53" y="463.5" ></text>
</g>
<g >
<title>LockHeldByMe (5,136,126,372 samples, 0.05%)</title><rect x="810.9" y="437" width="0.6" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="813.90" y="447.5" ></text>
</g>
<g >
<title>palloc0 (4,811,395,854 samples, 0.05%)</title><rect x="808.9" y="469" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="811.93" y="479.5" ></text>
</g>
<g >
<title>update_load_avg (2,364,090,286 samples, 0.03%)</title><rect x="204.5" y="277" width="0.3" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="207.50" y="287.5" ></text>
</g>
<g >
<title>bms_copy (1,940,280,520 samples, 0.02%)</title><rect x="877.4" y="469" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="880.37" y="479.5" ></text>
</g>
<g >
<title>put_prev_task_fair (1,576,824,359 samples, 0.02%)</title><rect x="160.6" y="325" width="0.2" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="163.56" y="335.5" ></text>
</g>
<g >
<title>AssertBufferLocksPermitCatalogRead (1,067,692,530 samples, 0.01%)</title><rect x="30.4" y="741" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="33.36" y="751.5" ></text>
</g>
<g >
<title>palloc0 (1,426,841,158 samples, 0.02%)</title><rect x="1070.4" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1073.40" y="447.5" ></text>
</g>
<g >
<title>XLogBeginInsert (1,518,171,410 samples, 0.02%)</title><rect x="109.1" y="757" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="112.08" y="767.5" ></text>
</g>
<g >
<title>palloc0 (3,055,741,479 samples, 0.03%)</title><rect x="919.6" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="922.55" y="463.5" ></text>
</g>
<g >
<title>pg_client_to_server (1,607,961,122 samples, 0.02%)</title><rect x="1168.2" y="757" width="0.2" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1171.24" y="767.5" ></text>
</g>
<g >
<title>RelationGetIndexPredicate (1,186,581,437 samples, 0.01%)</title><rect x="392.4" y="373" width="0.1" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="395.39" y="383.5" ></text>
</g>
<g >
<title>ExecMaterializeSlot (22,470,992,109 samples, 0.24%)</title><rect x="388.8" y="405" width="2.8" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="391.83" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,672,676,872 samples, 0.04%)</title><rect x="226.4" y="549" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="229.40" y="559.5" ></text>
</g>
<g >
<title>ResourceOwnerEnlarge (1,680,197,108 samples, 0.02%)</title><rect x="90.8" y="757" width="0.2" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="93.76" y="767.5" ></text>
</g>
<g >
<title>LockErrorCleanup (1,181,764,675 samples, 0.01%)</title><rect x="511.2" y="469" width="0.2" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="514.22" y="479.5" ></text>
</g>
<g >
<title>ItemPointerGetOffsetNumberNoCheck (2,302,812,315 samples, 0.02%)</title><rect x="55.3" y="757" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="58.31" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (853,658,537 samples, 0.01%)</title><rect x="93.2" y="757" width="0.1" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="96.21" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,284,959,160 samples, 0.02%)</title><rect x="394.4" y="309" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="397.41" y="319.5" ></text>
</g>
<g >
<title>__memcg_slab_free_hook (3,436,721,256 samples, 0.04%)</title><rect x="181.8" y="357" width="0.4" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="184.77" y="367.5" ></text>
</g>
<g >
<title>relation_open (15,075,674,704 samples, 0.16%)</title><rect x="1067.3" y="469" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1070.29" y="479.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (843,170,650 samples, 0.01%)</title><rect x="968.1" y="325" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="971.05" y="335.5" ></text>
</g>
<g >
<title>new_list (2,067,590,915 samples, 0.02%)</title><rect x="858.5" y="501" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="861.49" y="511.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,159,311,281 samples, 0.01%)</title><rect x="212.1" y="517" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="215.09" y="527.5" ></text>
</g>
<g >
<title>pgstat_report_activity (6,864,929,428 samples, 0.07%)</title><rect x="1072.1" y="581" width="0.9" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1075.09" y="591.5" ></text>
</g>
<g >
<title>XLogInsertRecord (33,551,283,699 samples, 0.36%)</title><rect x="378.5" y="325" width="4.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="381.53" y="335.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (14,757,981,681 samples, 0.16%)</title><rect x="1038.4" y="357" width="1.9" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1041.43" y="367.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (894,502,187 samples, 0.01%)</title><rect x="828.9" y="309" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="831.92" y="319.5" ></text>
</g>
<g >
<title>FreeExprContext (21,726,244,092 samples, 0.23%)</title><rect x="249.8" y="485" width="2.7" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="252.82" y="495.5" ></text>
</g>
<g >
<title>ExecutorRun (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="549" width="1.9" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="127.59" y="559.5" ></text>
</g>
<g >
<title>XactLockTableInsert (52,917,824,329 samples, 0.56%)</title><rect x="352.9" y="325" width="6.6" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="355.90" y="335.5" ></text>
</g>
<g >
<title>new_list (3,826,010,468 samples, 0.04%)</title><rect x="992.2" y="341" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="995.23" y="351.5" ></text>
</g>
<g >
<title>ReleaseSysCache (975,904,025 samples, 0.01%)</title><rect x="916.5" y="373" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="919.47" y="383.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (2,415,744,047 samples, 0.03%)</title><rect x="917.4" y="389" width="0.3" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="920.45" y="399.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_out (9,965,080,953 samples, 0.11%)</title><rect x="162.3" y="325" width="1.2" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="165.30" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (985,310,139 samples, 0.01%)</title><rect x="434.3" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="437.30" y="351.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (5,424,452,157 samples, 0.06%)</title><rect x="421.7" y="341" width="0.7" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="424.70" y="351.5" ></text>
</g>
<g >
<title>skb_copy_datagram_from_iter (7,887,466,457 samples, 0.08%)</title><rect x="192.9" y="405" width="0.9" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="195.85" y="415.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="725" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.04" y="735.5" ></text>
</g>
<g >
<title>BufferIsValid (1,416,243,865 samples, 0.02%)</title><rect x="112.1" y="741" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="115.09" y="751.5" ></text>
</g>
<g >
<title>makeIntConst (4,719,831,293 samples, 0.05%)</title><rect x="1117.6" y="741" width="0.6" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1120.61" y="751.5" ></text>
</g>
<g >
<title>log_heap_update (82,964,585,995 samples, 0.88%)</title><rect x="376.7" y="357" width="10.4" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="379.70" y="367.5" ></text>
</g>
<g >
<title>extract_nonindex_conditions (3,365,765,620 samples, 0.04%)</title><rect x="1016.1" y="325" width="0.5" height="15.0" fill="rgb(216,54,13)" rx="2" ry="2" />
<text  x="1019.15" y="335.5" ></text>
</g>
<g >
<title>ReadBuffer (1,217,297,839 samples, 0.01%)</title><rect x="124.1" y="245" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="127.08" y="255.5" ></text>
</g>
<g >
<title>ExecutorStart (395,843,700,216 samples, 4.19%)</title><rect x="403.2" y="533" width="49.5" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="406.20" y="543.5" >Exec..</text>
</g>
<g >
<title>GetTransactionSnapshot (34,827,388,444 samples, 0.37%)</title><rect x="217.3" y="581" width="4.3" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="220.26" y="591.5" ></text>
</g>
<g >
<title>psi_task_change (954,993,170 samples, 0.01%)</title><rect x="204.8" y="293" width="0.1" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="207.81" y="303.5" ></text>
</g>
<g >
<title>set_next_task_idle (1,222,531,555 samples, 0.01%)</title><rect x="477.2" y="261" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="480.24" y="271.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,002,258,860 samples, 0.01%)</title><rect x="97.8" y="165" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="100.83" y="175.5" ></text>
</g>
<g >
<title>cfree@GLIBC_2.2.5 (6,344,392,543 samples, 0.07%)</title><rect x="147.1" y="549" width="0.8" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="150.11" y="559.5" ></text>
</g>
<g >
<title>new_list (1,520,754,044 samples, 0.02%)</title><rect x="1048.3" y="485" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1051.28" y="495.5" ></text>
</g>
<g >
<title>BufferGetPage (970,461,366 samples, 0.01%)</title><rect x="335.8" y="197" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="338.76" y="207.5" ></text>
</g>
<g >
<title>__ceil_sse41 (1,313,311,658 samples, 0.01%)</title><rect x="1012.2" y="293" width="0.1" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1015.15" y="303.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (4,343,470,434 samples, 0.05%)</title><rect x="256.3" y="421" width="0.6" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="259.31" y="431.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (803,901,079 samples, 0.01%)</title><rect x="126.6" y="229" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="129.56" y="239.5" ></text>
</g>
<g >
<title>ExecReadyExpr (3,726,852,257 samples, 0.04%)</title><rect x="272.3" y="405" width="0.5" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="275.29" y="415.5" ></text>
</g>
<g >
<title>ReadBuffer (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="261" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="1161.26" y="271.5" ></text>
</g>
<g >
<title>heap_page_prune_and_freeze (8,508,876,719 samples, 0.09%)</title><rect x="1148.0" y="741" width="1.0" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1150.97" y="751.5" ></text>
</g>
<g >
<title>sock_alloc_send_pskb (30,486,170,407 samples, 0.32%)</title><rect x="193.9" y="405" width="3.8" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="196.86" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,404,061,525 samples, 0.01%)</title><rect x="508.1" y="389" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="511.11" y="399.5" ></text>
</g>
<g >
<title>preprocess_expression (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="533" width="0.1" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1161.36" y="543.5" ></text>
</g>
<g >
<title>new_list (1,825,100,980 samples, 0.02%)</title><rect x="393.8" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="396.79" y="367.5" ></text>
</g>
<g >
<title>new_list (2,288,947,187 samples, 0.02%)</title><rect x="860.0" y="485" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="862.96" y="495.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (820,415,596 samples, 0.01%)</title><rect x="971.1" y="309" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="974.15" y="319.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,222,645,769 samples, 0.01%)</title><rect x="1030.8" y="229" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1033.78" y="239.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="549" width="6.7" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="97.67" y="559.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,144,889,166 samples, 0.01%)</title><rect x="941.6" y="309" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="944.58" y="319.5" ></text>
</g>
<g >
<title>find_duplicate_ors (987,294,575 samples, 0.01%)</title><rect x="1055.3" y="453" width="0.1" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="1058.32" y="463.5" ></text>
</g>
<g >
<title>BufferIsValid (7,179,375,854 samples, 0.08%)</title><rect x="36.3" y="757" width="0.9" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="39.31" y="767.5" ></text>
</g>
<g >
<title>index_getnext_slot (17,824,932,041 samples, 0.19%)</title><rect x="280.0" y="341" width="2.2" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="282.98" y="351.5" ></text>
</g>
<g >
<title>fdget_pos (3,257,099,698 samples, 0.03%)</title><rect x="937.2" y="165" width="0.4" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="940.22" y="175.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (1,016,382,047 samples, 0.01%)</title><rect x="382.3" y="69" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="385.25" y="79.5" ></text>
</g>
<g >
<title>mutex_lock (2,661,730,368 samples, 0.03%)</title><rect x="157.7" y="373" width="0.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="160.68" y="383.5" ></text>
</g>
<g >
<title>__errno_location (2,510,860,208 samples, 0.03%)</title><rect x="151.3" y="501" width="0.3" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="154.29" y="511.5" ></text>
</g>
<g >
<title>BufferDescriptorGetBuffer (3,320,427,863 samples, 0.04%)</title><rect x="97.1" y="165" width="0.4" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="100.07" y="175.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,675,091,634 samples, 0.06%)</title><rect x="902.3" y="373" width="0.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="905.26" y="383.5" ></text>
</g>
<g >
<title>_bt_getbuf (1,932,277,437 samples, 0.02%)</title><rect x="126.0" y="245" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="129.00" y="255.5" ></text>
</g>
<g >
<title>new_list (1,720,820,747 samples, 0.02%)</title><rect x="847.4" y="389" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="850.45" y="399.5" ></text>
</g>
<g >
<title>preprocess_rowmarks (11,177,214,061 samples, 0.12%)</title><rect x="1069.2" y="501" width="1.4" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="1072.18" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,520,120,280 samples, 0.02%)</title><rect x="275.3" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="278.26" y="383.5" ></text>
</g>
<g >
<title>check_lock_if_inplace_updateable_rel (975,063,221 samples, 0.01%)</title><rect x="1126.3" y="757" width="0.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1129.34" y="767.5" ></text>
</g>
<g >
<title>select_task_rq (28,258,298,894 samples, 0.30%)</title><rect x="199.5" y="325" width="3.5" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="202.46" y="335.5" ></text>
</g>
<g >
<title>palloc0 (5,402,732,582 samples, 0.06%)</title><rect x="442.1" y="405" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="445.06" y="415.5" ></text>
</g>
<g >
<title>WalSndWakeupProcessRequests (5,915,487,806 samples, 0.06%)</title><rect x="491.5" y="485" width="0.7" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="494.49" y="495.5" ></text>
</g>
<g >
<title>clause_selectivity_ext (1,004,575,219 samples, 0.01%)</title><rect x="1012.5" y="261" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1015.51" y="271.5" ></text>
</g>
<g >
<title>new_list (4,137,929,705 samples, 0.04%)</title><rect x="1018.1" y="341" width="0.5" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1021.07" y="351.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="309" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="127.24" y="319.5" ></text>
</g>
<g >
<title>murmurhash32 (1,193,479,504 samples, 0.01%)</title><rect x="825.2" y="277" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="828.18" y="287.5" ></text>
</g>
<g >
<title>AtEOXact_RelationMap (2,609,988,942 samples, 0.03%)</title><rect x="463.7" y="517" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="466.68" y="527.5" ></text>
</g>
<g >
<title>new_list (1,775,622,503 samples, 0.02%)</title><rect x="1020.7" y="293" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1023.70" y="303.5" ></text>
</g>
<g >
<title>IndexNext (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="389" width="0.5" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="1160.94" y="399.5" ></text>
</g>
<g >
<title>pg_plan_query (2,826,087,191 samples, 0.03%)</title><rect x="124.2" y="597" width="0.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="127.24" y="607.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,468,604,515 samples, 0.04%)</title><rect x="836.1" y="325" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="839.06" y="335.5" ></text>
</g>
<g >
<title>pstrdup (2,589,197,096 samples, 0.03%)</title><rect x="921.4" y="437" width="0.3" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="924.40" y="447.5" ></text>
</g>
<g >
<title>__memcg_slab_post_alloc_hook (3,696,150,877 samples, 0.04%)</title><rect x="195.1" y="325" width="0.5" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="198.14" y="335.5" ></text>
</g>
<g >
<title>new_list (1,548,060,476 samples, 0.02%)</title><rect x="981.7" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="984.69" y="399.5" ></text>
</g>
<g >
<title>lappend (2,408,299,604 samples, 0.03%)</title><rect x="970.3" y="373" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="973.27" y="383.5" ></text>
</g>
<g >
<title>makeAlias (6,182,541,227 samples, 0.07%)</title><rect x="815.6" y="453" width="0.8" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="818.63" y="463.5" ></text>
</g>
<g >
<title>scm_recv_unix (2,332,158,925 samples, 0.02%)</title><rect x="182.8" y="373" width="0.3" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="185.79" y="383.5" ></text>
</g>
<g >
<title>__libc_start_main@@GLIBC_2.34 (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="757" width="2.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="127.59" y="767.5" ></text>
</g>
<g >
<title>palloc0 (3,333,922,818 samples, 0.04%)</title><rect x="906.1" y="485" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="909.06" y="495.5" ></text>
</g>
<g >
<title>BTreeTupleIsPosting (960,846,788 samples, 0.01%)</title><rect x="331.4" y="213" width="0.2" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="334.43" y="223.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,218,169,166 samples, 0.01%)</title><rect x="35.2" y="757" width="0.1" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="38.19" y="767.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (968,345,408 samples, 0.01%)</title><rect x="303.4" y="197" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="306.36" y="207.5" ></text>
</g>
<g >
<title>choose_bitmap_and (2,229,172,097 samples, 0.02%)</title><rect x="989.5" y="389" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="992.54" y="399.5" ></text>
</g>
<g >
<title>palloc (1,082,301,332 samples, 0.01%)</title><rect x="1048.3" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1051.32" y="479.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (975,684,566 samples, 0.01%)</title><rect x="1007.8" y="149" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1010.81" y="159.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,996,572,605 samples, 0.06%)</title><rect x="864.1" y="485" width="0.8" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="867.13" y="495.5" ></text>
</g>
<g >
<title>create_indexscan_plan (46,489,542,884 samples, 0.49%)</title><rect x="886.4" y="405" width="5.8" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="889.38" y="415.5" ></text>
</g>
<g >
<title>add_function_cost (4,989,405,707 samples, 0.05%)</title><rect x="1045.9" y="437" width="0.6" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="1048.87" y="447.5" ></text>
</g>
<g >
<title>index_getnext_tid (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="325" width="1.9" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="127.59" y="335.5" ></text>
</g>
<g >
<title>PortalRun (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="645" width="2.8" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="85.62" y="655.5" ></text>
</g>
<g >
<title>ModifyWaitEvent (1,579,578,658 samples, 0.02%)</title><rect x="150.8" y="517" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="153.79" y="527.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,760,180,403 samples, 0.04%)</title><rect x="1034.7" y="133" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1037.73" y="143.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (1,436,419,082 samples, 0.02%)</title><rect x="248.5" y="437" width="0.2" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="251.55" y="447.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,710,727,735 samples, 0.03%)</title><rect x="398.5" y="373" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="401.46" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,207,431,016 samples, 0.01%)</title><rect x="307.4" y="117" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="310.40" y="127.5" ></text>
</g>
<g >
<title>get_futex_key (3,598,816,503 samples, 0.04%)</title><rect x="485.7" y="325" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="488.74" y="335.5" ></text>
</g>
<g >
<title>make_one_rel (1,163,868,766 samples, 0.01%)</title><rect x="126.5" y="485" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="129.52" y="495.5" ></text>
</g>
<g >
<title>schedule (44,914,714,941 samples, 0.48%)</title><rect x="476.2" y="309" width="5.6" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="479.23" y="319.5" ></text>
</g>
<g >
<title>palloc0 (3,709,452,243 samples, 0.04%)</title><rect x="1117.7" y="709" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1120.73" y="719.5" ></text>
</g>
<g >
<title>table_relation_estimate_size (29,265,790,846 samples, 0.31%)</title><rect x="935.0" y="389" width="3.6" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="937.96" y="399.5" ></text>
</g>
<g >
<title>new_list (1,619,025,464 samples, 0.02%)</title><rect x="884.8" y="405" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="887.81" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerReleaseInternal (121,593,685,990 samples, 1.29%)</title><rect x="510.7" y="501" width="15.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="513.73" y="511.5" ></text>
</g>
<g >
<title>try_to_block_task.constprop.0.isra.0 (20,319,229,734 samples, 0.22%)</title><rect x="479.1" y="277" width="2.6" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="482.13" y="287.5" ></text>
</g>
<g >
<title>ServerLoop (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="741" width="3.0" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="85.62" y="751.5" ></text>
</g>
<g >
<title>AllocSetCheck (2,079,723,834 samples, 0.02%)</title><rect x="457.6" y="565" width="0.3" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="460.60" y="575.5" ></text>
</g>
<g >
<title>SearchCatCache3 (6,512,743,256 samples, 0.07%)</title><rect x="1041.3" y="341" width="0.8" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1044.26" y="351.5" ></text>
</g>
<g >
<title>set_rel_size (128,875,238,762 samples, 1.37%)</title><rect x="1026.8" y="437" width="16.1" height="15.0" fill="rgb(243,175,42)" rx="2" ry="2" />
<text  x="1029.82" y="447.5" ></text>
</g>
<g >
<title>pg_comp_crc32c_sse42 (926,970,170 samples, 0.01%)</title><rect x="1168.4" y="757" width="0.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="1171.44" y="767.5" ></text>
</g>
<g >
<title>buildNSItemFromTupleDesc (7,971,159,674 samples, 0.08%)</title><rect x="812.3" y="453" width="1.0" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="815.26" y="463.5" ></text>
</g>
<g >
<title>new_list (2,136,242,179 samples, 0.02%)</title><rect x="983.7" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="986.70" y="431.5" ></text>
</g>
<g >
<title>__errno_location (847,184,047 samples, 0.01%)</title><rect x="492.9" y="469" width="0.1" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="495.90" y="479.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (2,035,435,790 samples, 0.02%)</title><rect x="1059.4" y="437" width="0.3" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="1062.40" y="447.5" ></text>
</g>
<g >
<title>planner (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="629" width="3.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="104.45" y="639.5" ></text>
</g>
<g >
<title>create_projection_plan (66,560,528,221 samples, 0.71%)</title><rect x="884.1" y="453" width="8.3" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="887.09" y="463.5" ></text>
</g>
<g >
<title>UnGrantLock (1,976,558,730 samples, 0.02%)</title><rect x="522.1" y="453" width="0.3" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="525.15" y="463.5" ></text>
</g>
<g >
<title>transformStmt (450,184,607,096 samples, 4.77%)</title><rect x="800.6" y="517" width="56.3" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="803.58" y="527.5" >trans..</text>
</g>
<g >
<title>AllocSetAlloc (2,183,393,624 samples, 0.02%)</title><rect x="428.4" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="431.42" y="399.5" ></text>
</g>
<g >
<title>lcons (2,584,731,637 samples, 0.03%)</title><rect x="910.4" y="453" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="913.39" y="463.5" ></text>
</g>
<g >
<title>smgrnblocks (10,483,722,019 samples, 0.11%)</title><rect x="931.9" y="389" width="1.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="934.88" y="399.5" ></text>
</g>
<g >
<title>palloc0 (2,845,613,098 samples, 0.03%)</title><rect x="977.4" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="980.39" y="431.5" ></text>
</g>
<g >
<title>ExecModifyTable (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="501" width="0.5" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="1160.94" y="511.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,848,294,780 samples, 0.06%)</title><rect x="1021.8" y="245" width="0.7" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1024.77" y="255.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (2,768,473,694 samples, 0.03%)</title><rect x="82.6" y="325" width="0.4" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="85.62" y="335.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (2,421,126,990 samples, 0.03%)</title><rect x="272.0" y="389" width="0.3" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="274.99" y="399.5" ></text>
</g>
<g >
<title>__update_load_avg_se (871,708,146 samples, 0.01%)</title><rect x="481.3" y="197" width="0.1" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="484.34" y="207.5" ></text>
</g>
<g >
<title>__vdso_gettimeofday (1,000,322,092 samples, 0.01%)</title><rect x="207.8" y="565" width="0.1" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="210.81" y="575.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="613" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.04" y="623.5" ></text>
</g>
<g >
<title>list_make1_impl (2,011,925,160 samples, 0.02%)</title><rect x="1048.2" y="501" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1051.22" y="511.5" ></text>
</g>
<g >
<title>create_seqscan_path (14,368,907,245 samples, 0.15%)</title><rect x="1022.6" y="405" width="1.8" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1025.57" y="415.5" ></text>
</g>
<g >
<title>pfree (1,474,603,754 samples, 0.02%)</title><rect x="993.9" y="309" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="996.93" y="319.5" ></text>
</g>
<g >
<title>pgstat_count_slru_blocks_hit (921,505,580 samples, 0.01%)</title><rect x="307.8" y="149" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="310.75" y="159.5" ></text>
</g>
<g >
<title>ExecGetRangeTableRelation (1,522,984,930 samples, 0.02%)</title><rect x="439.7" y="405" width="0.2" height="15.0" fill="rgb(241,167,39)" rx="2" ry="2" />
<text  x="442.68" y="415.5" ></text>
</g>
<g >
<title>BufferGetBlockNumber (1,434,718,407 samples, 0.02%)</title><rect x="301.9" y="245" width="0.2" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="304.92" y="255.5" ></text>
</g>
<g >
<title>HeapDetermineColumnsInfo (15,245,372,907 samples, 0.16%)</title><rect x="359.6" y="357" width="1.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="362.55" y="367.5" ></text>
</g>
<g >
<title>xas_start (1,699,342,556 samples, 0.02%)</title><rect x="499.7" y="293" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="502.73" y="303.5" ></text>
</g>
<g >
<title>ItemPointerEquals (3,101,488,775 samples, 0.03%)</title><rect x="293.6" y="309" width="0.4" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="296.62" y="319.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="725" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1136.99" y="735.5" ></text>
</g>
<g >
<title>AllocSetFree (3,931,144,336 samples, 0.04%)</title><rect x="521.3" y="421" width="0.5" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="524.30" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (911,182,199 samples, 0.01%)</title><rect x="90.0" y="757" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="93.00" y="767.5" ></text>
</g>
<g >
<title>palloc (1,055,790,557 samples, 0.01%)</title><rect x="1057.7" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1060.67" y="383.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (4,658,756,202 samples, 0.05%)</title><rect x="24.7" y="741" width="0.6" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="27.72" y="751.5" ></text>
</g>
<g >
<title>ReadBuffer (18,810,485,137 samples, 0.20%)</title><rect x="366.4" y="357" width="2.3" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="369.39" y="367.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,558,426,216 samples, 0.02%)</title><rect x="436.2" y="309" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="439.22" y="319.5" ></text>
</g>
<g >
<title>LockReleaseAll (1,322,036,205 samples, 0.01%)</title><rect x="58.4" y="757" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="61.39" y="767.5" ></text>
</g>
<g >
<title>__virt_addr_valid (3,630,650,035 samples, 0.04%)</title><rect x="193.1" y="357" width="0.5" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="196.11" y="367.5" ></text>
</g>
<g >
<title>_bt_getbuf (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="293" width="1.5" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="86.22" y="303.5" ></text>
</g>
<g >
<title>ExecutorRun (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="613" width="6.7" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="97.67" y="623.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,687,286,754 samples, 0.02%)</title><rect x="470.3" y="357" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="473.35" y="367.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,845,059,265 samples, 0.07%)</title><rect x="861.2" y="453" width="0.9" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="864.25" y="463.5" ></text>
</g>
<g >
<title>ExecProcNode (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="533" width="0.5" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="1160.94" y="543.5" ></text>
</g>
<g >
<title>TransactionGroupUpdateXidStatus (5,261,457,778 samples, 0.06%)</title><rect x="470.8" y="453" width="0.7" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="473.83" y="463.5" ></text>
</g>
<g >
<title>bms_difference (2,559,732,497 samples, 0.03%)</title><rect x="966.4" y="373" width="0.4" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="969.44" y="383.5" ></text>
</g>
<g >
<title>MemoryContextSwitchTo (3,510,045,615 samples, 0.04%)</title><rect x="78.0" y="757" width="0.4" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="81.00" y="767.5" ></text>
</g>
<g >
<title>newNode (3,615,249,357 samples, 0.04%)</title><rect x="814.3" y="421" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="817.28" y="431.5" ></text>
</g>
<g >
<title>newNode (2,787,766,733 samples, 0.03%)</title><rect x="921.9" y="437" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="924.93" y="447.5" ></text>
</g>
<g >
<title>pg_utf8_verifystr (9,133,550,721 samples, 0.10%)</title><rect x="1081.9" y="533" width="1.2" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1084.94" y="543.5" ></text>
</g>
<g >
<title>clauselist_selectivity (1,653,989,611 samples, 0.02%)</title><rect x="1012.5" y="293" width="0.2" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="1015.46" y="303.5" ></text>
</g>
<g >
<title>tag_hash (3,897,863,881 samples, 0.04%)</title><rect x="861.6" y="421" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="864.61" y="431.5" ></text>
</g>
<g >
<title>SearchCatCache1 (6,922,868,022 samples, 0.07%)</title><rect x="423.2" y="325" width="0.9" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="426.21" y="335.5" ></text>
</g>
<g >
<title>get_tablespace (3,548,633,453 samples, 0.04%)</title><rect x="990.8" y="341" width="0.4" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="993.79" y="351.5" ></text>
</g>
<g >
<title>sysvec_thermal (1,075,229,263 samples, 0.01%)</title><rect x="791.7" y="501" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="794.69" y="511.5" ></text>
</g>
<g >
<title>hash_search (3,690,597,177 samples, 0.04%)</title><rect x="924.1" y="405" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="927.14" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,092,106,222 samples, 0.02%)</title><rect x="490.1" y="421" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="493.15" y="431.5" ></text>
</g>
<g >
<title>palloc (1,407,086,535 samples, 0.01%)</title><rect x="878.9" y="437" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="881.86" y="447.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,943,355,936 samples, 0.02%)</title><rect x="855.6" y="309" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="858.62" y="319.5" ></text>
</g>
<g >
<title>list_nth_cell (1,002,197,072 samples, 0.01%)</title><rect x="856.7" y="389" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="859.72" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,160,922,356 samples, 0.01%)</title><rect x="398.8" y="373" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="401.80" y="383.5" ></text>
</g>
<g >
<title>uint32_hash (1,608,792,599 samples, 0.02%)</title><rect x="991.0" y="309" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="994.03" y="319.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="501" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1161.36" y="511.5" ></text>
</g>
<g >
<title>generate_base_implied_equalities_const (9,461,083,912 samples, 0.10%)</title><rect x="981.2" y="453" width="1.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="984.20" y="463.5" ></text>
</g>
<g >
<title>ProcArrayEndTransaction (815,927,151 samples, 0.01%)</title><rect x="86.4" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="89.42" y="767.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (33,532,079,411 samples, 0.36%)</title><rect x="1003.8" y="197" width="4.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1006.84" y="207.5" ></text>
</g>
<g >
<title>LockHeldByMe (7,970,980,310 samples, 0.08%)</title><rect x="828.0" y="373" width="1.0" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="831.04" y="383.5" ></text>
</g>
<g >
<title>malloc (2,651,562,701 samples, 0.03%)</title><rect x="912.5" y="405" width="0.4" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="915.52" y="415.5" ></text>
</g>
<g >
<title>deconstruct_jointree (178,582,971,348 samples, 1.89%)</title><rect x="957.1" y="469" width="22.4" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="960.14" y="479.5" >d..</text>
</g>
<g >
<title>PostgresMain (81,441,377,880 samples, 0.86%)</title><rect x="94.7" y="693" width="10.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="97.67" y="703.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,674,174,199 samples, 0.02%)</title><rect x="344.1" y="293" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="347.08" y="303.5" ></text>
</g>
<g >
<title>newNode (27,198,529,610 samples, 0.29%)</title><rect x="1048.6" y="501" width="3.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1051.61" y="511.5" ></text>
</g>
<g >
<title>XLogWrite (104,293,958,364 samples, 1.11%)</title><rect x="492.3" y="485" width="13.0" height="15.0" fill="rgb(243,175,42)" rx="2" ry="2" />
<text  x="495.27" y="495.5" ></text>
</g>
<g >
<title>list_free_private (1,641,171,714 samples, 0.02%)</title><rect x="954.6" y="437" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="957.59" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,135,026,398 samples, 0.01%)</title><rect x="417.2" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="420.23" y="303.5" ></text>
</g>
<g >
<title>heap_page_prune_opt (9,066,291,436 samples, 0.10%)</title><rect x="1147.9" y="757" width="1.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1150.90" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (9,168,220,771 samples, 0.10%)</title><rect x="997.2" y="309" width="1.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1000.24" y="319.5" ></text>
</g>
<g >
<title>standard_ExecutorRun (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="549" width="1.7" height="15.0" fill="rgb(247,196,47)" rx="2" ry="2" />
<text  x="125.45" y="559.5" ></text>
</g>
<g >
<title>PortalRunMulti (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="645" width="6.7" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="97.67" y="655.5" ></text>
</g>
<g >
<title>PinBufferForBlock (16,885,685,466 samples, 0.18%)</title><rect x="399.6" y="309" width="2.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="402.61" y="319.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,597,697,164 samples, 0.05%)</title><rect x="1047.6" y="421" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1050.61" y="431.5" ></text>
</g>
<g >
<title>AllocSetFree (2,050,119,159 samples, 0.02%)</title><rect x="187.2" y="549" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="190.24" y="559.5" ></text>
</g>
<g >
<title>unlink_chunk.isra.0 (1,136,090,054 samples, 0.01%)</title><rect x="292.8" y="197" width="0.2" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="295.83" y="207.5" ></text>
</g>
<g >
<title>palloc0 (1,871,747,785 samples, 0.02%)</title><rect x="860.3" y="501" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="863.32" y="511.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,385,229,393 samples, 0.03%)</title><rect x="1165.0" y="741" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1168.02" y="751.5" ></text>
</g>
<g >
<title>uint32_hash (1,483,475,017 samples, 0.02%)</title><rect x="1066.8" y="453" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1069.77" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,730,810,649 samples, 0.05%)</title><rect x="802.9" y="373" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="805.90" y="383.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (6,666,748,927 samples, 0.07%)</title><rect x="423.2" y="309" width="0.9" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="426.24" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,059,131,075 samples, 0.01%)</title><rect x="847.5" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="850.52" y="367.5" ></text>
</g>
<g >
<title>get_op_opfamily_strategy (8,176,518,950 samples, 0.09%)</title><rect x="1014.3" y="309" width="1.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1017.33" y="319.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (7,808,261,197 samples, 0.08%)</title><rect x="459.7" y="485" width="1.0" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="462.69" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,420,192,478 samples, 0.02%)</title><rect x="973.1" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="976.12" y="367.5" ></text>
</g>
<g >
<title>ReleaseSysCache (2,080,572,717 samples, 0.02%)</title><rect x="90.2" y="757" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="93.20" y="767.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (1,292,560,940 samples, 0.01%)</title><rect x="303.3" y="213" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="306.33" y="223.5" ></text>
</g>
<g >
<title>__check_heap_object (836,505,831 samples, 0.01%)</title><rect x="183.8" y="293" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="186.76" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,230,709,463 samples, 0.01%)</title><rect x="873.4" y="517" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="876.36" y="527.5" ></text>
</g>
<g >
<title>__libc_start_call_main (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="741" width="2.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="127.59" y="751.5" ></text>
</g>
<g >
<title>pfree (1,314,832,661 samples, 0.01%)</title><rect x="1069.8" y="469" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1072.78" y="479.5" ></text>
</g>
<g >
<title>PortalStart (8,844,201,837 samples, 0.09%)</title><rect x="454.7" y="581" width="1.2" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="457.75" y="591.5" ></text>
</g>
<g >
<title>standard_qp_callback (1,150,751,802 samples, 0.01%)</title><rect x="1184.5" y="757" width="0.2" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="1187.55" y="767.5" ></text>
</g>
<g >
<title>palloc (1,621,391,192 samples, 0.02%)</title><rect x="449.8" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="452.77" y="431.5" ></text>
</g>
<g >
<title>fmgr_info (3,469,940,489 samples, 0.04%)</title><rect x="1037.0" y="309" width="0.4" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="1039.95" y="319.5" ></text>
</g>
<g >
<title>palloc (2,331,707,684 samples, 0.02%)</title><rect x="234.5" y="517" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="237.46" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (922,567,553 samples, 0.01%)</title><rect x="448.7" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="451.74" y="383.5" ></text>
</g>
<g >
<title>process_equivalence (40,978,371,857 samples, 0.43%)</title><rect x="968.9" y="405" width="5.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="971.86" y="415.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (2,171,518,979 samples, 0.02%)</title><rect x="1046.7" y="405" width="0.3" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1049.71" y="415.5" ></text>
</g>
<g >
<title>newNode (2,683,461,947 samples, 0.03%)</title><rect x="896.6" y="453" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="899.60" y="463.5" ></text>
</g>
<g >
<title>AtEOXact_LargeObject (908,110,346 samples, 0.01%)</title><rect x="462.0" y="517" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="464.99" y="527.5" ></text>
</g>
<g >
<title>do_syscall_64 (65,672,740,014 samples, 0.70%)</title><rect x="493.5" y="437" width="8.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="496.48" y="447.5" ></text>
</g>
<g >
<title>palloc (3,083,161,260 samples, 0.03%)</title><rect x="951.5" y="405" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="954.48" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u64_impl (998,791,099 samples, 0.01%)</title><rect x="491.0" y="437" width="0.2" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="494.03" y="447.5" ></text>
</g>
<g >
<title>bms_copy (1,899,271,165 samples, 0.02%)</title><rect x="975.6" y="421" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="978.63" y="431.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,734,757,561 samples, 0.04%)</title><rect x="1059.2" y="453" width="0.5" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1062.18" y="463.5" ></text>
</g>
<g >
<title>lappend (2,196,780,006 samples, 0.02%)</title><rect x="893.1" y="453" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="896.07" y="463.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="661" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.04" y="671.5" ></text>
</g>
<g >
<title>ExecStorePinnedBufferHeapTuple (2,671,957,668 samples, 0.03%)</title><rect x="397.2" y="405" width="0.3" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="400.15" y="415.5" ></text>
</g>
<g >
<title>_bt_readpage (2,079,231,909 samples, 0.02%)</title><rect x="83.0" y="309" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="85.96" y="319.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (7,555,275,284 samples, 0.08%)</title><rect x="1135.5" y="757" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1138.46" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,051,670,352 samples, 0.01%)</title><rect x="1056.3" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1059.33" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (2,120,865,920 samples, 0.02%)</title><rect x="474.2" y="453" width="0.3" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="477.21" y="463.5" ></text>
</g>
<g >
<title>__libc_start_main@@GLIBC_2.34 (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="741" width="953.9" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="133.07" y="751.5" >__libc_start_main@@GLIBC_2.34</text>
</g>
<g >
<title>palloc0 (2,543,857,723 samples, 0.03%)</title><rect x="978.3" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="981.30" y="447.5" ></text>
</g>
<g >
<title>LockBuffer (3,441,328,013 samples, 0.04%)</title><rect x="339.3" y="213" width="0.4" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="342.30" y="223.5" ></text>
</g>
<g >
<title>pfree (5,041,799,362 samples, 0.05%)</title><rect x="521.2" y="437" width="0.7" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="524.23" y="447.5" ></text>
</g>
<g >
<title>transformAExprOp (98,582,555,706 samples, 1.04%)</title><rect x="844.5" y="437" width="12.3" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="847.52" y="447.5" ></text>
</g>
<g >
<title>create_seqscan_path (1,216,757,170 samples, 0.01%)</title><rect x="1130.6" y="757" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1133.57" y="767.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (827,869,453 samples, 0.01%)</title><rect x="471.0" y="405" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="473.95" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,389,202,554 samples, 0.01%)</title><rect x="816.9" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="819.90" y="431.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,032,839,131 samples, 0.02%)</title><rect x="466.4" y="453" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="469.37" y="463.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,753,223,390 samples, 0.04%)</title><rect x="836.0" y="357" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="839.02" y="367.5" ></text>
</g>
<g >
<title>UnpinBuffer (851,228,299 samples, 0.01%)</title><rect x="108.0" y="757" width="0.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="111.00" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,599,928,533 samples, 0.05%)</title><rect x="803.6" y="357" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="806.64" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (9,717,387,733 samples, 0.10%)</title><rect x="143.4" y="533" width="1.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="146.44" y="543.5" ></text>
</g>
<g >
<title>heap_prune_satisfies_vacuum (3,190,381,856 samples, 0.03%)</title><rect x="1148.6" y="709" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1151.63" y="719.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (988,161,197 samples, 0.01%)</title><rect x="102.8" y="421" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="105.75" y="431.5" ></text>
</g>
<g >
<title>ReleaseCatCache (2,134,193,276 samples, 0.02%)</title><rect x="435.3" y="341" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="438.34" y="351.5" ></text>
</g>
<g >
<title>RelationClose (2,358,123,629 samples, 0.02%)</title><rect x="865.3" y="485" width="0.3" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="868.27" y="495.5" ></text>
</g>
<g >
<title>PortalRun (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="613" width="1.7" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="125.45" y="623.5" ></text>
</g>
<g >
<title>hash_search (5,394,610,019 samples, 0.06%)</title><rect x="868.4" y="469" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="871.39" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,654,353,835 samples, 0.02%)</title><rect x="456.8" y="533" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="459.76" y="543.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,315,719,021 samples, 0.01%)</title><rect x="862.1" y="437" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="865.15" y="447.5" ></text>
</g>
<g >
<title>_bt_first (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="325" width="0.5" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="1160.94" y="335.5" ></text>
</g>
<g >
<title>tag_hash (3,797,902,078 samples, 0.04%)</title><rect x="867.6" y="437" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="870.61" y="447.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberCatCacheRef (1,080,132,254 samples, 0.01%)</title><rect x="826.2" y="309" width="0.1" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="829.16" y="319.5" ></text>
</g>
<g >
<title>check_enable_rls (7,323,046,490 samples, 0.08%)</title><rect x="864.0" y="501" width="0.9" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="866.96" y="511.5" ></text>
</g>
<g >
<title>palloc0 (4,563,725,334 samples, 0.05%)</title><rect x="393.0" y="341" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="395.97" y="351.5" ></text>
</g>
<g >
<title>hash_initial_lookup (972,526,286 samples, 0.01%)</title><rect x="821.5" y="341" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="824.51" y="351.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (3,366,648,300 samples, 0.04%)</title><rect x="338.5" y="197" width="0.4" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="341.46" y="207.5" ></text>
</g>
<g >
<title>update_process_times (1,613,611,367 samples, 0.02%)</title><rect x="750.5" y="421" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="753.47" y="431.5" ></text>
</g>
<g >
<title>pull_up_subqueries_recurse (2,474,716,427 samples, 0.03%)</title><rect x="1071.1" y="469" width="0.3" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1074.10" y="479.5" ></text>
</g>
<g >
<title>setTargetTable (172,693,408,262 samples, 1.83%)</title><rect x="809.6" y="485" width="21.6" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="812.56" y="495.5" >s..</text>
</g>
<g >
<title>IsTidRangeClause (1,874,727,000 samples, 0.02%)</title><rect x="1026.0" y="373" width="0.2" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1029.01" y="383.5" ></text>
</g>
<g >
<title>LockBuffer (3,172,973,216 samples, 0.03%)</title><rect x="325.3" y="197" width="0.4" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="328.26" y="207.5" ></text>
</g>
<g >
<title>markRTEForSelectPriv (1,885,385,960 samples, 0.02%)</title><rect x="840.3" y="309" width="0.2" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="843.31" y="319.5" ></text>
</g>
<g >
<title>hash_bytes (2,221,587,457 samples, 0.02%)</title><rect x="443.6" y="357" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="446.58" y="367.5" ></text>
</g>
<g >
<title>XLogInsert (49,913,954,668 samples, 0.53%)</title><rect x="378.4" y="341" width="6.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="381.38" y="351.5" ></text>
</g>
<g >
<title>ensure_tabstat_xact_level (1,067,526,090 samples, 0.01%)</title><rect x="1132.9" y="757" width="0.1" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1135.91" y="767.5" ></text>
</g>
<g >
<title>palloc (2,756,130,897 samples, 0.03%)</title><rect x="890.7" y="341" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="893.71" y="351.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (817,927,632 samples, 0.01%)</title><rect x="960.4" y="261" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="963.36" y="271.5" ></text>
</g>
<g >
<title>LockHeldByMe (13,224,649,275 samples, 0.14%)</title><rect x="827.9" y="389" width="1.7" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="830.93" y="399.5" ></text>
</g>
<g >
<title>AtEOXact_SPI (901,857,180 samples, 0.01%)</title><rect x="32.8" y="757" width="0.2" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="35.85" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,924,240,591 samples, 0.03%)</title><rect x="1120.0" y="693" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1123.02" y="703.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,178,407,354 samples, 0.03%)</title><rect x="288.7" y="181" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="291.67" y="191.5" ></text>
</g>
<g >
<title>_bt_getrootheight (1,513,629,304 samples, 0.02%)</title><rect x="933.2" y="389" width="0.2" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="936.25" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (2,033,455,786 samples, 0.02%)</title><rect x="435.3" y="325" width="0.3" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="438.35" y="335.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,695,190,628 samples, 0.07%)</title><rect x="947.6" y="341" width="0.8" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="950.56" y="351.5" ></text>
</g>
<g >
<title>UnlockBufHdr (1,342,663,286 samples, 0.01%)</title><rect x="324.5" y="213" width="0.2" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="327.53" y="223.5" ></text>
</g>
<g >
<title>_bt_unlockbuf (3,407,274,089 samples, 0.04%)</title><rect x="325.2" y="213" width="0.5" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="328.24" y="223.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64 (2,493,329,070 samples, 0.03%)</title><rect x="505.3" y="485" width="0.3" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="508.33" y="495.5" ></text>
</g>
<g >
<title>AllocSetFree (991,779,624 samples, 0.01%)</title><rect x="994.5" y="341" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="997.47" y="351.5" ></text>
</g>
<g >
<title>ExecPushExprSetupSteps (6,676,282,795 samples, 0.07%)</title><rect x="416.6" y="341" width="0.8" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="419.56" y="351.5" ></text>
</g>
<g >
<title>hash_bytes (2,728,308,702 samples, 0.03%)</title><rect x="820.4" y="325" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="823.36" y="335.5" ></text>
</g>
<g >
<title>list_make1_impl (1,988,411,184 samples, 0.02%)</title><rect x="908.6" y="453" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="911.60" y="463.5" ></text>
</g>
<g >
<title>pq_getmessage (11,153,967,975 samples, 0.12%)</title><rect x="184.9" y="565" width="1.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="187.86" y="575.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,450,880,216 samples, 0.04%)</title><rect x="390.1" y="325" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="393.09" y="335.5" ></text>
</g>
<g >
<title>PinBuffer (1,757,898,060 samples, 0.02%)</title><rect x="368.1" y="245" width="0.2" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="371.08" y="255.5" ></text>
</g>
<g >
<title>slot_getsomeattrs (9,644,165,946 samples, 0.10%)</title><rect x="288.0" y="277" width="1.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="290.96" y="287.5" ></text>
</g>
<g >
<title>AllocSetDelete (6,889,019,738 samples, 0.07%)</title><rect x="223.1" y="533" width="0.9" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="226.10" y="543.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (5,097,576,411 samples, 0.05%)</title><rect x="848.4" y="357" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="851.36" y="367.5" ></text>
</g>
<g >
<title>standard_planner (1,980,044,175 samples, 0.02%)</title><rect x="85.4" y="597" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="88.40" y="607.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (886,882,643 samples, 0.01%)</title><rect x="124.5" y="421" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="127.48" y="431.5" ></text>
</g>
<g >
<title>estimate_expression_value (803,901,079 samples, 0.01%)</title><rect x="126.6" y="261" width="0.1" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="129.56" y="271.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,501,365,315 samples, 0.04%)</title><rect x="1078.0" y="517" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1080.97" y="527.5" ></text>
</g>
<g >
<title>ReadBuffer (1,404,600,886 samples, 0.01%)</title><rect x="335.5" y="213" width="0.2" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="338.50" y="223.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (2,602,827,348 samples, 0.03%)</title><rect x="76.7" y="757" width="0.3" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="79.65" y="767.5" ></text>
</g>
<g >
<title>BufferGetPage (1,873,258,057 samples, 0.02%)</title><rect x="309.6" y="245" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="312.57" y="255.5" ></text>
</g>
<g >
<title>SearchCatCacheList (8,537,128,675 samples, 0.09%)</title><rect x="962.6" y="357" width="1.1" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="965.64" y="367.5" ></text>
</g>
<g >
<title>set_plain_rel_pathlist (324,043,465,427 samples, 3.43%)</title><rect x="985.9" y="421" width="40.5" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="988.87" y="431.5" >set..</text>
</g>
<g >
<title>pull_varnos (5,093,814,385 samples, 0.05%)</title><rect x="968.2" y="373" width="0.6" height="15.0" fill="rgb(229,112,26)" rx="2" ry="2" />
<text  x="971.21" y="383.5" ></text>
</g>
<g >
<title>palloc0 (1,727,885,562 samples, 0.02%)</title><rect x="913.7" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="916.69" y="447.5" ></text>
</g>
<g >
<title>SetLocktagRelationOid (1,636,903,328 samples, 0.02%)</title><rect x="441.1" y="373" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="444.08" y="383.5" ></text>
</g>
<g >
<title>AdvanceXLInsertBuffer (804,770,827 samples, 0.01%)</title><rect x="379.9" y="277" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="382.95" y="287.5" ></text>
</g>
<g >
<title>newNode (2,542,619,483 samples, 0.03%)</title><rect x="833.7" y="421" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="836.73" y="431.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,068,815,456 samples, 0.01%)</title><rect x="398.7" y="325" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="401.66" y="335.5" ></text>
</g>
<g >
<title>AtEOXact_Enum (1,294,093,953 samples, 0.01%)</title><rect x="31.0" y="757" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="34.02" y="767.5" ></text>
</g>
<g >
<title>simplify_function (19,324,085,564 samples, 0.20%)</title><rect x="102.4" y="469" width="2.4" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="105.41" y="479.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (891,507,353 samples, 0.01%)</title><rect x="338.3" y="197" width="0.2" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="341.35" y="207.5" ></text>
</g>
<g >
<title>InjectionPointCacheRefresh (3,227,070,935 samples, 0.03%)</title><rect x="362.2" y="341" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="365.17" y="351.5" ></text>
</g>
<g >
<title>fdget (2,298,924,565 samples, 0.02%)</title><rect x="176.8" y="421" width="0.3" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="179.79" y="431.5" ></text>
</g>
<g >
<title>pg_plan_query (1,587,594,640,731 samples, 16.82%)</title><rect x="873.5" y="565" width="198.6" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="876.55" y="575.5" >pg_plan_query</text>
</g>
<g >
<title>verify_compact_attribute (1,368,564,439 samples, 0.01%)</title><rect x="268.6" y="245" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="271.59" y="255.5" ></text>
</g>
<g >
<title>XLogFlush (914,738,712 samples, 0.01%)</title><rect x="109.4" y="757" width="0.2" height="15.0" fill="rgb(251,213,50)" rx="2" ry="2" />
<text  x="112.44" y="767.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesMVCC (32,955,381,451 samples, 0.35%)</title><rect x="304.1" y="229" width="4.1" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="307.11" y="239.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,131,160,257 samples, 0.01%)</title><rect x="323.5" y="197" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="326.48" y="207.5" ></text>
</g>
<g >
<title>ExecAssignExprContext (10,444,913,934 samples, 0.11%)</title><rect x="414.4" y="421" width="1.3" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="417.41" y="431.5" ></text>
</g>
<g >
<title>ExecInitExprRec (11,207,895,693 samples, 0.12%)</title><rect x="431.0" y="405" width="1.4" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="434.02" y="415.5" ></text>
</g>
<g >
<title>RelationGetNumberOfBlocksInFork (22,427,398,684 samples, 0.24%)</title><rect x="935.4" y="341" width="2.8" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="938.43" y="351.5" ></text>
</g>
<g >
<title>HeapTupleHeaderXminFrozen (4,305,227,308 samples, 0.05%)</title><rect x="302.6" y="229" width="0.5" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="305.60" y="239.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,257,793,925 samples, 0.03%)</title><rect x="973.6" y="357" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="976.57" y="367.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,635,510,285 samples, 0.02%)</title><rect x="37.6" y="757" width="0.2" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="40.57" y="767.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,369,631,848 samples, 0.01%)</title><rect x="189.4" y="485" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="192.43" y="495.5" ></text>
</g>
<g >
<title>check_functions_in_node (6,980,752,066 samples, 0.07%)</title><rect x="916.2" y="421" width="0.9" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="919.23" y="431.5" ></text>
</g>
<g >
<title>palloc (1,358,335,168 samples, 0.01%)</title><rect x="922.6" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="925.59" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,218,264,295 samples, 0.01%)</title><rect x="860.4" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="863.41" y="495.5" ></text>
</g>
<g >
<title>namestrcmp (2,775,743,010 samples, 0.03%)</title><rect x="832.1" y="453" width="0.4" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="835.12" y="463.5" ></text>
</g>
<g >
<title>mutex_unlock (1,228,680,756 samples, 0.01%)</title><rect x="182.6" y="373" width="0.2" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="185.64" y="383.5" ></text>
</g>
<g >
<title>ExecInitScanTupleSlot (1,570,722,331 samples, 0.02%)</title><rect x="44.0" y="757" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="47.04" y="767.5" ></text>
</g>
<g >
<title>list_free_deep (5,878,483,285 samples, 0.06%)</title><rect x="978.6" y="453" width="0.8" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="981.63" y="463.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableFullXid (1,988,806,330 samples, 0.02%)</title><rect x="310.3" y="229" width="0.2" height="15.0" fill="rgb(232,128,30)" rx="2" ry="2" />
<text  x="313.26" y="239.5" ></text>
</g>
<g >
<title>lappend (2,227,811,531 samples, 0.02%)</title><rect x="981.6" y="405" width="0.3" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="984.60" y="415.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (10,670,317,115 samples, 0.11%)</title><rect x="947.5" y="373" width="1.3" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="950.49" y="383.5" ></text>
</g>
<g >
<title>LockReassignOwner (3,140,391,278 samples, 0.03%)</title><rect x="225.5" y="517" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="228.49" y="527.5" ></text>
</g>
<g >
<title>ExecutorRun (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="581" width="0.5" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="1160.94" y="591.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,094,230,857 samples, 0.01%)</title><rect x="941.8" y="325" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="944.84" y="335.5" ></text>
</g>
<g >
<title>object_aclcheck (1,932,176,355 samples, 0.02%)</title><rect x="420.7" y="325" width="0.3" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="423.73" y="335.5" ></text>
</g>
<g >
<title>hash_initial_lookup (803,788,687 samples, 0.01%)</title><rect x="440.7" y="325" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="443.70" y="335.5" ></text>
</g>
<g >
<title>palloc0 (1,549,328,571 samples, 0.02%)</title><rect x="974.9" y="277" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="977.87" y="287.5" ></text>
</g>
<g >
<title>palloc (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="309" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="88.40" y="319.5" ></text>
</g>
<g >
<title>XLogRegisterData (833,667,847 samples, 0.01%)</title><rect x="509.3" y="485" width="0.1" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="512.31" y="495.5" ></text>
</g>
<g >
<title>get_typlen (6,665,871,369 samples, 0.07%)</title><rect x="1042.1" y="357" width="0.8" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="1045.10" y="367.5" ></text>
</g>
<g >
<title>LWLockWakeup (2,428,468,505 samples, 0.03%)</title><rect x="363.6" y="309" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="366.64" y="319.5" ></text>
</g>
<g >
<title>BufferAlloc (26,684,185,460 samples, 0.28%)</title><rect x="94.8" y="197" width="3.3" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="97.79" y="207.5" ></text>
</g>
<g >
<title>__pick_next_task (15,873,601,603 samples, 0.17%)</title><rect x="159.2" y="341" width="2.0" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="162.17" y="351.5" ></text>
</g>
<g >
<title>SearchSysCache3 (5,452,754,940 samples, 0.06%)</title><rect x="1010.9" y="293" width="0.7" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1013.95" y="303.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,027,371,916 samples, 0.01%)</title><rect x="300.6" y="133" width="0.1" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="303.58" y="143.5" ></text>
</g>
<g >
<title>_int_malloc (2,021,878,420 samples, 0.02%)</title><rect x="912.6" y="389" width="0.3" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="915.60" y="399.5" ></text>
</g>
<g >
<title>bms_del_members (816,098,573 samples, 0.01%)</title><rect x="879.1" y="469" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="882.15" y="479.5" ></text>
</g>
<g >
<title>cpuacct_charge (1,123,579,800 samples, 0.01%)</title><rect x="169.3" y="245" width="0.1" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="172.27" y="255.5" ></text>
</g>
<g >
<title>get_leftop (1,013,196,930 samples, 0.01%)</title><rect x="972.0" y="389" width="0.1" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="975.01" y="399.5" ></text>
</g>
<g >
<title>__task_rq_lock (811,735,690 samples, 0.01%)</title><rect x="354.7" y="85" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="357.71" y="95.5" ></text>
</g>
<g >
<title>tag_hash (2,462,350,871 samples, 0.03%)</title><rect x="355.0" y="245" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="358.02" y="255.5" ></text>
</g>
<g >
<title>_int_free_merge_chunk (3,301,374,757 samples, 0.03%)</title><rect x="241.2" y="341" width="0.5" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="244.25" y="351.5" ></text>
</g>
<g >
<title>LockHeldByMe (6,536,167,978 samples, 0.07%)</title><rect x="1067.4" y="421" width="0.9" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1070.44" y="431.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,674,395,068 samples, 0.04%)</title><rect x="1056.7" y="405" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1059.69" y="415.5" ></text>
</g>
<g >
<title>palloc0 (4,730,988,301 samples, 0.05%)</title><rect x="816.5" y="437" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="819.48" y="447.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,022,784,425 samples, 0.02%)</title><rect x="374.2" y="341" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="377.22" y="351.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberCatCacheRef (1,027,064,660 samples, 0.01%)</title><rect x="91.8" y="757" width="0.1" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="94.79" y="767.5" ></text>
</g>
<g >
<title>lappend (3,334,822,066 samples, 0.04%)</title><rect x="889.8" y="277" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="892.85" y="287.5" ></text>
</g>
<g >
<title>hash_initial_lookup (2,109,372,957 samples, 0.02%)</title><rect x="514.9" y="421" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="517.86" y="431.5" ></text>
</g>
<g >
<title>gup_fast_fallback (1,647,591,694 samples, 0.02%)</title><rect x="351.2" y="117" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="354.17" y="127.5" ></text>
</g>
<g >
<title>ProcessQuery (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="629" width="6.7" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="97.67" y="639.5" ></text>
</g>
<g >
<title>BackendMain (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="693" width="3.0" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="85.62" y="703.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="501" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="127.24" y="511.5" ></text>
</g>
<g >
<title>do_syscall_64 (8,174,462,190 samples, 0.09%)</title><rect x="936.9" y="197" width="1.0" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="939.91" y="207.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,982,794,754 samples, 0.03%)</title><rect x="469.8" y="437" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="472.84" y="447.5" ></text>
</g>
<g >
<title>palloc (1,502,406,841 samples, 0.02%)</title><rect x="873.3" y="533" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="876.33" y="543.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (862,970,815 samples, 0.01%)</title><rect x="436.1" y="309" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="439.12" y="319.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (2,438,514,431 samples, 0.03%)</title><rect x="333.4" y="197" width="0.3" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="336.38" y="207.5" ></text>
</g>
<g >
<title>get_relation_notnullatts (65,523,862,200 samples, 0.69%)</title><rect x="1058.8" y="485" width="8.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="1061.77" y="495.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (920,045,266 samples, 0.01%)</title><rect x="28.9" y="741" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="31.90" y="751.5" ></text>
</g>
<g >
<title>pull_varattnos_walker (10,541,597,028 samples, 0.11%)</title><rect x="997.1" y="325" width="1.3" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="1000.07" y="335.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,128,658,246 samples, 0.01%)</title><rect x="237.3" y="373" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="240.27" y="383.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,482,607,802 samples, 0.02%)</title><rect x="819.8" y="357" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="822.79" y="367.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="693" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1176.04" y="703.5" ></text>
</g>
<g >
<title>ExecModifyTable (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="533" width="6.7" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="97.67" y="543.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (19,447,790,340 samples, 0.21%)</title><rect x="901.0" y="421" width="2.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="903.97" y="431.5" ></text>
</g>
<g >
<title>ReadBuffer_common (801,296,149 samples, 0.01%)</title><rect x="1158.3" y="229" width="0.1" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1161.26" y="239.5" ></text>
</g>
<g >
<title>generate_base_implied_equalities (19,173,611,572 samples, 0.20%)</title><rect x="980.0" y="469" width="2.4" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="983.01" y="479.5" ></text>
</g>
<g >
<title>__sigsetjmp (1,863,706,088 samples, 0.02%)</title><rect x="185.3" y="549" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="188.30" y="559.5" ></text>
</g>
<g >
<title>dequeue_entity (39,145,811,681 samples, 0.41%)</title><rect x="166.7" y="293" width="4.9" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="169.69" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,324,855,659 samples, 0.01%)</title><rect x="1173.0" y="565" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1176.04" y="575.5" ></text>
</g>
<g >
<title>futex_wake (27,545,769,084 samples, 0.29%)</title><rect x="485.3" y="341" width="3.4" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="488.28" y="351.5" ></text>
</g>
<g >
<title>lappend (1,468,943,018 samples, 0.02%)</title><rect x="897.4" y="469" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="900.40" y="479.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,369,057,098 samples, 0.05%)</title><rect x="349.7" y="293" width="0.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="352.75" y="303.5" ></text>
</g>
<g >
<title>table_index_fetch_reset (1,482,128,195 samples, 0.02%)</title><rect x="341.7" y="309" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="344.73" y="319.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,355,954,048 samples, 0.04%)</title><rect x="508.0" y="437" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="511.02" y="447.5" ></text>
</g>
<g >
<title>list_last_cell (1,687,501,329 samples, 0.02%)</title><rect x="1154.8" y="757" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1157.83" y="767.5" ></text>
</g>
<g >
<title>pgstat_count_slru_blocks_hit (1,237,933,197 samples, 0.01%)</title><rect x="472.3" y="421" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="475.31" y="431.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="469" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="127.24" y="479.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (26,749,603,550 samples, 0.28%)</title><rect x="297.5" y="181" width="3.3" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="300.46" y="191.5" ></text>
</g>
<g >
<title>WalSndWakeup (4,648,221,645 samples, 0.05%)</title><rect x="491.6" y="469" width="0.6" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="494.64" y="479.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,267,508,550 samples, 0.01%)</title><rect x="518.2" y="405" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="521.18" y="415.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,188,203,772 samples, 0.01%)</title><rect x="395.7" y="309" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="398.73" y="319.5" ></text>
</g>
<g >
<title>LWLockWakeup (2,614,500,691 samples, 0.03%)</title><rect x="382.1" y="245" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="385.11" y="255.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,262,820,018 samples, 0.02%)</title><rect x="942.6" y="325" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="945.57" y="335.5" ></text>
</g>
<g >
<title>smgrnblocks_cached (929,200,748 samples, 0.01%)</title><rect x="938.1" y="277" width="0.1" height="15.0" fill="rgb(243,175,41)" rx="2" ry="2" />
<text  x="941.11" y="287.5" ></text>
</g>
<g >
<title>AllocSetFree (2,620,987,918 samples, 0.03%)</title><rect x="252.1" y="453" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="255.14" y="463.5" ></text>
</g>
<g >
<title>heap_update (1,514,571,508 samples, 0.02%)</title><rect x="1149.1" y="757" width="0.2" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="1152.07" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (6,334,093,665 samples, 0.07%)</title><rect x="381.1" y="293" width="0.8" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="384.14" y="303.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,116,942,226 samples, 0.04%)</title><rect x="1056.6" y="421" width="0.6" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1059.64" y="431.5" ></text>
</g>
<g >
<title>log_heap_prune_and_freeze (1,087,897,863 samples, 0.01%)</title><rect x="1145.7" y="741" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1148.66" y="751.5" ></text>
</g>
<g >
<title>pfree (1,512,546,163 samples, 0.02%)</title><rect x="877.8" y="469" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="880.82" y="479.5" ></text>
</g>
<g >
<title>set_baserel_size_estimates (803,901,079 samples, 0.01%)</title><rect x="126.6" y="421" width="0.1" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="129.56" y="431.5" ></text>
</g>
<g >
<title>RestrictInfoIsTidQual (5,515,093,782 samples, 0.06%)</title><rect x="1025.1" y="373" width="0.6" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1028.05" y="383.5" ></text>
</g>
<g >
<title>newNode (2,217,801,030 samples, 0.02%)</title><rect x="835.7" y="373" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="838.70" y="383.5" ></text>
</g>
<g >
<title>__task_rq_lock (1,016,382,047 samples, 0.01%)</title><rect x="382.3" y="85" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="385.25" y="95.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,916,633,182 samples, 0.02%)</title><rect x="37.8" y="757" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="40.77" y="767.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (8,778,672,728 samples, 0.09%)</title><rect x="315.9" y="229" width="1.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="318.92" y="239.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (7,518,641,940 samples, 0.08%)</title><rect x="1009.7" y="245" width="1.0" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1012.74" y="255.5" ></text>
</g>
<g >
<title>UnpinBuffer (3,228,855,107 samples, 0.03%)</title><rect x="324.8" y="197" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="327.83" y="207.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="677" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.04" y="687.5" ></text>
</g>
<g >
<title>nocachegetattr (44,841,942,633 samples, 0.48%)</title><rect x="1002.7" y="213" width="5.6" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="1005.69" y="223.5" ></text>
</g>
<g >
<title>__futex_wait (853,688,244 samples, 0.01%)</title><rect x="398.7" y="229" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="401.69" y="239.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,392,288,237 samples, 0.04%)</title><rect x="1052.7" y="389" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1055.68" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (865,784,686 samples, 0.01%)</title><rect x="423.0" y="293" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="426.03" y="303.5" ></text>
</g>
<g >
<title>AllocSetFree (853,738,746 samples, 0.01%)</title><rect x="1069.8" y="453" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1072.82" y="463.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (811,735,690 samples, 0.01%)</title><rect x="354.7" y="37" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="357.71" y="47.5" ></text>
</g>
<g >
<title>__x64_sys_epoll_wait (145,809,087,310 samples, 1.55%)</title><rect x="154.0" y="437" width="18.3" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="157.05" y="447.5" ></text>
</g>
<g >
<title>hash_bytes (3,254,773,028 samples, 0.03%)</title><rect x="444.7" y="309" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="447.67" y="319.5" ></text>
</g>
<g >
<title>syscall_return_via_sysret (1,669,655,671 samples, 0.02%)</title><rect x="174.6" y="469" width="0.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="177.56" y="479.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,090,198,011 samples, 0.01%)</title><rect x="126.1" y="181" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="129.10" y="191.5" ></text>
</g>
<g >
<title>getRTEPermissionInfo (1,604,375,863 samples, 0.02%)</title><rect x="855.9" y="325" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="858.88" y="335.5" ></text>
</g>
<g >
<title>LockTagHashCode (2,921,714,654 samples, 0.03%)</title><rect x="942.0" y="341" width="0.3" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="944.98" y="351.5" ></text>
</g>
<g >
<title>bms_is_valid_set (853,301,545 samples, 0.01%)</title><rect x="879.0" y="453" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="882.03" y="463.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (2,423,896,142 samples, 0.03%)</title><rect x="354.6" y="261" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="357.61" y="271.5" ></text>
</g>
<g >
<title>dequeue_entities (18,475,895,140 samples, 0.20%)</title><rect x="479.4" y="245" width="2.3" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="482.36" y="255.5" ></text>
</g>
<g >
<title>sentinel_ok (1,149,348,748 samples, 0.01%)</title><rect x="261.0" y="389" width="0.2" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="264.04" y="399.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (975,983,726 samples, 0.01%)</title><rect x="126.7" y="517" width="0.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="129.71" y="527.5" ></text>
</g>
<g >
<title>LockHeldByMe (4,582,157,558 samples, 0.05%)</title><rect x="443.3" y="405" width="0.6" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="446.29" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,147,892,373 samples, 0.01%)</title><rect x="307.4" y="101" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="310.41" y="111.5" ></text>
</g>
<g >
<title>palloc (1,454,050,364 samples, 0.02%)</title><rect x="1020.7" y="277" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1023.74" y="287.5" ></text>
</g>
<g >
<title>AllocSetCheck (2,037,033,666,294 samples, 21.59%)</title><rect x="537.1" y="549" width="254.7" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="540.12" y="559.5" >AllocSetCheck</text>
</g>
<g >
<title>__x64_sys_sendto (131,462,536,470 samples, 1.39%)</title><rect x="189.8" y="453" width="16.4" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="192.79" y="463.5" ></text>
</g>
<g >
<title>avc_has_perm_noaudit (1,106,526,628 samples, 0.01%)</title><rect x="177.7" y="357" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="180.75" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,344,505,706 samples, 0.02%)</title><rect x="1121.3" y="709" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1124.33" y="719.5" ></text>
</g>
<g >
<title>GetUserId (1,508,955,635 samples, 0.02%)</title><rect x="50.0" y="757" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="53.04" y="767.5" ></text>
</g>
<g >
<title>new_list (1,495,485,314 samples, 0.02%)</title><rect x="833.4" y="437" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="836.35" y="447.5" ></text>
</g>
<g >
<title>palloc (1,228,822,670 samples, 0.01%)</title><rect x="933.8" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="936.84" y="367.5" ></text>
</g>
<g >
<title>replace_nestloop_params (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="389" width="0.2" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="127.24" y="399.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (5,066,144,589 samples, 0.05%)</title><rect x="350.8" y="245" width="0.6" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="353.77" y="255.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,183,056,938 samples, 0.02%)</title><rect x="400.2" y="261" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="403.15" y="271.5" ></text>
</g>
<g >
<title>ExecutorRun (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="565" width="1.7" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="125.45" y="575.5" ></text>
</g>
<g >
<title>exprCollation (1,481,652,228 samples, 0.02%)</title><rect x="971.5" y="373" width="0.2" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="974.50" y="383.5" ></text>
</g>
<g >
<title>LWLockConditionalAcquire (4,416,263,830 samples, 0.05%)</title><rect x="466.1" y="501" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="469.07" y="511.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (869,487,046 samples, 0.01%)</title><rect x="386.2" y="293" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="389.22" y="303.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (46,145,179,058 samples, 0.49%)</title><rect x="19.0" y="741" width="5.7" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="21.95" y="751.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,164,696,126 samples, 0.01%)</title><rect x="711.9" y="501" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="714.86" y="511.5" ></text>
</g>
<g >
<title>pq_beginmessage (970,773,939 samples, 0.01%)</title><rect x="1172.5" y="757" width="0.1" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="1175.49" y="767.5" ></text>
</g>
<g >
<title>BackendMain (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="677" width="0.6" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="1160.94" y="687.5" ></text>
</g>
<g >
<title>AllocSetAlloc (7,820,841,377 samples, 0.08%)</title><rect x="946.3" y="389" width="0.9" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="949.26" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,344,196,461 samples, 0.01%)</title><rect x="891.9" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="894.86" y="351.5" ></text>
</g>
<g >
<title>wakeup_preempt (864,591,013 samples, 0.01%)</title><rect x="205.6" y="325" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="208.57" y="335.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (2,871,252,156 samples, 0.03%)</title><rect x="123.2" y="229" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="126.18" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,033,370,213 samples, 0.01%)</title><rect x="840.1" y="277" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="843.09" y="287.5" ></text>
</g>
<g >
<title>HeapTupleHeaderXminCommitted (865,954,008 samples, 0.01%)</title><rect x="51.5" y="757" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="54.47" y="767.5" ></text>
</g>
<g >
<title>heap_fetch (33,989,644,301 samples, 0.36%)</title><rect x="397.5" y="405" width="4.3" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="400.52" y="415.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (857,954,703 samples, 0.01%)</title><rect x="489.0" y="453" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="492.03" y="463.5" ></text>
</g>
<g >
<title>xactGetCommittedChildren (839,446,438 samples, 0.01%)</title><rect x="1189.8" y="757" width="0.1" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="1192.84" y="767.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,013,969,305 samples, 0.01%)</title><rect x="797.7" y="533" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="800.70" y="543.5" ></text>
</g>
<g >
<title>BufTableLookup (5,764,061,619 samples, 0.06%)</title><rect x="99.4" y="181" width="0.8" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="102.44" y="191.5" ></text>
</g>
<g >
<title>simplify_function (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="661" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1136.99" y="671.5" ></text>
</g>
<g >
<title>XLogFlush (261,746,002,979 samples, 2.77%)</title><rect x="473.2" y="501" width="32.7" height="15.0" fill="rgb(251,213,50)" rx="2" ry="2" />
<text  x="476.20" y="511.5" >XL..</text>
</g>
<g >
<title>populate_compact_attribute_internal (1,052,767,760 samples, 0.01%)</title><rect x="268.6" y="229" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="271.63" y="239.5" ></text>
</g>
<g >
<title>ReadBuffer (28,787,750,420 samples, 0.31%)</title><rect x="297.2" y="245" width="3.6" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="300.25" y="255.5" ></text>
</g>
<g >
<title>eqsel_internal (52,322,615,973 samples, 0.55%)</title><rect x="1030.4" y="277" width="6.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1033.40" y="287.5" ></text>
</g>
<g >
<title>MemoryContextDeleteOnly (7,651,708,527 samples, 0.08%)</title><rect x="223.1" y="549" width="0.9" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="226.07" y="559.5" ></text>
</g>
<g >
<title>list_free_private (5,602,385,853 samples, 0.06%)</title><rect x="978.7" y="437" width="0.7" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="981.66" y="447.5" ></text>
</g>
<g >
<title>palloc (1,915,338,284 samples, 0.02%)</title><rect x="889.3" y="309" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="892.26" y="319.5" ></text>
</g>
<g >
<title>AtEOXact_Namespace (990,695,039 samples, 0.01%)</title><rect x="32.0" y="757" width="0.1" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="34.98" y="767.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (7,656,391,098 samples, 0.08%)</title><rect x="868.1" y="485" width="1.0" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="871.11" y="495.5" ></text>
</g>
<g >
<title>ResourceOwnerCreate (8,467,507,735 samples, 0.09%)</title><rect x="213.3" y="565" width="1.0" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="216.26" y="575.5" ></text>
</g>
<g >
<title>relation_open (17,886,124,952 samples, 0.19%)</title><rect x="861.0" y="501" width="2.3" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="864.01" y="511.5" ></text>
</g>
<g >
<title>palloc (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="581" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1176.04" y="591.5" ></text>
</g>
<g >
<title>AllocSetFree (3,228,964,248 samples, 0.03%)</title><rect x="224.3" y="533" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="227.31" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,389,009,045 samples, 0.01%)</title><rect x="964.2" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="967.20" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,242,295,980 samples, 0.01%)</title><rect x="102.2" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="105.21" y="447.5" ></text>
</g>
<g >
<title>rseq_ip_fixup (1,504,840,530 samples, 0.02%)</title><rect x="483.8" y="357" width="0.1" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="486.76" y="367.5" ></text>
</g>
<g >
<title>tts_virtual_clear (845,986,418 samples, 0.01%)</title><rect x="249.4" y="453" width="0.1" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="252.37" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (9,494,296,293 samples, 0.10%)</title><rect x="514.3" y="437" width="1.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="517.25" y="447.5" ></text>
</g>
<g >
<title>list_make1_impl (7,245,151,100 samples, 0.08%)</title><rect x="917.9" y="485" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="920.92" y="495.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (13,679,434,038 samples, 0.14%)</title><rect x="955.3" y="421" width="1.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="958.26" y="431.5" ></text>
</g>
<g >
<title>makeRawStmt (2,374,751,097 samples, 0.03%)</title><rect x="1118.8" y="741" width="0.3" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1121.81" y="751.5" ></text>
</g>
<g >
<title>__sys_sendto (129,708,484,625 samples, 1.37%)</title><rect x="190.0" y="437" width="16.2" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="193.01" y="447.5" ></text>
</g>
<g >
<title>_bt_first (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="341" width="2.8" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="85.62" y="351.5" ></text>
</g>
<g >
<title>update_min_vruntime (1,097,996,481 samples, 0.01%)</title><rect x="168.5" y="261" width="0.1" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="171.48" y="271.5" ></text>
</g>
<g >
<title>deconstruct_recurse (26,774,216,926 samples, 0.28%)</title><rect x="975.3" y="453" width="3.3" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="978.27" y="463.5" ></text>
</g>
<g >
<title>bms_copy (1,990,360,055 samples, 0.02%)</title><rect x="374.9" y="341" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="377.90" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,790,268,817 samples, 0.03%)</title><rect x="872.5" y="517" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="875.50" y="527.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (7,052,572,533 samples, 0.07%)</title><rect x="98.2" y="197" width="0.8" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="101.16" y="207.5" ></text>
</g>
<g >
<title>heapam_fetch_row_version (37,741,913,733 samples, 0.40%)</title><rect x="397.1" y="421" width="4.7" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="400.06" y="431.5" ></text>
</g>
<g >
<title>AtEOXact_ApplyLauncher (833,657,345 samples, 0.01%)</title><rect x="30.9" y="757" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="33.85" y="767.5" ></text>
</g>
<g >
<title>palloc (1,095,424,844 samples, 0.01%)</title><rect x="972.6" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="975.65" y="367.5" ></text>
</g>
<g >
<title>castNodeImpl (878,386,775 samples, 0.01%)</title><rect x="274.7" y="405" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="277.71" y="415.5" ></text>
</g>
<g >
<title>heap_fill_tuple (6,967,049,409 samples, 0.07%)</title><rect x="390.5" y="357" width="0.9" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="393.52" y="367.5" ></text>
</g>
<g >
<title>tts_buffer_heap_store_tuple (1,283,787,477 samples, 0.01%)</title><rect x="397.3" y="389" width="0.2" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="400.32" y="399.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,947,840,314 samples, 0.03%)</title><rect x="299.4" y="133" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="302.42" y="143.5" ></text>
</g>
<g >
<title>slot_getsomeattrs (3,891,036,043 samples, 0.04%)</title><rect x="268.3" y="341" width="0.5" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="271.31" y="351.5" ></text>
</g>
<g >
<title>palloc (1,168,921,261 samples, 0.01%)</title><rect x="897.7" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="900.71" y="447.5" ></text>
</g>
<g >
<title>relation_close (2,356,471,078 samples, 0.02%)</title><rect x="799.1" y="517" width="0.3" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="802.13" y="527.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (901,971,316 samples, 0.01%)</title><rect x="364.7" y="341" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="367.69" y="351.5" ></text>
</g>
<g >
<title>palloc (1,331,089,593 samples, 0.01%)</title><rect x="958.0" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="960.99" y="431.5" ></text>
</g>
<g >
<title>process_equivalence (1,502,833,478 samples, 0.02%)</title><rect x="1173.8" y="757" width="0.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1176.75" y="767.5" ></text>
</g>
<g >
<title>ReleasePredicateLocks (2,812,478,926 samples, 0.03%)</title><rect x="525.4" y="485" width="0.3" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="528.36" y="495.5" ></text>
</g>
<g >
<title>hash_search (7,537,713,188 samples, 0.08%)</title><rect x="226.3" y="565" width="1.0" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="229.31" y="575.5" ></text>
</g>
<g >
<title>subquery_planner (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="597" width="3.4" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="104.45" y="607.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (1,288,352,411 samples, 0.01%)</title><rect x="199.0" y="309" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="202.03" y="319.5" ></text>
</g>
<g >
<title>sched_tick (1,526,172,241 samples, 0.02%)</title><rect x="791.4" y="405" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="794.41" y="415.5" ></text>
</g>
<g >
<title>transformOptionalSelectInto (450,501,512,655 samples, 4.77%)</title><rect x="800.5" y="533" width="56.4" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="803.54" y="543.5" >trans..</text>
</g>
<g >
<title>BufferGetBlockNumber (2,717,130,474 samples, 0.03%)</title><rect x="83.7" y="181" width="0.3" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="86.71" y="191.5" ></text>
</g>
<g >
<title>create_index_paths (835,885,566 samples, 0.01%)</title><rect x="1129.9" y="757" width="0.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1132.86" y="767.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,979,861,304 samples, 0.02%)</title><rect x="865.3" y="469" width="0.3" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="868.32" y="479.5" ></text>
</g>
<g >
<title>record_times (1,384,175,991 samples, 0.01%)</title><rect x="478.4" y="261" width="0.2" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="481.41" y="271.5" ></text>
</g>
<g >
<title>canonicalize_ec_expression (3,388,995,240 samples, 0.04%)</title><rect x="971.4" y="389" width="0.4" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="974.35" y="399.5" ></text>
</g>
<g >
<title>ExecProject (51,595,225,219 samples, 0.55%)</title><rect x="282.8" y="357" width="6.5" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="285.81" y="367.5" ></text>
</g>
<g >
<title>examine_indexcol_variable (6,716,826,540 samples, 0.07%)</title><rect x="1010.8" y="309" width="0.8" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1013.79" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,852,593,071 samples, 0.02%)</title><rect x="367.4" y="229" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="370.40" y="239.5" ></text>
</g>
<g >
<title>list_make1_impl (2,825,651,879 samples, 0.03%)</title><rect x="1058.1" y="453" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1061.07" y="463.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,837,719,727 samples, 0.02%)</title><rect x="365.4" y="341" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="368.36" y="351.5" ></text>
</g>
<g >
<title>ReleaseSysCache (865,710,376 samples, 0.01%)</title><rect x="807.3" y="341" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="810.33" y="351.5" ></text>
</g>
<g >
<title>HeapTupleIsHeapOnly (942,405,251 samples, 0.01%)</title><rect x="51.6" y="757" width="0.1" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="54.61" y="767.5" ></text>
</g>
<g >
<title>MaintainLatestCompletedXid (3,063,508,982 samples, 0.03%)</title><rect x="467.2" y="485" width="0.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="470.15" y="495.5" ></text>
</g>
<g >
<title>remove_useless_self_joins (1,621,750,316 samples, 0.02%)</title><rect x="1044.0" y="469" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1047.01" y="479.5" ></text>
</g>
<g >
<title>MemoryContextReset (137,544,758,175 samples, 1.46%)</title><rect x="131.7" y="597" width="17.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="134.72" y="607.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (1,521,971,557 samples, 0.02%)</title><rect x="279.7" y="277" width="0.2" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="282.67" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (857,993,181 samples, 0.01%)</title><rect x="913.8" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="916.79" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,333,888,620 samples, 0.01%)</title><rect x="1020.7" y="261" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1023.75" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,353,417,383 samples, 0.01%)</title><rect x="424.4" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="427.44" y="335.5" ></text>
</g>
<g >
<title>standard_ExecutorStart (393,850,000,999 samples, 4.17%)</title><rect x="403.5" y="517" width="49.2" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="406.45" y="527.5" >stan..</text>
</g>
<g >
<title>yy_init_globals (857,478,604 samples, 0.01%)</title><rect x="872.2" y="517" width="0.1" height="15.0" fill="rgb(226,96,23)" rx="2" ry="2" />
<text  x="875.24" y="527.5" ></text>
</g>
<g >
<title>newNode (9,358,461,286 samples, 0.10%)</title><rect x="911.7" y="469" width="1.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="914.72" y="479.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetLock (2,294,446,474 samples, 0.02%)</title><rect x="521.9" y="453" width="0.2" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="524.86" y="463.5" ></text>
</g>
<g >
<title>hash_search (5,978,823,523 samples, 0.06%)</title><rect x="237.0" y="405" width="0.7" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="239.99" y="415.5" ></text>
</g>
<g >
<title>core_yylex (87,838,964,353 samples, 0.93%)</title><rect x="1104.0" y="725" width="11.0" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="1106.98" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,057,162,934 samples, 0.01%)</title><rect x="448.1" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="451.11" y="399.5" ></text>
</g>
<g >
<title>bms_add_member (2,637,733,854 samples, 0.03%)</title><rect x="832.5" y="469" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="835.47" y="479.5" ></text>
</g>
<g >
<title>preprocess_relation_rtes (85,739,396,343 samples, 0.91%)</title><rect x="1058.5" y="501" width="10.7" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1061.46" y="511.5" ></text>
</g>
<g >
<title>smgrDoPendingSyncs (1,108,623,850 samples, 0.01%)</title><rect x="1183.2" y="757" width="0.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="1186.18" y="767.5" ></text>
</g>
<g >
<title>bms_add_member (3,558,179,538 samples, 0.04%)</title><rect x="997.9" y="277" width="0.5" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1000.94" y="287.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,665,446,276 samples, 0.02%)</title><rect x="470.3" y="341" width="0.3" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="473.35" y="351.5" ></text>
</g>
<g >
<title>palloc (1,652,448,084 samples, 0.02%)</title><rect x="272.1" y="373" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="275.08" y="383.5" ></text>
</g>
<g >
<title>PortalRun (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="597" width="1.9" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="127.59" y="607.5" ></text>
</g>
<g >
<title>palloc0 (2,127,510,227 samples, 0.02%)</title><rect x="815.8" y="421" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="818.75" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,090,951,542 samples, 0.02%)</title><rect x="971.0" y="325" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="974.03" y="335.5" ></text>
</g>
<g >
<title>IsAbortedTransactionBlockState (1,183,873,310 samples, 0.01%)</title><rect x="131.5" y="597" width="0.2" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="134.52" y="607.5" ></text>
</g>
<g >
<title>index_beginscan_internal (26,044,089,435 samples, 0.28%)</title><rect x="289.8" y="309" width="3.3" height="15.0" fill="rgb(252,217,52)" rx="2" ry="2" />
<text  x="292.81" y="319.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (3,821,183,569 samples, 0.04%)</title><rect x="948.8" y="373" width="0.5" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="951.82" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (2,574,894,328 samples, 0.03%)</title><rect x="829.2" y="341" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="832.25" y="351.5" ></text>
</g>
<g >
<title>TransactionLogFetch (10,310,258,267 samples, 0.11%)</title><rect x="306.6" y="197" width="1.3" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="309.59" y="207.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (48,435,147,055 samples, 0.51%)</title><rect x="64.6" y="757" width="6.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="67.61" y="767.5" ></text>
</g>
<g >
<title>update_rq_clock (922,947,735 samples, 0.01%)</title><rect x="171.7" y="341" width="0.1" height="15.0" fill="rgb(231,119,28)" rx="2" ry="2" />
<text  x="174.67" y="351.5" ></text>
</g>
<g >
<title>ExecInitFunc (9,872,138,341 samples, 0.10%)</title><rect x="431.1" y="389" width="1.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="434.13" y="399.5" ></text>
</g>
<g >
<title>bms_copy (4,076,694,584 samples, 0.04%)</title><rect x="951.4" y="421" width="0.5" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="954.35" y="431.5" ></text>
</g>
<g >
<title>UnregisterSnapshotFromOwner (1,803,774,615 samples, 0.02%)</title><rect x="262.4" y="485" width="0.3" height="15.0" fill="rgb(223,87,20)" rx="2" ry="2" />
<text  x="265.44" y="495.5" ></text>
</g>
<g >
<title>ExecIndexScan (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="453" width="0.5" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1160.94" y="463.5" ></text>
</g>
<g >
<title>BTreeTupleIsPivot (855,157,050 samples, 0.01%)</title><rect x="315.7" y="229" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="318.73" y="239.5" ></text>
</g>
<g >
<title>ItemPointerGetOffsetNumber (896,535,760 samples, 0.01%)</title><rect x="293.9" y="293" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="296.89" y="303.5" ></text>
</g>
<g >
<title>ExecInitResultRelation (44,967,336,667 samples, 0.48%)</title><rect x="442.9" y="453" width="5.6" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="445.91" y="463.5" ></text>
</g>
<g >
<title>new_list (1,874,778,279 samples, 0.02%)</title><rect x="910.5" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="913.47" y="447.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (886,882,643 samples, 0.01%)</title><rect x="124.5" y="485" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="127.48" y="495.5" ></text>
</g>
<g >
<title>_mm_set1_epi8 (2,319,758,898 samples, 0.02%)</title><rect x="1082.6" y="485" width="0.3" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1085.63" y="495.5" ></text>
</g>
<g >
<title>set_rel_size (803,901,079 samples, 0.01%)</title><rect x="126.6" y="453" width="0.1" height="15.0" fill="rgb(243,175,42)" rx="2" ry="2" />
<text  x="129.56" y="463.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,274,209,964 samples, 0.01%)</title><rect x="921.5" y="405" width="0.2" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="924.55" y="415.5" ></text>
</g>
<g >
<title>int4hashfast (1,173,218,345 samples, 0.01%)</title><rect x="423.7" y="277" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="426.70" y="287.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (2,039,547,984 samples, 0.02%)</title><rect x="360.4" y="325" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="363.40" y="335.5" ></text>
</g>
<g >
<title>ExecScanFetch (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="373" width="1.9" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="127.59" y="383.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,849,644,793 samples, 0.02%)</title><rect x="819.7" y="373" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="822.75" y="383.5" ></text>
</g>
<g >
<title>exec_simple_query (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="645" width="0.6" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="1160.94" y="655.5" ></text>
</g>
<g >
<title>RelationGetIndexList (4,384,166,444 samples, 0.05%)</title><rect x="931.1" y="405" width="0.5" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="934.05" y="415.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (139,237,482,350 samples, 1.48%)</title><rect x="189.6" y="485" width="17.4" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="192.60" y="495.5" ></text>
</g>
<g >
<title>create_indexscan_plan (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="469" width="0.1" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="88.40" y="479.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (991,727,909 samples, 0.01%)</title><rect x="306.3" y="181" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="309.32" y="191.5" ></text>
</g>
<g >
<title>GetCurrentTimestamp (2,476,158,138 samples, 0.03%)</title><rect x="207.6" y="581" width="0.3" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="210.63" y="591.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="341" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="88.40" y="351.5" ></text>
</g>
<g >
<title>handle_softirqs (1,559,540,350 samples, 0.02%)</title><rect x="750.9" y="485" width="0.2" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="753.87" y="495.5" ></text>
</g>
<g >
<title>query_tree_walker_impl (56,333,435,022 samples, 0.60%)</title><rect x="801.5" y="469" width="7.0" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="804.49" y="479.5" ></text>
</g>
<g >
<title>fix_expr_common (1,050,418,394 samples, 0.01%)</title><rect x="902.8" y="325" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="905.83" y="335.5" ></text>
</g>
<g >
<title>pgstat_count_io_op_time (3,499,376,825 samples, 0.04%)</title><rect x="503.3" y="453" width="0.4" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="506.29" y="463.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (886,882,643 samples, 0.01%)</title><rect x="124.5" y="453" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="127.48" y="463.5" ></text>
</g>
<g >
<title>palloc (2,224,153,278 samples, 0.02%)</title><rect x="340.2" y="245" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="343.19" y="255.5" ></text>
</g>
<g >
<title>selinux_file_permission (3,346,544,753 samples, 0.04%)</title><rect x="494.9" y="357" width="0.4" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="497.87" y="367.5" ></text>
</g>
<g >
<title>native_write_msr (3,099,312,016 samples, 0.03%)</title><rect x="161.4" y="277" width="0.4" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="164.45" y="287.5" ></text>
</g>
<g >
<title>pgstat_report_plan_id (2,737,729,362 samples, 0.03%)</title><rect x="874.0" y="533" width="0.4" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="877.05" y="543.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (2,079,231,909 samples, 0.02%)</title><rect x="83.0" y="325" width="0.2" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="85.96" y="335.5" ></text>
</g>
<g >
<title>castNodeImpl (6,529,480,881 samples, 0.07%)</title><rect x="1124.9" y="757" width="0.8" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="1127.91" y="767.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,033,276,146 samples, 0.01%)</title><rect x="467.7" y="469" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="470.66" y="479.5" ></text>
</g>
<g >
<title>estimate_expression_value (3,397,404,926 samples, 0.04%)</title><rect x="1031.2" y="245" width="0.4" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="1034.21" y="255.5" ></text>
</g>
<g >
<title>pull_varattnos (11,297,960,034 samples, 0.12%)</title><rect x="997.0" y="341" width="1.4" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="999.98" y="351.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (901,778,171 samples, 0.01%)</title><rect x="964.6" y="341" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="967.60" y="351.5" ></text>
</g>
<g >
<title>LockAcquireExtended (30,275,932,855 samples, 0.32%)</title><rect x="818.2" y="389" width="3.8" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="821.21" y="399.5" ></text>
</g>
<g >
<title>IsBinaryTidClause (3,268,976,203 samples, 0.03%)</title><rect x="1025.3" y="341" width="0.4" height="15.0" fill="rgb(214,41,9)" rx="2" ry="2" />
<text  x="1028.28" y="351.5" ></text>
</g>
<g >
<title>ep_done_scan (2,363,917,908 samples, 0.03%)</title><rect x="156.1" y="373" width="0.3" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="159.12" y="383.5" ></text>
</g>
<g >
<title>list_nth_cell (891,985,513 samples, 0.01%)</title><rect x="979.4" y="453" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="982.36" y="463.5" ></text>
</g>
<g >
<title>ExecAssignScanProjectionInfo (79,881,849,599 samples, 0.85%)</title><rect x="415.7" y="421" width="10.0" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="418.71" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="581" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1136.99" y="591.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,208,256,248 samples, 0.01%)</title><rect x="877.5" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="880.46" y="447.5" ></text>
</g>
<g >
<title>handle_softirqs (866,318,073 samples, 0.01%)</title><rect x="791.2" y="469" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="794.16" y="479.5" ></text>
</g>
<g >
<title>palloc (1,332,387,157 samples, 0.01%)</title><rect x="871.1" y="469" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="874.15" y="479.5" ></text>
</g>
<g >
<title>heap_page_prune_execute (1,371,185,405 samples, 0.01%)</title><rect x="1148.0" y="725" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1151.01" y="735.5" ></text>
</g>
<g >
<title>equal (816,326,830 samples, 0.01%)</title><rect x="1133.7" y="757" width="0.1" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1136.75" y="767.5" ></text>
</g>
<g >
<title>update_se (3,183,939,445 samples, 0.03%)</title><rect x="480.4" y="197" width="0.4" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="483.36" y="207.5" ></text>
</g>
<g >
<title>EvalPlanQualSetPlan (801,897,149 samples, 0.01%)</title><rect x="412.8" y="453" width="0.1" height="15.0" fill="rgb(236,145,34)" rx="2" ry="2" />
<text  x="415.83" y="463.5" ></text>
</g>
<g >
<title>verify_compact_attribute (2,049,139,193 samples, 0.02%)</title><rect x="83.0" y="229" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="85.97" y="239.5" ></text>
</g>
<g >
<title>ExecScanExtended (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="405" width="1.7" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="125.45" y="415.5" ></text>
</g>
<g >
<title>selinux_socket_sendmsg (5,105,506,192 samples, 0.05%)</title><rect x="191.0" y="405" width="0.7" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="194.03" y="415.5" ></text>
</g>
<g >
<title>palloc0 (1,850,220,646 samples, 0.02%)</title><rect x="888.4" y="293" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="891.35" y="303.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (857,285,167 samples, 0.01%)</title><rect x="94.7" y="165" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="97.67" y="175.5" ></text>
</g>
<g >
<title>_mdnblocks (15,889,278,736 samples, 0.17%)</title><rect x="936.1" y="261" width="2.0" height="15.0" fill="rgb(217,58,14)" rx="2" ry="2" />
<text  x="939.10" y="271.5" ></text>
</g>
<g >
<title>QueryRewrite (1,194,128,620 samples, 0.01%)</title><rect x="87.1" y="757" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="90.07" y="767.5" ></text>
</g>
<g >
<title>string_hash (2,166,111,304 samples, 0.02%)</title><rect x="214.8" y="549" width="0.3" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="217.81" y="559.5" ></text>
</g>
<g >
<title>__futex_wait (884,973,785 samples, 0.01%)</title><rect x="363.4" y="197" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="366.41" y="207.5" ></text>
</g>
<g >
<title>set_next_task_idle (3,122,835,178 samples, 0.03%)</title><rect x="160.8" y="325" width="0.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="163.76" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,083,305,400 samples, 0.01%)</title><rect x="1013.1" y="229" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1016.14" y="239.5" ></text>
</g>
<g >
<title>index_getnext_slot (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="357" width="1.7" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="125.45" y="367.5" ></text>
</g>
<g >
<title>standard_planner (2,495,599,539 samples, 0.03%)</title><rect x="126.5" y="549" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="129.52" y="559.5" ></text>
</g>
<g >
<title>list_concat (2,822,906,261 samples, 0.03%)</title><rect x="899.4" y="485" width="0.3" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="902.35" y="495.5" ></text>
</g>
<g >
<title>is_redundant_with_indexclauses (1,439,844,552 samples, 0.02%)</title><rect x="891.1" y="389" width="0.2" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="894.10" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,305,154,704 samples, 0.01%)</title><rect x="349.9" y="261" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="352.90" y="271.5" ></text>
</g>
<g >
<title>add_row_identity_var (7,939,602,976 samples, 0.08%)</title><rect x="920.7" y="453" width="1.0" height="15.0" fill="rgb(210,25,5)" rx="2" ry="2" />
<text  x="923.73" y="463.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (8,733,915,880 samples, 0.09%)</title><rect x="955.9" y="373" width="1.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="958.88" y="383.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,161,549,664 samples, 0.02%)</title><rect x="274.2" y="389" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="277.19" y="399.5" ></text>
</g>
<g >
<title>pfree (4,468,470,764 samples, 0.05%)</title><rect x="978.8" y="421" width="0.6" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="981.80" y="431.5" ></text>
</g>
<g >
<title>exit_to_user_mode_loop (9,885,165,119 samples, 0.10%)</title><rect x="173.1" y="437" width="1.2" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="176.06" y="447.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,559,561,492 samples, 0.02%)</title><rect x="344.1" y="277" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="347.10" y="287.5" ></text>
</g>
<g >
<title>new_list (2,440,907,466 samples, 0.03%)</title><rect x="449.2" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="452.23" y="447.5" ></text>
</g>
<g >
<title>pgstat_count_heap_update (9,958,854,518 samples, 0.11%)</title><rect x="387.1" y="357" width="1.2" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="390.09" y="367.5" ></text>
</g>
<g >
<title>try_to_wake_up (896,553,262 samples, 0.01%)</title><rect x="470.4" y="277" width="0.2" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="473.44" y="287.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,654,671,758 samples, 0.02%)</title><rect x="296.6" y="229" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="299.62" y="239.5" ></text>
</g>
<g >
<title>dequeue_entity (16,231,831,711 samples, 0.17%)</title><rect x="479.6" y="229" width="2.0" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="482.61" y="239.5" ></text>
</g>
<g >
<title>new_list (4,641,557,119 samples, 0.05%)</title><rect x="1162.9" y="757" width="0.6" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1165.94" y="767.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (1,318,387,264 samples, 0.01%)</title><rect x="426.8" y="373" width="0.2" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="429.84" y="383.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (1,189,490,969 samples, 0.01%)</title><rect x="868.2" y="469" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="871.23" y="479.5" ></text>
</g>
<g >
<title>enlargeStringInfo (911,142,644 samples, 0.01%)</title><rect x="188.0" y="549" width="0.1" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="190.95" y="559.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="373" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="127.24" y="383.5" ></text>
</g>
<g >
<title>palloc (3,159,038,139 samples, 0.03%)</title><rect x="945.3" y="405" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="948.34" y="415.5" ></text>
</g>
<g >
<title>_copyVar (3,369,540,887 samples, 0.04%)</title><rect x="888.2" y="325" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="891.16" y="335.5" ></text>
</g>
<g >
<title>planstate_walk_subplans (1,073,575,633 samples, 0.01%)</title><rect x="1172.3" y="757" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1175.30" y="767.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (2,273,783,911 samples, 0.02%)</title><rect x="363.6" y="293" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="366.65" y="303.5" ></text>
</g>
<g >
<title>ShowTransactionState (1,111,797,917 samples, 0.01%)</title><rect x="105.0" y="757" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="108.04" y="767.5" ></text>
</g>
<g >
<title>pg_analyze_and_rewrite_fixedparams (570,565,614,590 samples, 6.05%)</title><rect x="798.2" y="581" width="71.3" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="801.20" y="591.5" >pg_analy..</text>
</g>
<g >
<title>ProcessQuery (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="597" width="0.5" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="1160.94" y="607.5" ></text>
</g>
<g >
<title>exec_rt_fetch (1,114,860,165 samples, 0.01%)</title><rect x="439.9" y="421" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="442.89" y="431.5" ></text>
</g>
<g >
<title>verify_compact_attribute (2,524,987,728 samples, 0.03%)</title><rect x="1189.4" y="757" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1192.41" y="767.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (1,179,151,144 samples, 0.01%)</title><rect x="1077.2" y="469" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1080.19" y="479.5" ></text>
</g>
<g >
<title>pg_plan_query (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="613" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="1161.36" y="623.5" ></text>
</g>
<g >
<title>AllocSetFree (1,220,523,978 samples, 0.01%)</title><rect x="249.2" y="405" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="252.19" y="415.5" ></text>
</g>
<g >
<title>fix_indexqual_references (30,749,781,246 samples, 0.33%)</title><rect x="887.3" y="389" width="3.8" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="890.25" y="399.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,047,421,377 samples, 0.01%)</title><rect x="400.3" y="245" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="403.29" y="255.5" ></text>
</g>
<g >
<title>lappend (3,429,797,266 samples, 0.04%)</title><rect x="884.6" y="421" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="887.62" y="431.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,031,759,564 samples, 0.02%)</title><rect x="828.2" y="341" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="831.17" y="351.5" ></text>
</g>
<g >
<title>relation_open (31,838,526,188 samples, 0.34%)</title><rect x="940.1" y="389" width="4.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="943.13" y="399.5" ></text>
</g>
<g >
<title>list_nth_cell (4,344,746,424 samples, 0.05%)</title><rect x="1157.0" y="757" width="0.5" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1159.97" y="767.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,218,570,205 samples, 0.01%)</title><rect x="232.0" y="485" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="234.99" y="495.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (7,014,131,750 samples, 0.07%)</title><rect x="285.0" y="245" width="0.8" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="287.96" y="255.5" ></text>
</g>
<g >
<title>palloc0 (4,033,733,464 samples, 0.04%)</title><rect x="421.0" y="325" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="424.00" y="335.5" ></text>
</g>
<g >
<title>remove_useless_groupby_columns (1,931,717,671 samples, 0.02%)</title><rect x="1043.7" y="469" width="0.2" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="1046.68" y="479.5" ></text>
</g>
<g >
<title>create_empty_pathtarget (1,833,519,991 samples, 0.02%)</title><rect x="913.7" y="469" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="916.68" y="479.5" ></text>
</g>
<g >
<title>restriction_selectivity (66,522,931,421 samples, 0.70%)</title><rect x="1029.7" y="341" width="8.3" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="1032.70" y="351.5" ></text>
</g>
<g >
<title>migrate_task_rq_fair (3,908,141,928 samples, 0.04%)</title><rect x="203.2" y="309" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="206.24" y="319.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,050,202,787 samples, 0.01%)</title><rect x="296.8" y="165" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="299.84" y="175.5" ></text>
</g>
<g >
<title>ModifyWaitEvent (826,528,478 samples, 0.01%)</title><rect x="78.7" y="757" width="0.1" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="81.72" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,377,722,920 samples, 0.03%)</title><rect x="446.0" y="357" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="448.99" y="367.5" ></text>
</g>
<g >
<title>_bt_preprocess_array_keys (1,393,708,179 samples, 0.01%)</title><rect x="122.5" y="277" width="0.1" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="125.45" y="287.5" ></text>
</g>
<g >
<title>hash_bytes (2,499,941,538 samples, 0.03%)</title><rect x="367.1" y="197" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="370.07" y="207.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,323,557,501 samples, 0.01%)</title><rect x="1047.9" y="373" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1050.91" y="383.5" ></text>
</g>
<g >
<title>socket_putmessage (2,848,053,623 samples, 0.03%)</title><rect x="187.5" y="565" width="0.4" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="190.53" y="575.5" ></text>
</g>
<g >
<title>relation_openrv_extended (109,097,271,938 samples, 1.16%)</title><rect x="817.4" y="437" width="13.6" height="15.0" fill="rgb(231,119,28)" rx="2" ry="2" />
<text  x="820.39" y="447.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,264,334,352 samples, 0.01%)</title><rect x="363.4" y="325" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="366.38" y="335.5" ></text>
</g>
<g >
<title>create_indexscan_plan (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="437" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="127.24" y="447.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,941,658,797 samples, 0.02%)</title><rect x="423.6" y="293" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="426.61" y="303.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,116,617,332 samples, 0.01%)</title><rect x="363.4" y="277" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="366.39" y="287.5" ></text>
</g>
<g >
<title>MakeSingleTupleTableSlot (20,340,886,046 samples, 0.22%)</title><rect x="275.7" y="405" width="2.6" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="278.72" y="415.5" ></text>
</g>
<g >
<title>SearchSysCache1 (7,465,633,656 samples, 0.08%)</title><rect x="807.4" y="341" width="1.0" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="810.44" y="351.5" ></text>
</g>
<g >
<title>InjectionPointRun (898,656,866 samples, 0.01%)</title><rect x="53.1" y="757" width="0.1" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="56.10" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (984,416,934 samples, 0.01%)</title><rect x="978.1" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="981.09" y="383.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,074,793,147 samples, 0.01%)</title><rect x="428.5" y="373" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="431.52" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,280,631,859 samples, 0.01%)</title><rect x="898.0" y="453" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="900.95" y="463.5" ></text>
</g>
<g >
<title>PointerGetDatum (2,972,797,032 samples, 0.03%)</title><rect x="81.7" y="757" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="84.68" y="767.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (876,126,871 samples, 0.01%)</title><rect x="868.0" y="405" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="870.98" y="415.5" ></text>
</g>
<g >
<title>pfree (1,569,317,336 samples, 0.02%)</title><rect x="321.6" y="245" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="324.58" y="255.5" ></text>
</g>
<g >
<title>ReportChangedGUCOptions (1,275,168,666 samples, 0.01%)</title><rect x="207.3" y="597" width="0.2" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="210.34" y="607.5" ></text>
</g>
<g >
<title>eval_const_expressions (975,983,726 samples, 0.01%)</title><rect x="126.7" y="485" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="129.71" y="495.5" ></text>
</g>
<g >
<title>tas (1,045,131,235 samples, 0.01%)</title><rect x="507.9" y="437" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="510.87" y="447.5" ></text>
</g>
<g >
<title>ExecReadyInterpretedExpr (2,870,735,029 samples, 0.03%)</title><rect x="272.3" y="389" width="0.4" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="275.32" y="399.5" ></text>
</g>
<g >
<title>wipe_mem (847,294,073 samples, 0.01%)</title><rect x="521.7" y="405" width="0.1" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="524.69" y="415.5" ></text>
</g>
<g >
<title>finalize_in_progress_typentries (836,275,155 samples, 0.01%)</title><rect x="1137.1" y="757" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1140.10" y="767.5" ></text>
</g>
<g >
<title>pg_ulltoa_n (1,666,366,157 samples, 0.02%)</title><rect x="1169.9" y="757" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1172.88" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,618,736,767 samples, 0.02%)</title><rect x="814.9" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="817.92" y="399.5" ></text>
</g>
<g >
<title>ceil@plt (921,226,412 samples, 0.01%)</title><rect x="1012.3" y="293" width="0.2" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="1015.34" y="303.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (3,039,681,679 samples, 0.03%)</title><rect x="456.6" y="549" width="0.4" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="459.59" y="559.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (967,911,353 samples, 0.01%)</title><rect x="1166.4" y="757" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="1169.36" y="767.5" ></text>
</g>
<g >
<title>ExecUpdatePrepareSlot (802,628,656 samples, 0.01%)</title><rect x="344.4" y="405" width="0.1" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="347.37" y="415.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (9,627,323,022 samples, 0.10%)</title><rect x="486.6" y="245" width="1.2" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="489.60" y="255.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,533,240,777 samples, 0.04%)</title><rect x="866.0" y="421" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="869.01" y="431.5" ></text>
</g>
<g >
<title>unix_stream_sendmsg (116,455,594,683 samples, 1.23%)</title><rect x="191.7" y="421" width="14.5" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="194.67" y="431.5" ></text>
</g>
<g >
<title>FreeSnapshot (1,664,998,059 samples, 0.02%)</title><rect x="232.9" y="533" width="0.2" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="235.91" y="543.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="709" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1136.99" y="719.5" ></text>
</g>
<g >
<title>fix_expr_common (3,069,260,076 samples, 0.03%)</title><rect x="903.4" y="437" width="0.4" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="906.40" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,927,252,364 samples, 0.02%)</title><rect x="1116.8" y="677" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1119.84" y="687.5" ></text>
</g>
<g >
<title>ExecInterpExpr (1,034,833,313 samples, 0.01%)</title><rect x="44.4" y="757" width="0.1" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="47.35" y="767.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (803,236,650 samples, 0.01%)</title><rect x="368.0" y="229" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="370.98" y="239.5" ></text>
</g>
<g >
<title>ExecIndexScan (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="469" width="2.8" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="85.62" y="479.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,287,305,201 samples, 0.02%)</title><rect x="1185.0" y="581" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1187.99" y="591.5" ></text>
</g>
<g >
<title>make_modifytable (12,089,425,633 samples, 0.13%)</title><rect x="892.4" y="469" width="1.6" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="895.44" y="479.5" ></text>
</g>
<g >
<title>index_getnext_slot (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="341" width="1.9" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="127.59" y="351.5" ></text>
</g>
<g >
<title>palloc (1,298,727,118 samples, 0.01%)</title><rect x="978.1" y="389" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="981.05" y="399.5" ></text>
</g>
<g >
<title>table_close (2,517,234,961 samples, 0.03%)</title><rect x="799.1" y="533" width="0.3" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="802.11" y="543.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,084,078,111 samples, 0.01%)</title><rect x="39.1" y="757" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="42.07" y="767.5" ></text>
</g>
<g >
<title>secure_read (276,831,650,799 samples, 2.93%)</title><rect x="150.2" y="533" width="34.6" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="153.21" y="543.5" >se..</text>
</g>
<g >
<title>palloc0 (2,661,918,344 samples, 0.03%)</title><rect x="877.0" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="879.98" y="463.5" ></text>
</g>
<g >
<title>hash_bytes (3,440,276,739 samples, 0.04%)</title><rect x="825.5" y="277" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="828.52" y="287.5" ></text>
</g>
<g >
<title>AllocSetDelete (14,323,127,708 samples, 0.15%)</title><rect x="132.0" y="533" width="1.8" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="135.04" y="543.5" ></text>
</g>
<g >
<title>SearchCatCache3 (4,934,294,837 samples, 0.05%)</title><rect x="1011.0" y="277" width="0.6" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="1014.01" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,212,025,269 samples, 0.02%)</title><rect x="1144.1" y="757" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1147.14" y="767.5" ></text>
</g>
<g >
<title>check_index_only (19,420,187,942 samples, 0.21%)</title><rect x="996.0" y="357" width="2.4" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="998.96" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkGetValue (815,249,886 samples, 0.01%)</title><rect x="113.9" y="741" width="0.1" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="116.93" y="751.5" ></text>
</g>
<g >
<title>__log_finite@GLIBC_2.15@plt (3,793,989,002 samples, 0.04%)</title><rect x="1000.8" y="309" width="0.5" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="1003.78" y="319.5" ></text>
</g>
<g >
<title>palloc (1,355,312,356 samples, 0.01%)</title><rect x="815.4" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="818.43" y="431.5" ></text>
</g>
<g >
<title>SearchCatCache1 (4,024,861,062 samples, 0.04%)</title><rect x="407.5" y="405" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="410.52" y="415.5" ></text>
</g>
<g >
<title>CreateExprContext (10,213,189,258 samples, 0.11%)</title><rect x="414.4" y="405" width="1.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="417.44" y="415.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,163,523,597 samples, 0.01%)</title><rect x="1069.0" y="405" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1072.01" y="415.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,411,762,772 samples, 0.03%)</title><rect x="996.3" y="325" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="999.29" y="335.5" ></text>
</g>
<g >
<title>pstrdup (3,082,561,379 samples, 0.03%)</title><rect x="816.0" y="437" width="0.4" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="819.02" y="447.5" ></text>
</g>
<g >
<title>palloc (1,206,980,858 samples, 0.01%)</title><rect x="393.9" y="341" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="396.85" y="351.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (5,398,623,691 samples, 0.06%)</title><rect x="350.7" y="261" width="0.7" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="353.74" y="271.5" ></text>
</g>
<g >
<title>GetCurrentTransactionStopTimestamp (4,175,603,632 samples, 0.04%)</title><rect x="468.8" y="501" width="0.5" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="471.78" y="511.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (803,901,079 samples, 0.01%)</title><rect x="126.6" y="245" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="129.56" y="255.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (165,355,302,821 samples, 1.75%)</title><rect x="153.8" y="469" width="20.7" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="156.80" y="479.5" ></text>
</g>
<g >
<title>LWLockConflictsWithVar (1,023,428,235 samples, 0.01%)</title><rect x="56.5" y="757" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="59.49" y="767.5" ></text>
</g>
<g >
<title>__update_idle_core (1,036,576,931 samples, 0.01%)</title><rect x="477.3" y="245" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="480.26" y="255.5" ></text>
</g>
<g >
<title>ReleaseSysCache (2,339,448,378 samples, 0.02%)</title><rect x="435.3" y="357" width="0.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="438.32" y="367.5" ></text>
</g>
<g >
<title>PortalRunMulti (992,692,643 samples, 0.01%)</title><rect x="82.3" y="757" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="85.30" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,375,100,389 samples, 0.05%)</title><rect x="418.2" y="261" width="0.5" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="421.20" y="271.5" ></text>
</g>
<g >
<title>hash_bytes (3,382,762,691 samples, 0.04%)</title><rect x="359.1" y="245" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="362.07" y="255.5" ></text>
</g>
<g >
<title>tag_hash (2,747,427,520 samples, 0.03%)</title><rect x="1186.5" y="757" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1189.53" y="767.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,103,642,114 samples, 0.01%)</title><rect x="347.8" y="341" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="350.77" y="351.5" ></text>
</g>
<g >
<title>btbeginscan (22,429,128,076 samples, 0.24%)</title><rect x="290.3" y="293" width="2.8" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="293.26" y="303.5" ></text>
</g>
<g >
<title>palloc0 (2,258,186,547 samples, 0.02%)</title><rect x="812.9" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="815.86" y="447.5" ></text>
</g>
<g >
<title>_bt_binsrch (60,929,855,501 samples, 0.65%)</title><rect x="313.9" y="261" width="7.6" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="316.90" y="271.5" ></text>
</g>
<g >
<title>__strlen_avx2 (923,050,285 samples, 0.01%)</title><rect x="1112.7" y="693" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1115.69" y="703.5" ></text>
</g>
<g >
<title>lappend (2,972,015,979 samples, 0.03%)</title><rect x="919.1" y="469" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="922.12" y="479.5" ></text>
</g>
<g >
<title>__libc_start_call_main (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="725" width="953.9" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="133.07" y="735.5" >__libc_start_call_main</text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,337,850,130 samples, 0.01%)</title><rect x="381.8" y="261" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="384.76" y="271.5" ></text>
</g>
<g >
<title>__futex_wait (1,514,989,439 samples, 0.02%)</title><rect x="490.6" y="325" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="493.56" y="335.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (964,630,174 samples, 0.01%)</title><rect x="467.7" y="437" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="470.66" y="447.5" ></text>
</g>
<g >
<title>newNode (4,167,390,496 samples, 0.04%)</title><rect x="891.5" y="373" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="894.51" y="383.5" ></text>
</g>
<g >
<title>DynaHashAlloc (1,644,660,769 samples, 0.02%)</title><rect x="1064.1" y="437" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1067.05" y="447.5" ></text>
</g>
<g >
<title>StartReadBuffer (26,936,985,946 samples, 0.29%)</title><rect x="297.4" y="197" width="3.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="300.43" y="207.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,468,653,060 samples, 0.02%)</title><rect x="293.7" y="293" width="0.2" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="296.71" y="303.5" ></text>
</g>
<g >
<title>switch_fpu_return (5,808,646,739 samples, 0.06%)</title><rect x="172.3" y="421" width="0.8" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="175.33" y="431.5" ></text>
</g>
<g >
<title>tag_hash (2,752,133,059 samples, 0.03%)</title><rect x="820.4" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="823.36" y="351.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (953,117,129 samples, 0.01%)</title><rect x="389.6" y="357" width="0.2" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="392.65" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,900,260,385 samples, 0.02%)</title><rect x="814.5" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="817.49" y="399.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (862,281,137 samples, 0.01%)</title><rect x="89.7" y="757" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="92.66" y="767.5" ></text>
</g>
<g >
<title>rseq_get_rseq_cs.isra.0 (959,245,051 samples, 0.01%)</title><rect x="483.8" y="341" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="486.83" y="351.5" ></text>
</g>
<g >
<title>AllocSetFree (3,412,943,860 samples, 0.04%)</title><rect x="28.8" y="757" width="0.4" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="31.79" y="767.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,098,373,844 samples, 0.01%)</title><rect x="232.5" y="501" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="235.49" y="511.5" ></text>
</g>
<g >
<title>make_plain_restrictinfo (24,468,151,337 samples, 0.26%)</title><rect x="965.8" y="389" width="3.0" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="968.78" y="399.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,246,201,243 samples, 0.01%)</title><rect x="85.1" y="165" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="88.13" y="175.5" ></text>
</g>
<g >
<title>bms_add_members (3,065,037,232 samples, 0.03%)</title><rect x="969.9" y="373" width="0.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="972.88" y="383.5" ></text>
</g>
<g >
<title>find_oper_cache_entry (13,807,117,198 samples, 0.15%)</title><rect x="836.5" y="357" width="1.7" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="839.49" y="367.5" ></text>
</g>
<g >
<title>palloc0 (3,359,567,899 samples, 0.04%)</title><rect x="428.3" y="405" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="431.29" y="415.5" ></text>
</g>
<g >
<title>postgres (9,437,180,925,478 samples, 100.00%)</title><rect x="10.0" y="773" width="1180.0" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="13.00" y="783.5" >postgres</text>
</g>
<g >
<title>pg_class_aclmask (9,780,708,039 samples, 0.10%)</title><rect x="406.8" y="453" width="1.2" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="409.83" y="463.5" ></text>
</g>
<g >
<title>do_futex (1,600,736,131 samples, 0.02%)</title><rect x="490.6" y="357" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="493.55" y="367.5" ></text>
</g>
<g >
<title>ExecClearTuple (884,922,045 samples, 0.01%)</title><rect x="42.1" y="757" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="45.09" y="767.5" ></text>
</g>
<g >
<title>expand_virtual_generated_columns (871,699,727 samples, 0.01%)</title><rect x="1058.7" y="485" width="0.1" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1061.66" y="495.5" ></text>
</g>
<g >
<title>heapam_slot_callbacks (1,730,192,845 samples, 0.02%)</title><rect x="1149.7" y="757" width="0.2" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="1152.68" y="767.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,188,508,199 samples, 0.01%)</title><rect x="364.4" y="325" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="367.42" y="335.5" ></text>
</g>
<g >
<title>MemoryContextStrdup (4,019,711,436 samples, 0.04%)</title><rect x="814.7" y="421" width="0.5" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="817.75" y="431.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="469" width="0.5" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1160.94" y="479.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (2,169,823,607 samples, 0.02%)</title><rect x="359.8" y="341" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="362.80" y="351.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,404,915,678 samples, 0.06%)</title><rect x="830.1" y="373" width="0.7" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="833.13" y="383.5" ></text>
</g>
<g >
<title>object_aclmask_ext (1,054,538,514 samples, 0.01%)</title><rect x="420.8" y="293" width="0.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="423.83" y="303.5" ></text>
</g>
<g >
<title>LockHeldByMe (986,139,823 samples, 0.01%)</title><rect x="58.0" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="60.96" y="767.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,674,839,267 samples, 0.02%)</title><rect x="490.6" y="405" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="493.55" y="415.5" ></text>
</g>
<g >
<title>PopActiveSnapshot (5,318,639,462 samples, 0.06%)</title><rect x="221.7" y="581" width="0.7" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="224.75" y="591.5" ></text>
</g>
<g >
<title>bms_overlap (1,854,488,419 samples, 0.02%)</title><rect x="376.1" y="357" width="0.2" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="379.07" y="367.5" ></text>
</g>
<g >
<title>printtup_create_DR (853,868,650 samples, 0.01%)</title><rect x="1173.6" y="757" width="0.1" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="1176.57" y="767.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple (3,487,181,926 samples, 0.04%)</title><rect x="343.9" y="325" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="346.88" y="335.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,278,440,340 samples, 0.05%)</title><rect x="96.2" y="181" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="99.20" y="191.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (2,043,583,317 samples, 0.02%)</title><rect x="961.2" y="309" width="0.2" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="964.17" y="319.5" ></text>
</g>
<g >
<title>btgettuple (12,435,172,942 samples, 0.13%)</title><rect x="280.2" y="309" width="1.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="283.20" y="319.5" ></text>
</g>
<g >
<title>build_index_paths (191,514,419,509 samples, 2.03%)</title><rect x="994.6" y="373" width="24.0" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="997.65" y="383.5" >b..</text>
</g>
<g >
<title>LockAcquireExtended (1,360,462,136 samples, 0.01%)</title><rect x="57.5" y="757" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="60.47" y="767.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,134,240,196 samples, 0.01%)</title><rect x="354.4" y="213" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="357.39" y="223.5" ></text>
</g>
<g >
<title>index_getnext_tid (17,333,438,750 samples, 0.18%)</title><rect x="280.0" y="325" width="2.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="283.04" y="335.5" ></text>
</g>
<g >
<title>ReleaseSysCache (850,106,674 samples, 0.01%)</title><rect x="960.4" y="293" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="963.36" y="303.5" ></text>
</g>
<g >
<title>palloc0 (1,307,955,479 samples, 0.01%)</title><rect x="810.5" y="437" width="0.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="813.46" y="447.5" ></text>
</g>
<g >
<title>palloc (1,292,827,613 samples, 0.01%)</title><rect x="847.5" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="850.49" y="383.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,090,198,011 samples, 0.01%)</title><rect x="126.1" y="197" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="129.10" y="207.5" ></text>
</g>
<g >
<title>RelationGetIndexAttrBitmap (11,781,874,405 samples, 0.12%)</title><rect x="368.7" y="357" width="1.5" height="15.0" fill="rgb(222,79,19)" rx="2" ry="2" />
<text  x="371.75" y="367.5" ></text>
</g>
<g >
<title>get_func_retset (7,993,348,119 samples, 0.08%)</title><rect x="846.3" y="405" width="1.0" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="849.35" y="415.5" ></text>
</g>
<g >
<title>AtEOXact_Snapshot (2,391,190,032 samples, 0.03%)</title><rect x="464.2" y="517" width="0.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="467.18" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (960,049,647 samples, 0.01%)</title><rect x="839.1" y="309" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="842.12" y="319.5" ></text>
</g>
<g >
<title>PreCommit_on_commit_actions (1,111,691,687 samples, 0.01%)</title><rect x="465.9" y="517" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="468.86" y="527.5" ></text>
</g>
<g >
<title>LWLockRelease (2,113,139,705 samples, 0.02%)</title><rect x="221.1" y="549" width="0.3" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="224.11" y="559.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,253,765,743 samples, 0.01%)</title><rect x="943.9" y="325" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="946.92" y="335.5" ></text>
</g>
<g >
<title>heapam_index_fetch_reset (3,165,517,359 samples, 0.03%)</title><rect x="281.8" y="293" width="0.4" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="284.81" y="303.5" ></text>
</g>
<g >
<title>tag_hash (4,860,712,628 samples, 0.05%)</title><rect x="828.4" y="341" width="0.6" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="831.43" y="351.5" ></text>
</g>
<g >
<title>int4eqfast (1,416,829,266 samples, 0.02%)</title><rect x="1151.2" y="757" width="0.2" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="1154.19" y="767.5" ></text>
</g>
<g >
<title>FreeSnapshot (2,323,569,695 samples, 0.02%)</title><rect x="221.9" y="565" width="0.3" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="224.90" y="575.5" ></text>
</g>
<g >
<title>palloc (1,281,262,778 samples, 0.01%)</title><rect x="899.5" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="902.52" y="447.5" ></text>
</g>
<g >
<title>StartReadBuffer (17,789,300,448 samples, 0.19%)</title><rect x="366.5" y="309" width="2.2" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="369.50" y="319.5" ></text>
</g>
<g >
<title>PortalRun (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="629" width="0.5" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="1160.94" y="639.5" ></text>
</g>
<g >
<title>make_rel_from_joinlist (1,096,779,060 samples, 0.01%)</title><rect x="1159.9" y="757" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1162.87" y="767.5" ></text>
</g>
<g >
<title>do_futex (60,882,863,825 samples, 0.65%)</title><rect x="475.8" y="373" width="7.6" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="478.81" y="383.5" ></text>
</g>
<g >
<title>__calc_delta.constprop.0 (1,013,607,850 samples, 0.01%)</title><rect x="168.3" y="261" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="171.30" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,697,669,191 samples, 0.03%)</title><rect x="428.7" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="431.71" y="399.5" ></text>
</g>
<g >
<title>extract_update_targetlist_colnos (3,843,807,125 samples, 0.04%)</title><rect x="922.3" y="469" width="0.5" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="925.30" y="479.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,006,400,843 samples, 0.01%)</title><rect x="96.0" y="149" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="98.96" y="159.5" ></text>
</g>
<g >
<title>AllocSetFree (2,178,445,211 samples, 0.02%)</title><rect x="798.8" y="517" width="0.3" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="801.80" y="527.5" ></text>
</g>
<g >
<title>table_slot_create (23,298,784,360 samples, 0.25%)</title><rect x="275.6" y="421" width="2.9" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="278.62" y="431.5" ></text>
</g>
<g >
<title>hash_search (4,248,126,274 samples, 0.05%)</title><rect x="440.5" y="357" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="443.54" y="367.5" ></text>
</g>
<g >
<title>btint4cmp (1,365,484,364 samples, 0.01%)</title><rect x="1124.2" y="757" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1127.17" y="767.5" ></text>
</g>
<g >
<title>pull_var_clause (17,435,996,045 samples, 0.18%)</title><rect x="954.8" y="453" width="2.2" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="957.80" y="463.5" ></text>
</g>
<g >
<title>bsearch@plt (816,113,865 samples, 0.01%)</title><rect x="285.8" y="261" width="0.1" height="15.0" fill="rgb(226,100,23)" rx="2" ry="2" />
<text  x="288.84" y="271.5" ></text>
</g>
<g >
<title>PortalRunMulti (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="597" width="1.7" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="125.45" y="607.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,110,453,897 samples, 0.01%)</title><rect x="1014.9" y="245" width="0.1" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1017.85" y="255.5" ></text>
</g>
<g >
<title>selinux_socket_recvmsg (4,300,973,778 samples, 0.05%)</title><rect x="177.3" y="389" width="0.6" height="15.0" fill="rgb(227,104,25)" rx="2" ry="2" />
<text  x="180.35" y="399.5" ></text>
</g>
<g >
<title>CopyXLogRecordToWAL (9,663,899,662 samples, 0.10%)</title><rect x="379.2" y="309" width="1.2" height="15.0" fill="rgb(213,36,8)" rx="2" ry="2" />
<text  x="382.15" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (851,486,225 samples, 0.01%)</title><rect x="945.2" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="948.22" y="383.5" ></text>
</g>
<g >
<title>create_bitmap_heap_path (14,889,761,417 samples, 0.16%)</title><rect x="989.8" y="389" width="1.9" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="992.82" y="399.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (7,266,181,855 samples, 0.08%)</title><rect x="298.4" y="117" width="0.9" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="301.41" y="127.5" ></text>
</g>
<g >
<title>hash_bytes (11,485,276,461 samples, 0.12%)</title><rect x="849.9" y="341" width="1.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="852.94" y="351.5" ></text>
</g>
<g >
<title>heap_getattr (9,092,132,779 samples, 0.10%)</title><rect x="826.4" y="357" width="1.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="829.38" y="367.5" ></text>
</g>
<g >
<title>check_redundant_nullability_qual (1,042,115,703 samples, 0.01%)</title><rect x="965.3" y="405" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="968.34" y="415.5" ></text>
</g>
<g >
<title>ReleaseAndReadBuffer (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="293" width="0.7" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="87.72" y="303.5" ></text>
</g>
<g >
<title>setup_eager_aggregation (841,298,501 samples, 0.01%)</title><rect x="1044.2" y="469" width="0.1" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="1047.24" y="479.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,903,728,468 samples, 0.04%)</title><rect x="964.9" y="341" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="967.85" y="351.5" ></text>
</g>
<g >
<title>list_delete_ptr (5,445,843,031 samples, 0.06%)</title><rect x="251.4" y="469" width="0.7" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="254.44" y="479.5" ></text>
</g>
<g >
<title>LockBuffer (6,387,107,417 samples, 0.07%)</title><rect x="398.2" y="389" width="0.7" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="401.15" y="399.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (4,266,551,562 samples, 0.05%)</title><rect x="1054.0" y="421" width="0.6" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1057.04" y="431.5" ></text>
</g>
<g >
<title>LWLockWakeup (7,038,800,237 samples, 0.07%)</title><rect x="351.7" y="277" width="0.8" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="354.67" y="287.5" ></text>
</g>
<g >
<title>_bt_relbuf (7,388,287,010 samples, 0.08%)</title><rect x="324.7" y="229" width="1.0" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="327.74" y="239.5" ></text>
</g>
<g >
<title>base_yylex (92,047,656,846 samples, 0.98%)</title><rect x="1103.5" y="741" width="11.5" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1106.47" y="751.5" ></text>
</g>
<g >
<title>proclist_push_head_offset (961,411,365 samples, 0.01%)</title><rect x="474.8" y="453" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="477.77" y="463.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,006,933,506 samples, 0.01%)</title><rect x="398.7" y="277" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="401.67" y="287.5" ></text>
</g>
<g >
<title>index_close (13,230,057,433 samples, 0.14%)</title><rect x="236.1" y="453" width="1.7" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="239.10" y="463.5" ></text>
</g>
<g >
<title>ExecCheckPermissions (20,199,736,930 samples, 0.21%)</title><rect x="406.1" y="485" width="2.6" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="409.15" y="495.5" ></text>
</g>
<g >
<title>table_index_fetch_reset (3,628,120,789 samples, 0.04%)</title><rect x="281.8" y="309" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="284.75" y="319.5" ></text>
</g>
<g >
<title>LockBuffer (8,036,028,049 samples, 0.09%)</title><rect x="296.2" y="261" width="1.0" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="299.18" y="271.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (12,488,711,083 samples, 0.13%)</title><rect x="1075.8" y="501" width="1.6" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="1078.81" y="511.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,658,179,519 samples, 0.02%)</title><rect x="288.9" y="165" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="291.86" y="175.5" ></text>
</g>
<g >
<title>ReleaseSysCache (866,754,178 samples, 0.01%)</title><rect x="1056.5" y="421" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1059.53" y="431.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (1,705,897,301 samples, 0.02%)</title><rect x="310.0" y="213" width="0.3" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="313.04" y="223.5" ></text>
</g>
<g >
<title>tag_hash (2,925,242,434 samples, 0.03%)</title><rect x="445.4" y="341" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="448.36" y="351.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (14,709,367,593 samples, 0.16%)</title><rect x="119.4" y="741" width="1.8" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="122.39" y="751.5" ></text>
</g>
<g >
<title>standard_ExecutorEnd (222,751,119,073 samples, 2.36%)</title><rect x="234.8" y="517" width="27.9" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="237.84" y="527.5" >s..</text>
</g>
<g >
<title>RelationGetNumberOfBlocksInFork (11,300,070,965 samples, 0.12%)</title><rect x="931.8" y="405" width="1.4" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="934.77" y="415.5" ></text>
</g>
<g >
<title>CreateExecutorState (895,408,435 samples, 0.01%)</title><rect x="39.4" y="757" width="0.1" height="15.0" fill="rgb(228,105,25)" rx="2" ry="2" />
<text  x="42.41" y="767.5" ></text>
</g>
<g >
<title>clause_selectivity_ext (73,931,853,763 samples, 0.78%)</title><rect x="1028.8" y="357" width="9.3" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1031.81" y="367.5" ></text>
</g>
<g >
<title>genericcostestimate (21,570,724,734 samples, 0.23%)</title><rect x="1011.6" y="309" width="2.7" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1014.63" y="319.5" ></text>
</g>
<g >
<title>list_copy (3,142,417,912 samples, 0.03%)</title><rect x="393.6" y="373" width="0.4" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="396.62" y="383.5" ></text>
</g>
<g >
<title>ReleaseSysCache (860,275,277 samples, 0.01%)</title><rect x="1014.5" y="293" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1017.50" y="303.5" ></text>
</g>
<g >
<title>makeString (4,026,684,481 samples, 0.04%)</title><rect x="1116.6" y="725" width="0.5" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="1119.59" y="735.5" ></text>
</g>
<g >
<title>page_verify_redirects (987,531,719 samples, 0.01%)</title><rect x="1148.1" y="709" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1151.06" y="719.5" ></text>
</g>
<g >
<title>process_integer_literal (3,062,871,444 samples, 0.03%)</title><rect x="1114.4" y="709" width="0.4" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="1117.42" y="719.5" ></text>
</g>
<g >
<title>generate_base_implied_equalities (1,425,556,393 samples, 0.02%)</title><rect x="1139.0" y="757" width="0.2" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="1142.04" y="767.5" ></text>
</g>
<g >
<title>uint32_hash (1,219,537,086 samples, 0.01%)</title><rect x="1016.8" y="277" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1019.82" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (843,114,520 samples, 0.01%)</title><rect x="871.4" y="485" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="874.40" y="495.5" ></text>
</g>
<g >
<title>set_rel_pathlist (335,029,955,320 samples, 3.55%)</title><rect x="984.5" y="437" width="41.9" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="987.49" y="447.5" >set..</text>
</g>
<g >
<title>get_restriction_variable (44,792,031,446 samples, 0.47%)</title><rect x="1031.0" y="261" width="5.6" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="1033.95" y="271.5" ></text>
</g>
<g >
<title>futex_wake (1,605,003,608 samples, 0.02%)</title><rect x="470.4" y="309" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="473.36" y="319.5" ></text>
</g>
<g >
<title>__check_object_size.part.0 (4,864,702,396 samples, 0.05%)</title><rect x="183.7" y="309" width="0.6" height="15.0" fill="rgb(236,142,34)" rx="2" ry="2" />
<text  x="186.73" y="319.5" ></text>
</g>
<g >
<title>lappend (2,830,488,899 samples, 0.03%)</title><rect x="976.8" y="421" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="979.80" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,365,645,079 samples, 0.01%)</title><rect x="449.4" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="452.36" y="415.5" ></text>
</g>
<g >
<title>ExecScanExtended (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="389" width="1.9" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="127.59" y="399.5" ></text>
</g>
<g >
<title>get_relname_relid (40,670,556,973 samples, 0.43%)</title><rect x="822.5" y="389" width="5.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="825.51" y="399.5" ></text>
</g>
<g >
<title>pgstat_report_wait_start (901,932,183 samples, 0.01%)</title><rect x="1171.5" y="757" width="0.2" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="1174.55" y="767.5" ></text>
</g>
<g >
<title>ExecConditionalAssignProjectionInfo (79,433,751,100 samples, 0.84%)</title><rect x="415.8" y="405" width="9.9" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="418.77" y="415.5" ></text>
</g>
<g >
<title>PortalRunMulti (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="613" width="0.5" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1160.94" y="623.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (4,460,175,412 samples, 0.05%)</title><rect x="1052.5" y="421" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1055.55" y="431.5" ></text>
</g>
<g >
<title>AfterTriggerBeginXact (943,547,687 samples, 0.01%)</title><rect x="10.3" y="757" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="13.32" y="767.5" ></text>
</g>
<g >
<title>table_open (15,359,312,359 samples, 0.16%)</title><rect x="1067.3" y="485" width="1.9" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1070.26" y="495.5" ></text>
</g>
<g >
<title>ChoosePortalStrategy (3,267,619,470 samples, 0.03%)</title><rect x="455.2" y="565" width="0.4" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="458.18" y="575.5" ></text>
</g>
<g >
<title>slab_update_freelist.isra.0 (1,022,431,433 samples, 0.01%)</title><rect x="182.3" y="341" width="0.1" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="185.29" y="351.5" ></text>
</g>
<g >
<title>palloc (916,027,998 samples, 0.01%)</title><rect x="981.8" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="984.76" y="383.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,138,976,822 samples, 0.01%)</title><rect x="295.6" y="213" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="298.55" y="223.5" ></text>
</g>
<g >
<title>ExecEndIndexScan (65,628,078,743 samples, 0.70%)</title><rect x="238.6" y="437" width="8.2" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="241.61" y="447.5" ></text>
</g>
<g >
<title>palloc (1,477,709,315 samples, 0.02%)</title><rect x="417.2" y="309" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="420.20" y="319.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,918,249,590 samples, 0.05%)</title><rect x="441.3" y="389" width="0.6" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="444.29" y="399.5" ></text>
</g>
<g >
<title>makeFromExpr (2,368,158,493 samples, 0.03%)</title><rect x="808.5" y="485" width="0.3" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="811.55" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (943,873,443 samples, 0.01%)</title><rect x="1019.6" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1022.61" y="303.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,843,097,366 samples, 0.02%)</title><rect x="350.1" y="277" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="353.06" y="287.5" ></text>
</g>
<g >
<title>SysCacheGetAttrNotNull (49,245,008,738 samples, 0.52%)</title><rect x="1002.1" y="277" width="6.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="1005.15" y="287.5" ></text>
</g>
<g >
<title>unix_poll (3,072,329,504 samples, 0.03%)</title><rect x="157.3" y="341" width="0.4" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="160.30" y="351.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (852,532,760 samples, 0.01%)</title><rect x="1078.3" y="469" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1081.27" y="479.5" ></text>
</g>
<g >
<title>select_task_rq_fair (27,095,655,447 samples, 0.29%)</title><rect x="199.6" y="309" width="3.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="202.61" y="319.5" ></text>
</g>
<g >
<title>HeapTupleHeaderAdvanceConflictHorizon (861,619,540 samples, 0.01%)</title><rect x="1146.0" y="709" width="0.1" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="1149.02" y="719.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (2,025,970,730 samples, 0.02%)</title><rect x="325.0" y="181" width="0.2" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="327.98" y="191.5" ></text>
</g>
<g >
<title>shmem_file_llseek (1,641,907,696 samples, 0.02%)</title><rect x="932.9" y="261" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="935.89" y="271.5" ></text>
</g>
<g >
<title>makeVar (3,799,508,687 samples, 0.04%)</title><rect x="934.4" y="389" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="937.42" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,214,431,617 samples, 0.01%)</title><rect x="1058.2" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1061.24" y="415.5" ></text>
</g>
<g >
<title>BufferIsLockedByMeInMode (3,037,013,076 samples, 0.03%)</title><rect x="364.3" y="341" width="0.3" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="367.25" y="351.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,239,263,977 samples, 0.01%)</title><rect x="102.7" y="453" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="105.72" y="463.5" ></text>
</g>
<g >
<title>ReadBuffer (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="277" width="0.7" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="87.72" y="287.5" ></text>
</g>
<g >
<title>wake_affine (1,413,626,855 samples, 0.01%)</title><rect x="202.8" y="293" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="205.82" y="303.5" ></text>
</g>
<g >
<title>RegisterSnapshotOnOwner (4,153,320,640 samples, 0.04%)</title><rect x="233.9" y="501" width="0.5" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="236.93" y="511.5" ></text>
</g>
<g >
<title>XLogRecordAssemble (14,750,576,546 samples, 0.16%)</title><rect x="382.7" y="325" width="1.9" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="385.72" y="335.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="533" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="88.40" y="543.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple_internal (6,341,113,793 samples, 0.07%)</title><rect x="288.4" y="213" width="0.8" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="291.36" y="223.5" ></text>
</g>
<g >
<title>make_parsestate (5,495,144,209 samples, 0.06%)</title><rect x="799.4" y="549" width="0.7" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="802.43" y="559.5" ></text>
</g>
<g >
<title>futex_wake (1,418,667,084 samples, 0.02%)</title><rect x="354.7" y="133" width="0.1" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="357.65" y="143.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_or_u32 (958,784,958 samples, 0.01%)</title><rect x="1166.9" y="757" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="1169.86" y="767.5" ></text>
</g>
<g >
<title>PageGetLSN (905,321,982 samples, 0.01%)</title><rect x="80.6" y="757" width="0.1" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="83.60" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,066,417,037 samples, 0.01%)</title><rect x="922.6" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="925.62" y="415.5" ></text>
</g>
<g >
<title>MarkBufferDirtyHint (2,416,535,618 samples, 0.03%)</title><rect x="1146.7" y="677" width="0.3" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="1149.66" y="687.5" ></text>
</g>
<g >
<title>LWLockConditionalAcquire (3,234,326,015 samples, 0.03%)</title><rect x="469.8" y="453" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="472.81" y="463.5" ></text>
</g>
<g >
<title>replace_nestloop_params (14,125,778,347 samples, 0.15%)</title><rect x="888.7" y="357" width="1.8" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="891.73" y="367.5" ></text>
</g>
<g >
<title>schedule (107,500,493,911 samples, 1.14%)</title><rect x="158.6" y="373" width="13.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="161.57" y="383.5" ></text>
</g>
<g >
<title>HeapTupleHasNulls (887,160,945 samples, 0.01%)</title><rect x="50.8" y="757" width="0.1" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="53.82" y="767.5" ></text>
</g>
<g >
<title>_bt_relandgetbuf (1,217,297,839 samples, 0.01%)</title><rect x="124.1" y="277" width="0.1" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="127.08" y="287.5" ></text>
</g>
<g >
<title>LWLockRelease (3,669,367,424 samples, 0.04%)</title><rect x="363.5" y="341" width="0.5" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="366.55" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,020,466,053 samples, 0.02%)</title><rect x="945.9" y="389" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="948.94" y="399.5" ></text>
</g>
<g >
<title>fetch_att (860,419,010 samples, 0.01%)</title><rect x="1136.9" y="757" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1139.91" y="767.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,579,923,334 samples, 0.03%)</title><rect x="100.2" y="181" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="103.23" y="191.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVisibility (1,663,979,181 samples, 0.02%)</title><rect x="397.8" y="389" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="400.84" y="399.5" ></text>
</g>
<g >
<title>list_free (1,579,855,057 samples, 0.02%)</title><rect x="395.9" y="389" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="398.92" y="399.5" ></text>
</g>
<g >
<title>pfree (3,392,170,985 samples, 0.04%)</title><rect x="224.3" y="549" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="227.30" y="559.5" ></text>
</g>
<g >
<title>bms_add_members (1,100,188,250 samples, 0.01%)</title><rect x="1121.8" y="757" width="0.1" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="1124.81" y="767.5" ></text>
</g>
<g >
<title>ItemPointerIsValid (2,222,080,396 samples, 0.02%)</title><rect x="55.6" y="757" width="0.3" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="58.60" y="767.5" ></text>
</g>
<g >
<title>pg_nextpower2_32 (1,727,625,086 samples, 0.02%)</title><rect x="1163.3" y="741" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1166.31" y="751.5" ></text>
</g>
<g >
<title>AllocSetFree (1,163,214,572 samples, 0.01%)</title><rect x="877.8" y="453" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="880.84" y="463.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,115,623,333 samples, 0.01%)</title><rect x="89.8" y="757" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="92.85" y="767.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (310,290,018,988 samples, 3.29%)</title><rect x="712.0" y="533" width="38.8" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="715.04" y="543.5" >Mem..</text>
</g>
<g >
<title>FunctionCall2Coll (1,588,978,839 samples, 0.02%)</title><rect x="122.6" y="229" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="125.63" y="239.5" ></text>
</g>
<g >
<title>SearchSysCache1 (5,523,016,716 samples, 0.06%)</title><rect x="1042.2" y="341" width="0.7" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1045.25" y="351.5" ></text>
</g>
<g >
<title>tag_hash (2,709,571,307 samples, 0.03%)</title><rect x="95.1" y="149" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="98.11" y="159.5" ></text>
</g>
<g >
<title>GetScanKeyword (2,011,022,255 samples, 0.02%)</title><rect x="1111.2" y="693" width="0.3" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="1114.22" y="703.5" ></text>
</g>
<g >
<title>simplify_function (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="629" width="0.3" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1187.99" y="639.5" ></text>
</g>
<g >
<title>palloc0 (8,846,029,005 samples, 0.09%)</title><rect x="911.8" y="453" width="1.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="914.79" y="463.5" ></text>
</g>
<g >
<title>AllocSetAllocLarge (11,731,141,455 samples, 0.12%)</title><rect x="291.5" y="245" width="1.5" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="294.54" y="255.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,037,111,545 samples, 0.01%)</title><rect x="878.9" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="881.90" y="431.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,936,021,069 samples, 0.05%)</title><rect x="924.6" y="437" width="0.6" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="927.61" y="447.5" ></text>
</g>
<g >
<title>get_typlen (9,944,859,674 samples, 0.11%)</title><rect x="422.8" y="357" width="1.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="425.83" y="367.5" ></text>
</g>
<g >
<title>add_vars_to_targetlist (28,615,761,282 samples, 0.30%)</title><rect x="950.7" y="453" width="3.6" height="15.0" fill="rgb(221,76,18)" rx="2" ry="2" />
<text  x="953.68" y="463.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple (7,424,660,116 samples, 0.08%)</title><rect x="288.2" y="229" width="1.0" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="291.23" y="239.5" ></text>
</g>
<g >
<title>exprLocation (2,132,001,021 samples, 0.02%)</title><rect x="806.8" y="309" width="0.3" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="809.84" y="319.5" ></text>
</g>
<g >
<title>ReleaseCatCacheList (1,211,175,912 samples, 0.01%)</title><rect x="962.3" y="373" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="965.32" y="383.5" ></text>
</g>
<g >
<title>setup_simple_rel_arrays (3,863,134,391 samples, 0.04%)</title><rect x="1044.3" y="469" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1047.34" y="479.5" ></text>
</g>
<g >
<title>sched_clock_cpu (809,365,050 samples, 0.01%)</title><rect x="171.7" y="325" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="174.68" y="335.5" ></text>
</g>
<g >
<title>CreateCommandTag (2,188,843,457 samples, 0.02%)</title><rect x="209.9" y="581" width="0.3" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="212.94" y="591.5" ></text>
</g>
<g >
<title>initStringInfo (2,941,314,159 samples, 0.03%)</title><rect x="186.7" y="565" width="0.4" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="189.75" y="575.5" ></text>
</g>
<g >
<title>TupleDescAttr (890,283,347 samples, 0.01%)</title><rect x="827.0" y="277" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="830.04" y="287.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,264,334,352 samples, 0.01%)</title><rect x="363.4" y="309" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="366.38" y="319.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,573,153,694 samples, 0.02%)</title><rect x="111.3" y="741" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="114.30" y="751.5" ></text>
</g>
<g >
<title>psi_task_switch (3,770,055,510 samples, 0.04%)</title><rect x="478.6" y="277" width="0.5" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="481.61" y="287.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple (3,352,899,693 samples, 0.04%)</title><rect x="268.4" y="293" width="0.4" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="271.37" y="303.5" ></text>
</g>
<g >
<title>pthread_testcancel@@GLIBC_2.34 (864,019,100 samples, 0.01%)</title><rect x="484.4" y="437" width="0.1" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="487.37" y="447.5" ></text>
</g>
<g >
<title>PageGetMaxOffsetNumber (2,247,747,610 samples, 0.02%)</title><rect x="80.7" y="757" width="0.3" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="83.71" y="767.5" ></text>
</g>
<g >
<title>ExecutePlan (1,113,079,919,834 samples, 11.79%)</title><rect x="263.9" y="501" width="139.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="266.89" y="511.5" >ExecutePlan</text>
</g>
<g >
<title>AllocSetAlloc (1,222,348,979 samples, 0.01%)</title><rect x="1118.7" y="693" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1121.66" y="703.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,935,307,708 samples, 0.03%)</title><rect x="418.4" y="245" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="421.38" y="255.5" ></text>
</g>
<g >
<title>do_futex (940,728,870 samples, 0.01%)</title><rect x="363.4" y="229" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="366.40" y="239.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_object (1,306,731,137 samples, 0.01%)</title><rect x="504.8" y="421" width="0.1" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="507.78" y="431.5" ></text>
</g>
<g >
<title>AllocSetFree (1,630,182,992 samples, 0.02%)</title><rect x="989.3" y="357" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="992.27" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_monotonic_advance_u64 (2,244,454,767 samples, 0.02%)</title><rect x="490.9" y="469" width="0.3" height="15.0" fill="rgb(206,8,1)" rx="2" ry="2" />
<text  x="493.92" y="479.5" ></text>
</g>
<g >
<title>__sysvec_thermal (1,577,756,675 samples, 0.02%)</title><rect x="751.8" y="501" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="754.82" y="511.5" ></text>
</g>
<g >
<title>finalize_primnode (1,037,260,390 samples, 0.01%)</title><rect x="881.0" y="389" width="0.1" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="883.98" y="399.5" ></text>
</g>
<g >
<title>index_getnext_tid (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="341" width="1.7" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="125.45" y="351.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (905,048,235 samples, 0.01%)</title><rect x="834.8" y="325" width="0.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="837.84" y="335.5" ></text>
</g>
<g >
<title>sock_poll (6,516,209,004 samples, 0.07%)</title><rect x="156.9" y="357" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="159.87" y="367.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="677" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1136.99" y="687.5" ></text>
</g>
<g >
<title>ServerLoop (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="677" width="953.9" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="133.07" y="687.5" >ServerLoop</text>
</g>
<g >
<title>EndCommand (16,361,681,566 samples, 0.17%)</title><rect x="215.1" y="581" width="2.0" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="218.08" y="591.5" ></text>
</g>
<g >
<title>secure_write (1,321,209,385 samples, 0.01%)</title><rect x="1177.1" y="757" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1180.06" y="767.5" ></text>
</g>
<g >
<title>get_futex_key (7,421,100,911 samples, 0.08%)</title><rect x="482.5" y="309" width="0.9" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="485.49" y="319.5" ></text>
</g>
<g >
<title>heapam_index_fetch_begin (1,887,959,506 samples, 0.02%)</title><rect x="293.1" y="293" width="0.3" height="15.0" fill="rgb(243,174,41)" rx="2" ry="2" />
<text  x="296.11" y="303.5" ></text>
</g>
<g >
<title>wake_up_q (919,445,600 samples, 0.01%)</title><rect x="470.4" y="293" width="0.2" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="473.44" y="303.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,489,146,792 samples, 0.02%)</title><rect x="925.0" y="389" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="928.04" y="399.5" ></text>
</g>
<g >
<title>make_parsestate (1,180,620,922 samples, 0.01%)</title><rect x="1159.4" y="757" width="0.2" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="1162.43" y="767.5" ></text>
</g>
<g >
<title>AtEOXact_Snapshot (809,521,992 samples, 0.01%)</title><rect x="33.0" y="757" width="0.1" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="35.96" y="767.5" ></text>
</g>
<g >
<title>lappend (4,702,695,812 samples, 0.05%)</title><rect x="890.5" y="373" width="0.6" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="893.50" y="383.5" ></text>
</g>
<g >
<title>palloc (1,619,358,875 samples, 0.02%)</title><rect x="983.8" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="986.75" y="415.5" ></text>
</g>
<g >
<title>PopActiveSnapshot (3,370,774,812 samples, 0.04%)</title><rect x="232.8" y="549" width="0.4" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="235.82" y="559.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,534,859,334 samples, 0.05%)</title><rect x="473.9" y="469" width="0.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="476.91" y="479.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,078,647,951 samples, 0.01%)</title><rect x="502.3" y="421" width="0.1" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="505.25" y="431.5" ></text>
</g>
<g >
<title>set_pathtarget_cost_width (21,129,584,188 samples, 0.22%)</title><rect x="1045.5" y="485" width="2.7" height="15.0" fill="rgb(213,41,9)" rx="2" ry="2" />
<text  x="1048.54" y="495.5" ></text>
</g>
<g >
<title>vfs_write (59,329,540,342 samples, 0.63%)</title><rect x="494.0" y="405" width="7.4" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="496.98" y="415.5" ></text>
</g>
<g >
<title>ExecUpdateAct (363,503,329,882 samples, 3.85%)</title><rect x="342.9" y="421" width="45.5" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="345.91" y="431.5" >Exec..</text>
</g>
<g >
<title>BufferDescriptorGetBuffer (1,149,245,609 samples, 0.01%)</title><rect x="299.9" y="117" width="0.2" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="302.93" y="127.5" ></text>
</g>
<g >
<title>palloc0 (1,717,739,053 samples, 0.02%)</title><rect x="293.1" y="277" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="296.14" y="287.5" ></text>
</g>
<g >
<title>_bt_compare (1,338,761,308 samples, 0.01%)</title><rect x="123.9" y="261" width="0.2" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="126.91" y="271.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,502,079,788 samples, 0.02%)</title><rect x="1023.5" y="309" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1026.49" y="319.5" ></text>
</g>
<g >
<title>GlobalVisTestFor (1,232,036,239 samples, 0.01%)</title><rect x="309.8" y="245" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="312.82" y="255.5" ></text>
</g>
<g >
<title>hash_search (4,944,399,482 samples, 0.05%)</title><rect x="394.4" y="325" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="397.40" y="335.5" ></text>
</g>
<g >
<title>uint32_hash (1,392,307,894 samples, 0.01%)</title><rect x="1069.0" y="421" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1071.98" y="431.5" ></text>
</g>
<g >
<title>int4pl (1,279,163,086 samples, 0.01%)</title><rect x="1151.6" y="757" width="0.1" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="1154.58" y="767.5" ></text>
</g>
<g >
<title>do_futex (1,635,169,642 samples, 0.02%)</title><rect x="470.4" y="325" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="473.35" y="335.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="517" width="0.5" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1160.94" y="527.5" ></text>
</g>
<g >
<title>palloc0 (2,447,133,431 samples, 0.03%)</title><rect x="425.0" y="341" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="428.01" y="351.5" ></text>
</g>
<g >
<title>palloc0 (1,973,978,440 samples, 0.02%)</title><rect x="853.6" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="856.62" y="367.5" ></text>
</g>
<g >
<title>palloc (3,274,855,486 samples, 0.03%)</title><rect x="871.8" y="501" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="874.81" y="511.5" ></text>
</g>
<g >
<title>LockRelationOid (23,998,565,187 samples, 0.25%)</title><rect x="940.3" y="373" width="3.0" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="943.28" y="383.5" ></text>
</g>
<g >
<title>list_make1_impl (1,738,661,515 samples, 0.02%)</title><rect x="972.6" y="389" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="975.57" y="399.5" ></text>
</g>
<g >
<title>get_typcollation (8,971,219,343 samples, 0.10%)</title><rect x="807.3" y="357" width="1.1" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="810.25" y="367.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,492,639,098 samples, 0.05%)</title><rect x="864.3" y="453" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="867.31" y="463.5" ></text>
</g>
<g >
<title>AllocSetFree (899,540,635 samples, 0.01%)</title><rect x="954.7" y="405" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="957.66" y="415.5" ></text>
</g>
<g >
<title>PortalRun (1,817,074,711,274 samples, 19.25%)</title><rect x="227.5" y="581" width="227.2" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="230.50" y="591.5" >PortalRun</text>
</g>
<g >
<title>LWLockAttemptLock (3,720,522,411 samples, 0.04%)</title><rect x="96.3" y="165" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="99.27" y="175.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,060,137,340 samples, 0.01%)</title><rect x="296.0" y="261" width="0.1" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="299.01" y="271.5" ></text>
</g>
<g >
<title>tas (952,288,078 samples, 0.01%)</title><rect x="358.2" y="229" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="361.24" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,327,734,082 samples, 0.01%)</title><rect x="387.7" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="390.69" y="303.5" ></text>
</g>
<g >
<title>newNode (3,239,783,603 samples, 0.03%)</title><rect x="909.7" y="453" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="912.71" y="463.5" ></text>
</g>
<g >
<title>pg_parse_query (26,852,953,248 samples, 0.28%)</title><rect x="869.5" y="581" width="3.4" height="15.0" fill="rgb(209,18,4)" rx="2" ry="2" />
<text  x="872.54" y="591.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (1,753,890,525 samples, 0.02%)</title><rect x="399.4" y="309" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="402.39" y="319.5" ></text>
</g>
<g >
<title>do_syscall_64 (165,208,223,590 samples, 1.75%)</title><rect x="153.8" y="453" width="20.7" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="156.82" y="463.5" ></text>
</g>
<g >
<title>PinBuffer (4,698,696,939 samples, 0.05%)</title><rect x="299.9" y="133" width="0.6" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="302.87" y="143.5" ></text>
</g>
<g >
<title>BlockNumberIsValid (2,774,151,055 samples, 0.03%)</title><rect x="34.1" y="757" width="0.3" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="37.06" y="767.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,982,430,372 samples, 0.02%)</title><rect x="1011.3" y="245" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1014.28" y="255.5" ></text>
</g>
<g >
<title>_bt_check_compare (2,568,695,668 samples, 0.03%)</title><rect x="1157.9" y="261" width="0.4" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1160.94" y="271.5" ></text>
</g>
<g >
<title>finalize_primnode (14,459,074,695 samples, 0.15%)</title><rect x="880.0" y="469" width="1.8" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="882.99" y="479.5" ></text>
</g>
<g >
<title>list_delete_nth_cell (3,806,838,489 samples, 0.04%)</title><rect x="251.6" y="437" width="0.5" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="254.64" y="447.5" ></text>
</g>
<g >
<title>lappend (4,795,466,642 samples, 0.05%)</title><rect x="1153.3" y="757" width="0.6" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1156.32" y="767.5" ></text>
</g>
<g >
<title>TransactionIdSetPageStatus (26,940,974,785 samples, 0.29%)</title><rect x="469.7" y="469" width="3.4" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="472.70" y="479.5" ></text>
</g>
<g >
<title>cfree@GLIBC_2.2.5 (7,454,096,212 samples, 0.08%)</title><rect x="240.7" y="357" width="1.0" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="243.73" y="367.5" ></text>
</g>
<g >
<title>hash_bytes (2,602,840,619 samples, 0.03%)</title><rect x="811.2" y="389" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="814.20" y="399.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberTupleDesc (1,361,513,433 samples, 0.01%)</title><rect x="276.3" y="357" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="279.29" y="367.5" ></text>
</g>
<g >
<title>create_plan (1,078,082,070 samples, 0.01%)</title><rect x="1130.2" y="757" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="1133.19" y="767.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,771,464,562 samples, 0.02%)</title><rect x="107.7" y="741" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="110.74" y="751.5" ></text>
</g>
<g >
<title>obj_cgroup_charge_pages (1,437,923,572 samples, 0.02%)</title><rect x="195.4" y="293" width="0.2" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="198.40" y="303.5" ></text>
</g>
<g >
<title>_int_free (864,430,769 samples, 0.01%)</title><rect x="147.3" y="533" width="0.2" height="15.0" fill="rgb(247,196,46)" rx="2" ry="2" />
<text  x="150.34" y="543.5" ></text>
</g>
<g >
<title>_bt_getroot (14,094,754,105 samples, 0.15%)</title><rect x="334.9" y="245" width="1.8" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="337.92" y="255.5" ></text>
</g>
<g >
<title>XLogRecordAssemble (3,523,160,067 samples, 0.04%)</title><rect x="508.8" y="469" width="0.5" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="511.84" y="479.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="213" width="0.7" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="87.72" y="223.5" ></text>
</g>
<g >
<title>verify_compact_attribute (2,871,252,156 samples, 0.03%)</title><rect x="123.2" y="213" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="126.18" y="223.5" ></text>
</g>
<g >
<title>AtEOXact_RelationCache (1,307,247,510 samples, 0.01%)</title><rect x="32.5" y="757" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="35.46" y="767.5" ></text>
</g>
<g >
<title>replace_nestloop_params (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="421" width="0.1" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="88.40" y="431.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (826,749,802 samples, 0.01%)</title><rect x="283.1" y="309" width="0.1" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="286.09" y="319.5" ></text>
</g>
<g >
<title>ReleaseCatCache (864,137,029 samples, 0.01%)</title><rect x="1046.0" y="405" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1049.00" y="415.5" ></text>
</g>
<g >
<title>HeapTupleSetHeapOnly (947,384,474 samples, 0.01%)</title><rect x="362.0" y="357" width="0.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="365.00" y="367.5" ></text>
</g>
<g >
<title>newNode (2,038,406,325 samples, 0.02%)</title><rect x="888.3" y="309" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="891.33" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,177,366,135 samples, 0.01%)</title><rect x="977.0" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="979.98" y="383.5" ></text>
</g>
<g >
<title>ExecFetchSlotHeapTuple (2,278,114,108 samples, 0.02%)</title><rect x="345.0" y="373" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="347.97" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,421,308,255 samples, 0.02%)</title><rect x="1066.5" y="373" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1069.53" y="383.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (861,601,226 samples, 0.01%)</title><rect x="299.5" y="101" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="302.51" y="111.5" ></text>
</g>
<g >
<title>IsSystemClass (1,950,300,908 samples, 0.02%)</title><rect x="407.0" y="421" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="409.99" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (803,901,079 samples, 0.01%)</title><rect x="126.6" y="197" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="129.56" y="207.5" ></text>
</g>
<g >
<title>BufferIsPermanent (2,388,759,659 samples, 0.03%)</title><rect x="305.4" y="197" width="0.3" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="308.45" y="207.5" ></text>
</g>
<g >
<title>apply_scanjoin_target_to_paths (14,790,522,374 samples, 0.16%)</title><rect x="908.9" y="485" width="1.8" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="911.86" y="495.5" ></text>
</g>
<g >
<title>bms_difference (2,157,362,777 samples, 0.02%)</title><rect x="882.0" y="517" width="0.2" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="884.97" y="527.5" ></text>
</g>
<g >
<title>tts_buffer_heap_materialize (1,234,009,875 samples, 0.01%)</title><rect x="1188.4" y="757" width="0.1" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="1191.39" y="767.5" ></text>
</g>
<g >
<title>core_yy_load_buffer_state (998,839,354 samples, 0.01%)</title><rect x="870.9" y="501" width="0.1" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="873.86" y="511.5" ></text>
</g>
<g >
<title>int4hashfast (1,712,064,080 samples, 0.02%)</title><rect x="1151.4" y="757" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1154.37" y="767.5" ></text>
</g>
<g >
<title>XLogRegisterBufData (5,056,773,797 samples, 0.05%)</title><rect x="384.6" y="341" width="0.7" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="387.64" y="351.5" ></text>
</g>
<g >
<title>new_list (1,792,098,665 samples, 0.02%)</title><rect x="919.3" y="453" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="922.27" y="463.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (975,983,726 samples, 0.01%)</title><rect x="126.7" y="437" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="129.71" y="447.5" ></text>
</g>
<g >
<title>scanRTEForColumn (1,970,798,245 samples, 0.02%)</title><rect x="840.5" y="325" width="0.3" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="843.55" y="335.5" ></text>
</g>
<g >
<title>build_index_tlist (11,645,594,183 samples, 0.12%)</title><rect x="933.4" y="405" width="1.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="936.44" y="415.5" ></text>
</g>
<g >
<title>ExecScanExtended (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="453" width="6.6" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="97.67" y="463.5" ></text>
</g>
<g >
<title>initStringInfo (3,346,309,443 samples, 0.04%)</title><rect x="1079.1" y="597" width="0.4" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="1082.06" y="607.5" ></text>
</g>
<g >
<title>index_getattr (2,871,252,156 samples, 0.03%)</title><rect x="123.2" y="245" width="0.3" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="126.18" y="255.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (10,113,640,878 samples, 0.11%)</title><rect x="248.1" y="453" width="1.3" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="251.10" y="463.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,844,845,426 samples, 0.02%)</title><rect x="440.6" y="341" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="443.57" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,961,359,880 samples, 0.02%)</title><rect x="872.0" y="485" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="874.97" y="495.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (4,219,387,214 samples, 0.04%)</title><rect x="1057.3" y="421" width="0.5" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1060.32" y="431.5" ></text>
</g>
<g >
<title>palloc0 (4,230,552,764 samples, 0.04%)</title><rect x="431.8" y="373" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="434.83" y="383.5" ></text>
</g>
<g >
<title>bms_equal (1,196,429,127 samples, 0.01%)</title><rect x="983.1" y="453" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="986.09" y="463.5" ></text>
</g>
<g >
<title>make_rel_from_joinlist (6,398,252,039 samples, 0.07%)</title><rect x="983.3" y="453" width="0.8" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="986.29" y="463.5" ></text>
</g>
<g >
<title>new_list (1,921,991,063 samples, 0.02%)</title><rect x="815.4" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="818.38" y="447.5" ></text>
</g>
<g >
<title>palloc (1,265,541,821 samples, 0.01%)</title><rect x="893.2" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="896.16" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,121,404,409 samples, 0.02%)</title><rect x="1117.9" y="693" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1120.91" y="703.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="709" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1187.99" y="719.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_object (1,629,901,716 samples, 0.02%)</title><rect x="98.8" y="165" width="0.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="101.84" y="175.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (13,375,545,250 samples, 0.14%)</title><rect x="1053.2" y="469" width="1.6" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1056.18" y="479.5" ></text>
</g>
<g >
<title>ProcessClientReadInterrupt (5,502,474,906 samples, 0.06%)</title><rect x="151.0" y="517" width="0.7" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="153.99" y="527.5" ></text>
</g>
<g >
<title>UnregisterSnapshotNoOwner (2,391,989,560 samples, 0.03%)</title><rect x="452.9" y="485" width="0.3" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="455.94" y="495.5" ></text>
</g>
<g >
<title>ExecScanFetch (421,419,578,134 samples, 4.47%)</title><rect x="289.3" y="357" width="52.7" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="292.26" y="367.5" >ExecS..</text>
</g>
<g >
<title>contain_volatile_functions_walker (1,004,398,571 samples, 0.01%)</title><rect x="1128.9" y="757" width="0.1" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="1131.85" y="767.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVisibility (969,090,889 samples, 0.01%)</title><rect x="52.1" y="757" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="55.09" y="767.5" ></text>
</g>
<g >
<title>XLogNeedsFlush (859,651,908 samples, 0.01%)</title><rect x="109.8" y="757" width="0.1" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="112.80" y="767.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,099,355,822 samples, 0.01%)</title><rect x="1016.8" y="261" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1019.83" y="271.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,443,285,421 samples, 0.02%)</title><rect x="518.5" y="437" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="521.54" y="447.5" ></text>
</g>
<g >
<title>LockReassignCurrentOwner (4,808,950,144 samples, 0.05%)</title><rect x="225.3" y="533" width="0.6" height="15.0" fill="rgb(223,85,20)" rx="2" ry="2" />
<text  x="228.31" y="543.5" ></text>
</g>
<g >
<title>pq_getbytes (3,912,967,208 samples, 0.04%)</title><rect x="185.6" y="549" width="0.5" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="188.63" y="559.5" ></text>
</g>
<g >
<title>LWLockQueueSelf (1,234,464,932 samples, 0.01%)</title><rect x="350.5" y="293" width="0.2" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="353.51" y="303.5" ></text>
</g>
<g >
<title>enqueue_entity (2,653,738,561 samples, 0.03%)</title><rect x="203.9" y="277" width="0.3" height="15.0" fill="rgb(218,62,15)" rx="2" ry="2" />
<text  x="206.89" y="287.5" ></text>
</g>
<g >
<title>StartReadBuffer (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="229" width="1.5" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="86.22" y="239.5" ></text>
</g>
<g >
<title>grouping_planner (1,131,175,381,385 samples, 11.99%)</title><rect x="906.8" y="501" width="141.4" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="909.77" y="511.5" >grouping_planner</text>
</g>
<g >
<title>set_cheapest (3,428,468,120 samples, 0.04%)</title><rect x="910.3" y="469" width="0.4" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="913.28" y="479.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,289,183,885 samples, 0.01%)</title><rect x="1016.7" y="277" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1019.66" y="287.5" ></text>
</g>
<g >
<title>log_heap_update (2,165,021,221 samples, 0.02%)</title><rect x="1157.7" y="757" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1160.67" y="767.5" ></text>
</g>
<g >
<title>generic_write_check_limits (1,074,424,052 samples, 0.01%)</title><rect x="501.2" y="357" width="0.1" height="15.0" fill="rgb(206,9,2)" rx="2" ry="2" />
<text  x="504.20" y="367.5" ></text>
</g>
<g >
<title>StartTransaction (34,975,466,932 samples, 0.37%)</title><rect x="1074.4" y="549" width="4.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1077.37" y="559.5" ></text>
</g>
<g >
<title>pg_fdatasync (10,705,566,224 samples, 0.11%)</title><rect x="501.9" y="453" width="1.4" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="504.95" y="463.5" ></text>
</g>
<g >
<title>heap_compute_data_size (5,890,551,548 samples, 0.06%)</title><rect x="389.8" y="357" width="0.7" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="392.79" y="367.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,062,515,007 samples, 0.01%)</title><rect x="522.8" y="421" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="525.78" y="431.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,560,777,898 samples, 0.03%)</title><rect x="430.7" y="341" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="433.69" y="351.5" ></text>
</g>
<g >
<title>ExecAssignProjectionInfo (70,554,760,944 samples, 0.75%)</title><rect x="415.8" y="389" width="8.9" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="418.83" y="399.5" ></text>
</g>
<g >
<title>get_oprrest (4,778,657,341 samples, 0.05%)</title><rect x="1037.4" y="325" width="0.6" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="1040.42" y="335.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_in (5,497,035,850 samples, 0.06%)</title><rect x="161.3" y="325" width="0.7" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="164.34" y="335.5" ></text>
</g>
<g >
<title>CheckExprStillValid (8,090,072,918 samples, 0.09%)</title><rect x="266.7" y="357" width="1.0" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="269.70" y="367.5" ></text>
</g>
<g >
<title>RelationGetIndexExpressions (1,503,567,060 samples, 0.02%)</title><rect x="930.9" y="405" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="933.86" y="415.5" ></text>
</g>
<g >
<title>__schedule (105,962,157,836 samples, 1.12%)</title><rect x="158.8" y="357" width="13.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="161.76" y="367.5" ></text>
</g>
<g >
<title>CheckCmdReplicaIdentity (7,148,074,270 samples, 0.08%)</title><rect x="411.5" y="437" width="0.9" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="414.53" y="447.5" ></text>
</g>
<g >
<title>check_functions_in_node (1,496,880,573 samples, 0.02%)</title><rect x="959.9" y="357" width="0.2" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="962.87" y="367.5" ></text>
</g>
<g >
<title>ReleaseCatCache (989,313,226 samples, 0.01%)</title><rect x="834.8" y="341" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="837.84" y="351.5" ></text>
</g>
<g >
<title>newNode (2,802,152,626 samples, 0.03%)</title><rect x="855.0" y="341" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="857.96" y="351.5" ></text>
</g>
<g >
<title>ExecEndNode (66,911,448,439 samples, 0.71%)</title><rect x="238.5" y="453" width="8.3" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="241.48" y="463.5" ></text>
</g>
<g >
<title>uint32_hash (1,233,349,446 samples, 0.01%)</title><rect x="441.7" y="357" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="444.75" y="367.5" ></text>
</g>
<g >
<title>add_rtes_to_flat_rtable (20,919,260,118 samples, 0.22%)</title><rect x="895.5" y="501" width="2.6" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="898.52" y="511.5" ></text>
</g>
<g >
<title>ExecEvalStepOp (15,165,387,576 samples, 0.16%)</title><rect x="284.1" y="277" width="1.9" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="287.05" y="287.5" ></text>
</g>
<g >
<title>_copy_from_iter (1,569,809,700 samples, 0.02%)</title><rect x="193.6" y="389" width="0.2" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="196.64" y="399.5" ></text>
</g>
<g >
<title>is_publishable_relation (820,852,016 samples, 0.01%)</title><rect x="412.2" y="405" width="0.1" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="415.23" y="415.5" ></text>
</g>
<g >
<title>futex_wait_setup (11,912,207,490 samples, 0.13%)</title><rect x="481.9" y="325" width="1.5" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="484.92" y="335.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,099,158,758 samples, 0.04%)</title><rect x="231.9" y="501" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="234.88" y="511.5" ></text>
</g>
<g >
<title>_bt_checkkeys (4,418,702,744 samples, 0.05%)</title><rect x="122.6" y="261" width="0.6" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="125.63" y="271.5" ></text>
</g>
<g >
<title>IndexNext (24,158,740,785 samples, 0.26%)</title><rect x="279.2" y="357" width="3.0" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="282.19" y="367.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (5,289,593,129 samples, 0.06%)</title><rect x="83.5" y="197" width="0.7" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="86.50" y="207.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,092,375,782 samples, 0.01%)</title><rect x="885.6" y="357" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="888.58" y="367.5" ></text>
</g>
<g >
<title>pfree (3,357,301,308 samples, 0.04%)</title><rect x="252.1" y="469" width="0.4" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="255.12" y="479.5" ></text>
</g>
<g >
<title>makeColumnRef (15,181,812,941 samples, 0.16%)</title><rect x="1115.7" y="741" width="1.9" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="1118.71" y="751.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (5,177,556,579 samples, 0.05%)</title><rect x="279.3" y="325" width="0.7" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="282.33" y="335.5" ></text>
</g>
<g >
<title>fdget_pos (1,217,229,414 samples, 0.01%)</title><rect x="932.7" y="261" width="0.2" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="935.71" y="271.5" ></text>
</g>
<g >
<title>prepare_task_switch (10,180,103,317 samples, 0.11%)</title><rect x="162.3" y="341" width="1.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="165.27" y="351.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (76,451,845,678 samples, 0.81%)</title><rect x="474.9" y="469" width="9.6" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="477.94" y="479.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,060,015,742 samples, 0.03%)</title><rect x="390.8" y="325" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="393.80" y="335.5" ></text>
</g>
<g >
<title>create_plan_recurse (91,728,301,447 samples, 0.97%)</title><rect x="882.5" y="501" width="11.5" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="885.49" y="511.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,181,595,429 samples, 0.01%)</title><rect x="398.7" y="357" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="401.66" y="367.5" ></text>
</g>
<g >
<title>murmurhash32 (1,011,866,332 samples, 0.01%)</title><rect x="423.7" y="261" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="426.73" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,302,190,712 samples, 0.01%)</title><rect x="456.3" y="517" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="459.27" y="527.5" ></text>
</g>
<g >
<title>SocketBackend (297,446,276,367 samples, 3.15%)</title><rect x="149.1" y="581" width="37.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="152.09" y="591.5" >Soc..</text>
</g>
<g >
<title>palloc0 (1,513,760,155 samples, 0.02%)</title><rect x="855.7" y="293" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="858.67" y="303.5" ></text>
</g>
<g >
<title>__sys_recvfrom (63,650,122,283 samples, 0.67%)</title><rect x="176.4" y="437" width="7.9" height="15.0" fill="rgb(247,197,47)" rx="2" ry="2" />
<text  x="179.39" y="447.5" ></text>
</g>
<g >
<title>epoll_wait (1,744,030,300 samples, 0.02%)</title><rect x="174.8" y="485" width="0.2" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="177.77" y="495.5" ></text>
</g>
<g >
<title>PageGetItem (926,025,039 samples, 0.01%)</title><rect x="327.3" y="229" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="330.28" y="239.5" ></text>
</g>
<g >
<title>get_hash_value (3,106,909,423 samples, 0.03%)</title><rect x="518.8" y="437" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="521.76" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,045,476,124 samples, 0.01%)</title><rect x="958.0" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="961.01" y="415.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,430,590,009 samples, 0.02%)</title><rect x="49.3" y="757" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="52.34" y="767.5" ></text>
</g>
<g >
<title>ExecComputeSlotInfo (901,278,717 samples, 0.01%)</title><rect x="430.1" y="373" width="0.1" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="433.09" y="383.5" ></text>
</g>
<g >
<title>__virt_addr_valid (955,256,778 samples, 0.01%)</title><rect x="184.2" y="277" width="0.1" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="187.18" y="287.5" ></text>
</g>
<g >
<title>native_write_msr (1,056,607,369 samples, 0.01%)</title><rect x="477.5" y="213" width="0.2" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="480.53" y="223.5" ></text>
</g>
<g >
<title>new_list (2,062,245,138 samples, 0.02%)</title><rect x="994.2" y="325" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="997.20" y="335.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,363,002,046 samples, 0.01%)</title><rect x="365.4" y="325" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="368.42" y="335.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,099,612,506 samples, 0.03%)</title><rect x="916.7" y="341" width="0.3" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="919.66" y="351.5" ></text>
</g>
<g >
<title>get_hash_value (3,710,333,973 samples, 0.04%)</title><rect x="95.0" y="165" width="0.4" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="97.99" y="175.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (11,096,201,232 samples, 0.12%)</title><rect x="829.6" y="405" width="1.4" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="832.59" y="415.5" ></text>
</g>
<g >
<title>palloc (1,382,127,983 samples, 0.01%)</title><rect x="975.7" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="978.69" y="415.5" ></text>
</g>
<g >
<title>palloc0 (2,003,209,274 samples, 0.02%)</title><rect x="847.9" y="389" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="850.88" y="399.5" ></text>
</g>
<g >
<title>hash_search (7,168,821,777 samples, 0.08%)</title><rect x="828.1" y="357" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="831.14" y="367.5" ></text>
</g>
<g >
<title>ExecUpdate (432,576,653,946 samples, 4.58%)</title><rect x="342.4" y="437" width="54.1" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="345.45" y="447.5" >ExecU..</text>
</g>
<g >
<title>palloc0 (1,958,117,809 samples, 0.02%)</title><rect x="934.2" y="357" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="937.17" y="367.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (3,710,526,870 samples, 0.04%)</title><rect x="824.5" y="309" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="827.49" y="319.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,980,296,158 samples, 0.02%)</title><rect x="1143.8" y="741" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1146.78" y="751.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,304,332,163 samples, 0.01%)</title><rect x="833.9" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="836.89" y="399.5" ></text>
</g>
<g >
<title>ReleaseCatCacheListWithOwner (1,093,348,912 samples, 0.01%)</title><rect x="962.3" y="357" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="965.33" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u64 (1,375,055,131 samples, 0.01%)</title><rect x="491.0" y="453" width="0.2" height="15.0" fill="rgb(220,72,17)" rx="2" ry="2" />
<text  x="493.99" y="463.5" ></text>
</g>
<g >
<title>ExecModifyTable (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="485" width="1.7" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="125.45" y="495.5" ></text>
</g>
<g >
<title>xas_load (4,285,587,205 samples, 0.05%)</title><rect x="499.4" y="309" width="0.5" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="502.41" y="319.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (2,114,434,518 samples, 0.02%)</title><rect x="363.7" y="261" width="0.2" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="366.66" y="271.5" ></text>
</g>
<g >
<title>LWLockRelease (9,787,617,554 samples, 0.10%)</title><rect x="351.6" y="309" width="1.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="354.57" y="319.5" ></text>
</g>
<g >
<title>match_foreign_keys_to_quals (1,058,819,836 samples, 0.01%)</title><rect x="1043.0" y="469" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1045.99" y="479.5" ></text>
</g>
<g >
<title>ReserveXLogInsertLocation (4,333,978,973 samples, 0.05%)</title><rect x="507.5" y="453" width="0.5" height="15.0" fill="rgb(250,208,49)" rx="2" ry="2" />
<text  x="510.46" y="463.5" ></text>
</g>
<g >
<title>MemoryContextCheck (4,043,390,878 samples, 0.04%)</title><rect x="77.2" y="757" width="0.6" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="80.25" y="767.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,557,198,707 samples, 0.02%)</title><rect x="391.0" y="309" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="393.98" y="319.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (2,187,083,772 samples, 0.02%)</title><rect x="504.4" y="437" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="507.37" y="447.5" ></text>
</g>
<g >
<title>makeIndexInfo (8,007,070,251 samples, 0.08%)</title><rect x="392.5" y="373" width="1.0" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="395.54" y="383.5" ></text>
</g>
<g >
<title>bms_copy (2,011,697,217 samples, 0.02%)</title><rect x="957.9" y="437" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="960.91" y="447.5" ></text>
</g>
<g >
<title>palloc (8,093,163,978 samples, 0.09%)</title><rect x="946.3" y="405" width="1.0" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="949.26" y="415.5" ></text>
</g>
<g >
<title>index_open (15,172,699,445 samples, 0.16%)</title><rect x="440.1" y="421" width="1.9" height="15.0" fill="rgb(233,132,31)" rx="2" ry="2" />
<text  x="443.06" y="431.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,754,891,449 samples, 0.02%)</title><rect x="362.9" y="341" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="365.93" y="351.5" ></text>
</g>
<g >
<title>ExecProcNode (510,117,137,222 samples, 5.41%)</title><rect x="278.5" y="437" width="63.8" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="281.54" y="447.5" >ExecPro..</text>
</g>
<g >
<title>IncrTupleDescRefCount (1,944,917,604 samples, 0.02%)</title><rect x="438.8" y="373" width="0.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="441.82" y="383.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (1,099,617,154 samples, 0.01%)</title><rect x="290.1" y="293" width="0.2" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="293.12" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (979,412,122 samples, 0.01%)</title><rect x="899.6" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="902.56" y="431.5" ></text>
</g>
<g >
<title>index_fetch_heap (136,345,995,961 samples, 1.44%)</title><rect x="294.1" y="309" width="17.0" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="297.06" y="319.5" ></text>
</g>
<g >
<title>XLogInsertRecord (945,667,145 samples, 0.01%)</title><rect x="109.7" y="757" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="112.68" y="767.5" ></text>
</g>
<g >
<title>hash_search (6,079,245,216 samples, 0.06%)</title><rect x="444.3" y="341" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="447.32" y="351.5" ></text>
</g>
<g >
<title>set_sentinel (1,892,721,499 samples, 0.02%)</title><rect x="12.6" y="741" width="0.2" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="15.57" y="751.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop_walker (3,869,836,487 samples, 0.04%)</title><rect x="1052.6" y="405" width="0.5" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1055.62" y="415.5" ></text>
</g>
<g >
<title>colNameToVar (11,449,870,506 samples, 0.12%)</title><rect x="839.4" y="357" width="1.4" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="842.36" y="367.5" ></text>
</g>
<g >
<title>fetch_att (1,229,558,913 samples, 0.01%)</title><rect x="321.3" y="213" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="324.35" y="223.5" ></text>
</g>
<g >
<title>new_list (1,972,162,088 samples, 0.02%)</title><rect x="869.2" y="517" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="872.23" y="527.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,229,340,899 samples, 0.03%)</title><rect x="881.4" y="373" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="884.38" y="383.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,286,522,611 samples, 0.01%)</title><rect x="963.4" y="341" width="0.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="966.43" y="351.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (1,401,664,762 samples, 0.01%)</title><rect x="865.4" y="453" width="0.2" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="868.39" y="463.5" ></text>
</g>
<g >
<title>__futex_wait (58,999,884,472 samples, 0.63%)</title><rect x="476.0" y="341" width="7.4" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="479.04" y="351.5" ></text>
</g>
<g >
<title>PostgresMain (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="629" width="2.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="127.59" y="639.5" ></text>
</g>
<g >
<title>pq_sendint8 (1,219,918,298 samples, 0.01%)</title><rect x="187.9" y="565" width="0.2" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="190.95" y="575.5" ></text>
</g>
<g >
<title>ExecInitModifyTable (337,028,191,893 samples, 3.57%)</title><rect x="409.3" y="469" width="42.1" height="15.0" fill="rgb(209,19,4)" rx="2" ry="2" />
<text  x="412.27" y="479.5" >Exe..</text>
</g>
<g >
<title>makeSimpleA_Expr (15,536,950,957 samples, 0.16%)</title><rect x="1119.1" y="741" width="2.0" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="1122.11" y="751.5" ></text>
</g>
<g >
<title>transformExprRecurse (103,544,088,885 samples, 1.10%)</title><rect x="843.9" y="453" width="13.0" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="846.90" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,043,494,886 samples, 0.01%)</title><rect x="1078.2" y="485" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="1081.24" y="495.5" ></text>
</g>
<g >
<title>lcons (2,890,149,063 samples, 0.03%)</title><rect x="944.1" y="405" width="0.4" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="947.12" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (848,227,045 samples, 0.01%)</title><rect x="1057.7" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1060.69" y="367.5" ></text>
</g>
<g >
<title>get_typavgwidth (6,893,502,912 samples, 0.07%)</title><rect x="1042.1" y="373" width="0.8" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="1045.07" y="383.5" ></text>
</g>
<g >
<title>__cgroup_account_cputime (1,781,141,046 samples, 0.02%)</title><rect x="169.0" y="245" width="0.3" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="172.05" y="255.5" ></text>
</g>
<g >
<title>gup_fast (6,036,401,898 samples, 0.06%)</title><rect x="482.7" y="277" width="0.7" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="485.66" y="287.5" ></text>
</g>
<g >
<title>bms_add_members (4,725,152,473 samples, 0.05%)</title><rect x="951.3" y="437" width="0.6" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="954.31" y="447.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (980,617,970 samples, 0.01%)</title><rect x="302.0" y="229" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="304.98" y="239.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,162,197,000 samples, 0.01%)</title><rect x="291.1" y="229" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="294.06" y="239.5" ></text>
</g>
<g >
<title>update_process_times (3,755,269,844 samples, 0.04%)</title><rect x="751.2" y="437" width="0.4" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="754.18" y="447.5" ></text>
</g>
<g >
<title>_bt_first (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="357" width="6.6" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="97.67" y="367.5" ></text>
</g>
<g >
<title>hash_search (7,205,185,158 samples, 0.08%)</title><rect x="358.6" y="277" width="0.9" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="361.59" y="287.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="341" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="127.24" y="351.5" ></text>
</g>
<g >
<title>AllocSetFree (1,006,899,954 samples, 0.01%)</title><rect x="1166.1" y="741" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="1169.11" y="751.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (2,297,024,922 samples, 0.02%)</title><rect x="382.1" y="213" width="0.3" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="385.14" y="223.5" ></text>
</g>
<g >
<title>do_futex (966,647,202 samples, 0.01%)</title><rect x="381.8" y="181" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="384.78" y="191.5" ></text>
</g>
<g >
<title>check_heap_object (3,653,122,710 samples, 0.04%)</title><rect x="183.9" y="293" width="0.4" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="186.86" y="303.5" ></text>
</g>
<g >
<title>SPI_inside_nonatomic_context (886,798,561 samples, 0.01%)</title><rect x="92.6" y="757" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="95.56" y="767.5" ></text>
</g>
<g >
<title>RelationClose (1,147,599,230 samples, 0.01%)</title><rect x="1067.1" y="453" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="1070.11" y="463.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,947,735,967 samples, 0.02%)</title><rect x="362.3" y="309" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="365.33" y="319.5" ></text>
</g>
<g >
<title>__hrtimer_run_queues (2,650,857,371 samples, 0.03%)</title><rect x="791.3" y="453" width="0.3" height="15.0" fill="rgb(237,150,35)" rx="2" ry="2" />
<text  x="794.30" y="463.5" ></text>
</g>
<g >
<title>perf_ctx_disable (7,111,178,126 samples, 0.08%)</title><rect x="162.7" y="293" width="0.8" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="165.65" y="303.5" ></text>
</g>
<g >
<title>FastPathGrantRelationLock (6,330,614,017 samples, 0.07%)</title><rect x="818.8" y="373" width="0.8" height="15.0" fill="rgb(245,188,44)" rx="2" ry="2" />
<text  x="821.80" y="383.5" ></text>
</g>
<g >
<title>PageXLogRecPtrGet (1,025,518,256 samples, 0.01%)</title><rect x="81.3" y="757" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="84.30" y="767.5" ></text>
</g>
<g >
<title>LockHeldByMe (12,790,686,889 samples, 0.14%)</title><rect x="444.1" y="373" width="1.6" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="447.13" y="383.5" ></text>
</g>
<g >
<title>tag_hash (2,337,860,981 samples, 0.02%)</title><rect x="942.0" y="309" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="945.05" y="319.5" ></text>
</g>
<g >
<title>makeTargetEntry (3,459,570,070 samples, 0.04%)</title><rect x="833.6" y="437" width="0.5" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="836.62" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (875,450,931 samples, 0.01%)</title><rect x="994.3" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="997.33" y="303.5" ></text>
</g>
<g >
<title>match_opclause_to_indexcol (20,008,164,790 samples, 0.21%)</title><rect x="1020.0" y="325" width="2.5" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1023.01" y="335.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberBuffer (944,844,737 samples, 0.01%)</title><rect x="295.8" y="213" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="298.80" y="223.5" ></text>
</g>
<g >
<title>bms_add_member (2,616,544,328 samples, 0.03%)</title><rect x="980.5" y="453" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="983.52" y="463.5" ></text>
</g>
<g >
<title>IsBinaryTidClause (1,525,392,229 samples, 0.02%)</title><rect x="1026.0" y="357" width="0.2" height="15.0" fill="rgb(214,41,9)" rx="2" ry="2" />
<text  x="1029.04" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (995,978,367 samples, 0.01%)</title><rect x="452.6" y="485" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="455.57" y="495.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,038,398,311 samples, 0.01%)</title><rect x="398.7" y="293" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="401.66" y="303.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (16,372,556,319 samples, 0.17%)</title><rect x="954.9" y="437" width="2.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="957.93" y="447.5" ></text>
</g>
<g >
<title>palloc0 (2,325,738,820 samples, 0.02%)</title><rect x="934.6" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="937.60" y="367.5" ></text>
</g>
<g >
<title>MemoryContextTraverseNext (2,280,280,273 samples, 0.02%)</title><rect x="78.4" y="757" width="0.3" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="81.44" y="767.5" ></text>
</g>
<g >
<title>pick_next_task_fair (9,528,939,711 samples, 0.10%)</title><rect x="159.3" y="325" width="1.2" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="162.33" y="335.5" ></text>
</g>
<g >
<title>AllocSetCheck (27,747,280,927 samples, 0.29%)</title><rect x="257.7" y="405" width="3.5" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="260.72" y="415.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,387,123,297 samples, 0.01%)</title><rect x="1173.0" y="709" width="0.2" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1176.04" y="719.5" ></text>
</g>
<g >
<title>pfree (1,640,597,507 samples, 0.02%)</title><rect x="246.6" y="373" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="249.59" y="383.5" ></text>
</g>
<g >
<title>shmem_file_llseek (2,106,618,633 samples, 0.02%)</title><rect x="937.6" y="165" width="0.3" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="940.64" y="175.5" ></text>
</g>
<g >
<title>namestrcpy (858,840,059 samples, 0.01%)</title><rect x="1161.4" y="757" width="0.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="1164.41" y="767.5" ></text>
</g>
<g >
<title>lappend (2,690,493,502 samples, 0.03%)</title><rect x="983.6" y="437" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="986.63" y="447.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (7,455,280,719 samples, 0.08%)</title><rect x="213.4" y="549" width="0.9" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="216.36" y="559.5" ></text>
</g>
<g >
<title>TransactionIdDidCommit (7,147,513,795 samples, 0.08%)</title><rect x="1147.0" y="693" width="0.9" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="1149.97" y="703.5" ></text>
</g>
<g >
<title>PGSemaphoreLock (1,410,329,829 samples, 0.01%)</title><rect x="381.8" y="277" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="384.76" y="287.5" ></text>
</g>
<g >
<title>fireASTriggers (1,106,571,322 samples, 0.01%)</title><rect x="396.7" y="437" width="0.1" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="399.67" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,683,873,573 samples, 0.02%)</title><rect x="1119.6" y="677" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1122.63" y="687.5" ></text>
</g>
<g >
<title>TupleDescInitEntryCollation (1,113,601,426 samples, 0.01%)</title><rect x="437.5" y="373" width="0.1" height="15.0" fill="rgb(206,8,1)" rx="2" ry="2" />
<text  x="440.48" y="383.5" ></text>
</g>
<g >
<title>string_compare (882,154,198 samples, 0.01%)</title><rect x="226.7" y="533" width="0.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="229.74" y="543.5" ></text>
</g>
<g >
<title>_int_malloc (6,577,674,580 samples, 0.07%)</title><rect x="1051.1" y="421" width="0.8" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="1054.07" y="431.5" ></text>
</g>
<g >
<title>BackendMain (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="645" width="2.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="127.59" y="655.5" ></text>
</g>
<g >
<title>CopyXLogRecordToWAL (3,295,450,357 samples, 0.03%)</title><rect x="507.0" y="453" width="0.4" height="15.0" fill="rgb(213,36,8)" rx="2" ry="2" />
<text  x="510.03" y="463.5" ></text>
</g>
<g >
<title>futex_wait (4,391,689,701 samples, 0.05%)</title><rect x="350.8" y="181" width="0.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="353.82" y="191.5" ></text>
</g>
<g >
<title>tts_buffer_heap_store_tuple (6,415,446,137 samples, 0.07%)</title><rect x="295.1" y="245" width="0.8" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="298.13" y="255.5" ></text>
</g>
<g >
<title>relation_close (2,645,714,073 samples, 0.03%)</title><rect x="865.3" y="501" width="0.3" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="868.25" y="511.5" ></text>
</g>
<g >
<title>fix_expr_common (1,362,768,323 samples, 0.01%)</title><rect x="903.0" y="373" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="905.97" y="383.5" ></text>
</g>
<g >
<title>tick_nohz_handler (3,972,867,047 samples, 0.04%)</title><rect x="751.2" y="453" width="0.5" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="754.16" y="463.5" ></text>
</g>
<g >
<title>LockHeldByMe (10,390,989,548 samples, 0.11%)</title><rect x="1067.4" y="437" width="1.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1070.37" y="447.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,254,494,249 samples, 0.01%)</title><rect x="412.1" y="405" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="415.07" y="415.5" ></text>
</g>
<g >
<title>SearchSysCache3 (7,063,094,595 samples, 0.07%)</title><rect x="1041.2" y="357" width="0.9" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1044.19" y="367.5" ></text>
</g>
<g >
<title>prune_freeze_plan (6,705,999,285 samples, 0.07%)</title><rect x="1148.2" y="725" width="0.8" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="1151.19" y="735.5" ></text>
</g>
<g >
<title>AllocSetAlloc (866,881,167 samples, 0.01%)</title><rect x="972.7" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="975.67" y="351.5" ></text>
</g>
<g >
<title>palloc0 (1,669,073,304 samples, 0.02%)</title><rect x="927.6" y="405" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="930.64" y="415.5" ></text>
</g>
<g >
<title>ExecCloseIndices (16,422,274,179 samples, 0.17%)</title><rect x="235.9" y="469" width="2.0" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="238.89" y="479.5" ></text>
</g>
<g >
<title>new_list (2,614,249,085 samples, 0.03%)</title><rect x="964.1" y="357" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="967.07" y="367.5" ></text>
</g>
<g >
<title>AllocSetDelete (6,318,373,198 samples, 0.07%)</title><rect x="250.3" y="437" width="0.8" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="253.28" y="447.5" ></text>
</g>
<g >
<title>ServerLoop (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="709" width="2.1" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="125.45" y="719.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,085,701,648 samples, 0.01%)</title><rect x="978.5" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="981.49" y="431.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (877,669,091 samples, 0.01%)</title><rect x="1068.3" y="405" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1071.28" y="415.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,455,385,594 samples, 0.02%)</title><rect x="126.9" y="757" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="129.91" y="767.5" ></text>
</g>
<g >
<title>ReadBuffer_common (5,413,996,234 samples, 0.06%)</title><rect x="84.7" y="245" width="0.7" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="87.72" y="255.5" ></text>
</g>
<g >
<title>_bt_lockbuf (3,610,517,911 samples, 0.04%)</title><rect x="339.3" y="229" width="0.4" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="342.28" y="239.5" ></text>
</g>
<g >
<title>kmem_cache_free (8,120,220,080 samples, 0.09%)</title><rect x="181.4" y="373" width="1.0" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="184.40" y="383.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (1,229,399,354 samples, 0.01%)</title><rect x="89.3" y="757" width="0.2" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="92.30" y="767.5" ></text>
</g>
<g >
<title>PageGetHeapFreeSpace (4,218,283,942 samples, 0.04%)</title><rect x="310.5" y="245" width="0.5" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="313.52" y="255.5" ></text>
</g>
<g >
<title>wipe_mem (35,634,002,487 samples, 0.38%)</title><rect x="241.9" y="357" width="4.4" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="244.87" y="367.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,546,004,627 samples, 0.02%)</title><rect x="1013.6" y="229" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1016.58" y="239.5" ></text>
</g>
<g >
<title>pull_varnos (1,414,859,144 samples, 0.01%)</title><rect x="1036.2" y="229" width="0.2" height="15.0" fill="rgb(229,112,26)" rx="2" ry="2" />
<text  x="1039.19" y="239.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,464,790,840 samples, 0.04%)</title><rect x="1037.6" y="309" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1040.58" y="319.5" ></text>
</g>
<g >
<title>palloc0 (4,029,736,087 samples, 0.04%)</title><rect x="944.8" y="389" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="947.83" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,414,076,305 samples, 0.01%)</title><rect x="928.1" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="931.06" y="383.5" ></text>
</g>
<g >
<title>SimpleLruReadPage_ReadOnly (5,266,597,177 samples, 0.06%)</title><rect x="1147.2" y="645" width="0.6" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="1150.19" y="655.5" ></text>
</g>
<g >
<title>canonicalize_qual (1,413,866,068 samples, 0.01%)</title><rect x="1055.3" y="469" width="0.1" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="1058.27" y="479.5" ></text>
</g>
<g >
<title>socket_putmessage (1,915,779,420 samples, 0.02%)</title><rect x="1183.7" y="757" width="0.2" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="1186.65" y="767.5" ></text>
</g>
<g >
<title>planner (1,584,787,541,025 samples, 16.79%)</title><rect x="873.9" y="549" width="198.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="876.90" y="559.5" >planner</text>
</g>
<g >
<title>CreateTemplateTupleDesc (2,137,958,966 samples, 0.02%)</title><rect x="434.2" y="373" width="0.2" height="15.0" fill="rgb(218,61,14)" rx="2" ry="2" />
<text  x="437.16" y="383.5" ></text>
</g>
<g >
<title>AtEOXact_PgStat (8,087,658,608 samples, 0.09%)</title><rect x="462.4" y="517" width="1.1" height="15.0" fill="rgb(246,193,46)" rx="2" ry="2" />
<text  x="465.44" y="527.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (5,985,553,827 samples, 0.06%)</title><rect x="502.4" y="421" width="0.7" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="505.39" y="431.5" ></text>
</g>
<g >
<title>select_idle_sibling (21,454,358,423 samples, 0.23%)</title><rect x="200.1" y="293" width="2.7" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="203.13" y="303.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_in (1,785,025,036 samples, 0.02%)</title><rect x="477.5" y="261" width="0.2" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="480.48" y="271.5" ></text>
</g>
<g >
<title>fdget (4,031,368,803 samples, 0.04%)</title><rect x="190.5" y="421" width="0.5" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="193.46" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,534,198,266 samples, 0.02%)</title><rect x="919.7" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="922.73" y="447.5" ></text>
</g>
<g >
<title>ReadBuffer (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="277" width="1.5" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="86.22" y="287.5" ></text>
</g>
<g >
<title>ExecInitNode (864,044,589 samples, 0.01%)</title><rect x="43.7" y="757" width="0.1" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="46.73" y="767.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (3,173,615,450 samples, 0.03%)</title><rect x="750.3" y="501" width="0.4" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="753.32" y="511.5" ></text>
</g>
<g >
<title>set_ps_display (6,364,146,273 samples, 0.07%)</title><rect x="1083.1" y="597" width="0.8" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="1086.14" y="607.5" ></text>
</g>
<g >
<title>ExecComputeSlotInfo (1,989,301,285 samples, 0.02%)</title><rect x="416.8" y="325" width="0.2" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="419.80" y="335.5" ></text>
</g>
<g >
<title>CleanUpLock (11,459,405,484 samples, 0.12%)</title><rect x="514.0" y="453" width="1.4" height="15.0" fill="rgb(232,124,29)" rx="2" ry="2" />
<text  x="517.00" y="463.5" ></text>
</g>
<g >
<title>_bt_checkkeys (3,382,337,140 samples, 0.04%)</title><rect x="327.7" y="229" width="0.4" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="330.67" y="239.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (1,052,371,179 samples, 0.01%)</title><rect x="206.2" y="453" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="209.23" y="463.5" ></text>
</g>
<g >
<title>raw_spin_rq_lock_nested (9,911,313,283 samples, 0.11%)</title><rect x="486.6" y="277" width="1.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="489.57" y="287.5" ></text>
</g>
<g >
<title>fastgetattr (46,782,264,731 samples, 0.50%)</title><rect x="1002.4" y="229" width="5.9" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="1005.45" y="239.5" ></text>
</g>
<g >
<title>bms_add_members (1,549,789,576 samples, 0.02%)</title><rect x="995.3" y="357" width="0.2" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="998.34" y="367.5" ></text>
</g>
<g >
<title>palloc0 (26,612,332,773 samples, 0.28%)</title><rect x="1048.7" y="485" width="3.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1051.69" y="495.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,040,552,818 samples, 0.01%)</title><rect x="393.9" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="396.87" y="335.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (7,178,497,424 samples, 0.08%)</title><rect x="435.7" y="325" width="0.9" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="438.71" y="335.5" ></text>
</g>
<g >
<title>ExecScan (477,391,824,181 samples, 5.06%)</title><rect x="282.5" y="389" width="59.7" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="285.48" y="399.5" >ExecScan</text>
</g>
<g >
<title>file_update_time (4,030,006,442 samples, 0.04%)</title><rect x="495.6" y="373" width="0.5" height="15.0" fill="rgb(210,27,6)" rx="2" ry="2" />
<text  x="498.56" y="383.5" ></text>
</g>
<g >
<title>tag_hash (7,830,204,602 samples, 0.08%)</title><rect x="520.2" y="421" width="1.0" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="523.24" y="431.5" ></text>
</g>
<g >
<title>BufTableHashCode (2,805,703,809 samples, 0.03%)</title><rect x="298.0" y="133" width="0.4" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="301.05" y="143.5" ></text>
</g>
<g >
<title>list_insert_nth (2,154,113,756 samples, 0.02%)</title><rect x="908.6" y="469" width="0.3" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="911.59" y="479.5" ></text>
</g>
<g >
<title>pg_ceil_log2_64 (890,068,881 samples, 0.01%)</title><rect x="1064.4" y="405" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1067.44" y="415.5" ></text>
</g>
<g >
<title>bms_make_singleton (3,559,881,921 samples, 0.04%)</title><rect x="876.9" y="469" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="879.86" y="479.5" ></text>
</g>
<g >
<title>AtEOXact_Parallel (1,348,555,151 samples, 0.01%)</title><rect x="32.1" y="757" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="35.10" y="767.5" ></text>
</g>
<g >
<title>_bt_first (231,466,074,617 samples, 2.45%)</title><rect x="311.7" y="277" width="29.0" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="314.75" y="287.5" >_b..</text>
</g>
<g >
<title>GlobalVisUpdate (1,810,538,987 samples, 0.02%)</title><rect x="310.3" y="213" width="0.2" height="15.0" fill="rgb(209,18,4)" rx="2" ry="2" />
<text  x="313.28" y="223.5" ></text>
</g>
<g >
<title>heap_freetuple (1,637,762,519 samples, 0.02%)</title><rect x="249.2" y="437" width="0.2" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="252.16" y="447.5" ></text>
</g>
<g >
<title>relation_open (26,333,369,033 samples, 0.28%)</title><rect x="827.7" y="421" width="3.3" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="830.74" y="431.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (2,274,198,684 samples, 0.02%)</title><rect x="750.4" y="469" width="0.3" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="753.42" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,662,596,813 samples, 0.03%)</title><rect x="290.9" y="245" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="293.93" y="255.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (44,718,528,546 samples, 0.47%)</title><rect x="59.0" y="757" width="5.6" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="62.02" y="767.5" ></text>
</g>
<g >
<title>DynaHashAlloc (1,125,296,997 samples, 0.01%)</title><rect x="40.9" y="757" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="43.87" y="767.5" ></text>
</g>
<g >
<title>new_list (1,785,405,187 samples, 0.02%)</title><rect x="448.0" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="451.05" y="431.5" ></text>
</g>
<g >
<title>SetupLockInTable (24,041,077,292 samples, 0.25%)</title><rect x="355.6" y="277" width="3.0" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="358.55" y="287.5" ></text>
</g>
<g >
<title>_raw_spin_lock_irq (847,867,855 samples, 0.01%)</title><rect x="156.3" y="357" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="159.31" y="367.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (3,571,709,823 samples, 0.04%)</title><rect x="1061.0" y="453" width="0.4" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="1063.96" y="463.5" ></text>
</g>
<g >
<title>pfree (1,876,258,578 samples, 0.02%)</title><rect x="222.0" y="549" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="224.95" y="559.5" ></text>
</g>
<g >
<title>pfree (1,651,427,061 samples, 0.02%)</title><rect x="453.3" y="517" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="456.27" y="527.5" ></text>
</g>
<g >
<title>exprType (2,344,835,468 samples, 0.02%)</title><rect x="846.1" y="405" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="849.05" y="415.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,716,915,043 samples, 0.04%)</title><rect x="211.8" y="533" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="214.77" y="543.5" ></text>
</g>
<g >
<title>ExecInitUpdateProjection (942,172,141 samples, 0.01%)</title><rect x="44.2" y="757" width="0.2" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="47.23" y="767.5" ></text>
</g>
<g >
<title>new_list (1,731,892,941 samples, 0.02%)</title><rect x="439.4" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="442.41" y="383.5" ></text>
</g>
<g >
<title>exprCollation (2,846,291,607 samples, 0.03%)</title><rect x="437.6" y="373" width="0.4" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="440.62" y="383.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,057,323,995 samples, 0.01%)</title><rect x="381.8" y="197" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="384.77" y="207.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,395,567,090 samples, 0.01%)</title><rect x="1061.7" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1064.70" y="431.5" ></text>
</g>
<g >
<title>hash_bytes (4,675,649,123 samples, 0.05%)</title><rect x="828.4" y="325" width="0.6" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="831.45" y="335.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (865,104,027 samples, 0.01%)</title><rect x="234.6" y="485" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="237.60" y="495.5" ></text>
</g>
<g >
<title>futex_wake (1,792,791,505 samples, 0.02%)</title><rect x="382.2" y="133" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="385.19" y="143.5" ></text>
</g>
<g >
<title>scanNSItemForColumn (17,363,673,564 samples, 0.18%)</title><rect x="854.5" y="373" width="2.2" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="857.51" y="383.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (4,418,702,744 samples, 0.05%)</title><rect x="122.6" y="293" width="0.6" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="125.63" y="303.5" ></text>
</g>
<g >
<title>ReleaseBuffer (3,822,853,876 samples, 0.04%)</title><rect x="324.8" y="213" width="0.4" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="327.76" y="223.5" ></text>
</g>
<g >
<title>do_futex (1,418,667,084 samples, 0.02%)</title><rect x="354.7" y="149" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="357.65" y="159.5" ></text>
</g>
<g >
<title>ExecIndexScan (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="421" width="1.9" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="127.59" y="431.5" ></text>
</g>
<g >
<title>PushActiveSnapshotWithLevel (8,596,297,276 samples, 0.09%)</title><rect x="455.9" y="565" width="1.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="458.90" y="575.5" ></text>
</g>
<g >
<title>list_nth (881,367,923 samples, 0.01%)</title><rect x="443.9" y="405" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="446.87" y="415.5" ></text>
</g>
<g >
<title>malloc (8,846,349,933 samples, 0.09%)</title><rect x="1050.8" y="437" width="1.1" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="1053.79" y="447.5" ></text>
</g>
<g >
<title>transformExprRecurse (17,078,167,893 samples, 0.18%)</title><rect x="838.7" y="389" width="2.1" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="841.69" y="399.5" ></text>
</g>
<g >
<title>PostgresMain (7,628,621,015,091 samples, 80.84%)</title><rect x="130.1" y="613" width="953.8" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="133.08" y="623.5" >PostgresMain</text>
</g>
<g >
<title>_bt_getbuf (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="309" width="4.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="97.67" y="319.5" ></text>
</g>
<g >
<title>ExecClearTuple (2,263,285,559 samples, 0.02%)</title><rect x="266.3" y="405" width="0.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="269.30" y="415.5" ></text>
</g>
<g >
<title>pgstat_report_query_id (1,554,113,178 samples, 0.02%)</title><rect x="800.1" y="549" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="803.13" y="559.5" ></text>
</g>
<g >
<title>palloc (1,525,643,583 samples, 0.02%)</title><rect x="976.9" y="389" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="979.95" y="399.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (1,124,384,969 samples, 0.01%)</title><rect x="466.7" y="485" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="469.66" y="495.5" ></text>
</g>
<g >
<title>pull_up_subqueries_recurse (5,132,368,255 samples, 0.05%)</title><rect x="1070.8" y="485" width="0.6" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="1073.77" y="495.5" ></text>
</g>
<g >
<title>BufferGetBlock (1,294,624,926 samples, 0.01%)</title><rect x="370.6" y="325" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="373.60" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (988,251,051 samples, 0.01%)</title><rect x="439.5" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="442.49" y="351.5" ></text>
</g>
<g >
<title>_bt_steppage (8,063,607,695 samples, 0.09%)</title><rect x="280.7" y="277" width="1.0" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="283.72" y="287.5" ></text>
</g>
<g >
<title>check_list_invariants (1,224,877,805 samples, 0.01%)</title><rect x="1153.6" y="741" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1156.56" y="751.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,015,071,787 samples, 0.01%)</title><rect x="834.5" y="373" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="837.46" y="383.5" ></text>
</g>
<g >
<title>tag_hash (3,477,888,837 samples, 0.04%)</title><rect x="359.1" y="261" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="362.06" y="271.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (5,638,149,810 samples, 0.06%)</title><rect x="99.5" y="165" width="0.7" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="102.46" y="175.5" ></text>
</g>
<g >
<title>_bt_compare (2,871,252,156 samples, 0.03%)</title><rect x="123.2" y="261" width="0.3" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="126.18" y="271.5" ></text>
</g>
<g >
<title>palloc (1,745,379,926 samples, 0.02%)</title><rect x="967.2" y="341" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="970.20" y="351.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,158,295,759 samples, 0.03%)</title><rect x="363.2" y="341" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="366.15" y="351.5" ></text>
</g>
<g >
<title>pull_varattnos_walker (4,515,981,856 samples, 0.05%)</title><rect x="997.8" y="293" width="0.6" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="1000.82" y="303.5" ></text>
</g>
<g >
<title>hash_bytes (2,321,714,376 samples, 0.02%)</title><rect x="942.9" y="309" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="945.86" y="319.5" ></text>
</g>
<g >
<title>palloc0 (2,795,300,033 samples, 0.03%)</title><rect x="210.5" y="549" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="213.47" y="559.5" ></text>
</g>
<g >
<title>pull_varnos_walker (1,051,531,123 samples, 0.01%)</title><rect x="1036.2" y="197" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="1039.24" y="207.5" ></text>
</g>
<g >
<title>ExecScanFetch (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="389" width="1.7" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="125.45" y="399.5" ></text>
</g>
<g >
<title>AtEOXact_GUC (1,201,026,492 samples, 0.01%)</title><rect x="31.3" y="757" width="0.1" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="34.25" y="767.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (4,313,585,585 samples, 0.05%)</title><rect x="1023.1" y="325" width="0.6" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1026.14" y="335.5" ></text>
</g>
<g >
<title>transformFromClause (1,608,288,698 samples, 0.02%)</title><rect x="831.2" y="485" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="834.16" y="495.5" ></text>
</g>
<g >
<title>btgettuple (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="373" width="6.6" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="97.67" y="383.5" ></text>
</g>
<g >
<title>int4hashfast (832,039,210 samples, 0.01%)</title><rect x="103.3" y="389" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="106.29" y="399.5" ></text>
</g>
<g >
<title>makeConst (2,652,947,007 samples, 0.03%)</title><rect x="838.9" y="357" width="0.3" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="841.91" y="367.5" ></text>
</g>
<g >
<title>create_scan_plan (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="485" width="0.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="88.40" y="495.5" ></text>
</g>
<g >
<title>hash_search (3,241,538,361 samples, 0.03%)</title><rect x="938.7" y="389" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="941.71" y="399.5" ></text>
</g>
<g >
<title>RelationGetIndexScan (5,891,813,543 samples, 0.06%)</title><rect x="290.5" y="277" width="0.8" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="293.55" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (935,747,975 samples, 0.01%)</title><rect x="816.1" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="819.12" y="399.5" ></text>
</g>
<g >
<title>rewriteTargetListIU (1,002,914,448 samples, 0.01%)</title><rect x="1176.4" y="757" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="1179.39" y="767.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,079,025,424 samples, 0.01%)</title><rect x="354.4" y="181" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="357.39" y="191.5" ></text>
</g>
<g >
<title>lappend (2,862,682,519 samples, 0.03%)</title><rect x="858.4" y="517" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="861.40" y="527.5" ></text>
</g>
<g >
<title>pfree (1,139,364,269 samples, 0.01%)</title><rect x="954.7" y="421" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="957.65" y="431.5" ></text>
</g>
<g >
<title>palloc0 (2,280,289,864 samples, 0.02%)</title><rect x="1056.2" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1059.18" y="431.5" ></text>
</g>
<g >
<title>SetHintBits (9,710,894,851 samples, 0.10%)</title><rect x="305.4" y="213" width="1.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="308.35" y="223.5" ></text>
</g>
<g >
<title>get_hash_value (4,179,986,745 samples, 0.04%)</title><rect x="820.2" y="357" width="0.5" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="823.18" y="367.5" ></text>
</g>
<g >
<title>transformTopLevelStmt (451,473,314,561 samples, 4.78%)</title><rect x="800.4" y="549" width="56.5" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="803.42" y="559.5" >trans..</text>
</g>
<g >
<title>eval_const_expressions_mutator (975,983,726 samples, 0.01%)</title><rect x="126.7" y="469" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="129.71" y="479.5" ></text>
</g>
<g >
<title>task_tick_fair (923,816,712 samples, 0.01%)</title><rect x="791.5" y="389" width="0.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="794.47" y="399.5" ></text>
</g>
<g >
<title>compute_new_xmax_infomask (2,057,139,801 samples, 0.02%)</title><rect x="376.4" y="357" width="0.3" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="379.42" y="367.5" ></text>
</g>
<g >
<title>disable_statement_timeout (1,524,813,162 samples, 0.02%)</title><rect x="797.9" y="565" width="0.2" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="800.90" y="575.5" ></text>
</g>
<g >
<title>palloc0 (4,706,744,388 samples, 0.05%)</title><rect x="1164.7" y="757" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1167.73" y="767.5" ></text>
</g>
<g >
<title>BackendStartup (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="725" width="3.0" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="85.62" y="735.5" ></text>
</g>
<g >
<title>free_parsestate (6,172,061,280 samples, 0.07%)</title><rect x="798.7" y="549" width="0.7" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="801.66" y="559.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_safe_stack (3,224,881,303 samples, 0.03%)</title><rect x="1133.1" y="757" width="0.4" height="15.0" fill="rgb(231,120,28)" rx="2" ry="2" />
<text  x="1136.13" y="767.5" ></text>
</g>
<g >
<title>preprocess_function_rtes (1,621,300,606 samples, 0.02%)</title><rect x="1054.8" y="501" width="0.3" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="1057.85" y="511.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,189,625,120 samples, 0.01%)</title><rect x="711.9" y="517" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="714.86" y="527.5" ></text>
</g>
<g >
<title>exec_simple_query (6,965,474,342,363 samples, 73.81%)</title><rect x="208.0" y="597" width="871.0" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="211.02" y="607.5" >exec_simple_query</text>
</g>
<g >
<title>ksys_lseek (5,625,399,365 samples, 0.06%)</title><rect x="937.2" y="181" width="0.7" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="940.20" y="191.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,549,544,928 samples, 0.03%)</title><rect x="299.5" y="117" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="302.47" y="127.5" ></text>
</g>
<g >
<title>do_futex (982,400,339 samples, 0.01%)</title><rect x="398.7" y="261" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="401.67" y="271.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (839,893,324 samples, 0.01%)</title><rect x="872.1" y="469" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="875.05" y="479.5" ></text>
</g>
<g >
<title>LockTagHashCode (3,397,289,990 samples, 0.04%)</title><rect x="518.7" y="453" width="0.4" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="521.72" y="463.5" ></text>
</g>
<g >
<title>lappend (2,130,641,366 samples, 0.02%)</title><rect x="1054.6" y="437" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1057.57" y="447.5" ></text>
</g>
<g >
<title>PinBufferForBlock (4,349,270,893 samples, 0.05%)</title><rect x="84.2" y="197" width="0.5" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="87.17" y="207.5" ></text>
</g>
<g >
<title>_raw_spin_lock (1,234,962,817 samples, 0.01%)</title><rect x="162.5" y="293" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="165.50" y="303.5" ></text>
</g>
<g >
<title>new_list (2,083,360,679 samples, 0.02%)</title><rect x="976.9" y="405" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="979.89" y="415.5" ></text>
</g>
<g >
<title>BufTableLookup (7,323,954,163 samples, 0.08%)</title><rect x="298.4" y="133" width="0.9" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="301.41" y="143.5" ></text>
</g>
<g >
<title>makeString (3,664,182,240 samples, 0.04%)</title><rect x="1119.9" y="725" width="0.5" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="1122.93" y="735.5" ></text>
</g>
<g >
<title>ItemPointerSetInvalid (858,186,334 samples, 0.01%)</title><rect x="56.0" y="757" width="0.1" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="58.96" y="767.5" ></text>
</g>
<g >
<title>palloc (1,317,221,226 samples, 0.01%)</title><rect x="433.0" y="373" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="436.01" y="383.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,082,265,838 samples, 0.01%)</title><rect x="797.7" y="549" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="800.69" y="559.5" ></text>
</g>
<g >
<title>list_free_private (1,486,022,785 samples, 0.02%)</title><rect x="395.9" y="373" width="0.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="398.93" y="383.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="693" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1187.99" y="703.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (3,467,470,174 samples, 0.04%)</title><rect x="267.2" y="309" width="0.5" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="270.25" y="319.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,120,197,275 samples, 0.04%)</title><rect x="1047.7" y="389" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1050.67" y="399.5" ></text>
</g>
<g >
<title>slab_update_freelist.isra.0 (1,364,194,969 samples, 0.01%)</title><rect x="179.9" y="309" width="0.2" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="182.88" y="319.5" ></text>
</g>
<g >
<title>_bt_unlockbuf (3,515,407,154 samples, 0.04%)</title><rect x="339.7" y="229" width="0.5" height="15.0" fill="rgb(244,179,43)" rx="2" ry="2" />
<text  x="342.73" y="239.5" ></text>
</g>
<g >
<title>newNode (5,595,662,651 samples, 0.06%)</title><rect x="952.9" y="405" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="955.92" y="415.5" ></text>
</g>
<g >
<title>list_concat (3,008,503,159 samples, 0.03%)</title><rect x="977.9" y="437" width="0.3" height="15.0" fill="rgb(249,202,48)" rx="2" ry="2" />
<text  x="980.86" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_read_membarrier_u64_impl (1,289,339,486 samples, 0.01%)</title><rect x="491.2" y="453" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="494.23" y="463.5" ></text>
</g>
<g >
<title>__cgroup_account_cputime (1,065,104,113 samples, 0.01%)</title><rect x="480.6" y="181" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="483.58" y="191.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,406,971,742 samples, 0.03%)</title><rect x="1120.7" y="693" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1123.72" y="703.5" ></text>
</g>
<g >
<title>set_plain_rel_size (803,901,079 samples, 0.01%)</title><rect x="126.6" y="437" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="129.56" y="447.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,543,288,860 samples, 0.02%)</title><rect x="358.4" y="245" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="361.36" y="255.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,251,170,992 samples, 0.01%)</title><rect x="503.4" y="421" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="506.40" y="431.5" ></text>
</g>
<g >
<title>__vdso_gettimeofday (915,859,628 samples, 0.01%)</title><rect x="1080.1" y="565" width="0.2" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="1083.14" y="575.5" ></text>
</g>
<g >
<title>setNamespaceLateralState (818,083,144 samples, 0.01%)</title><rect x="1181.0" y="757" width="0.1" height="15.0" fill="rgb(252,220,52)" rx="2" ry="2" />
<text  x="1183.98" y="767.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (3,228,633,656 samples, 0.03%)</title><rect x="750.3" y="517" width="0.4" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="753.31" y="527.5" ></text>
</g>
<g >
<title>GetCommandTagNameAndLen (1,264,879,297 samples, 0.01%)</title><rect x="215.5" y="549" width="0.2" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="218.55" y="559.5" ></text>
</g>
<g >
<title>GetSnapshotData (27,502,169,760 samples, 0.29%)</title><rect x="229.3" y="533" width="3.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="232.29" y="543.5" ></text>
</g>
<g >
<title>ExecOpenIndices (852,353,406 samples, 0.01%)</title><rect x="44.7" y="757" width="0.1" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="47.74" y="767.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (903,786,370 samples, 0.01%)</title><rect x="77.1" y="757" width="0.1" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="80.10" y="767.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (1,977,147,596 samples, 0.02%)</title><rect x="470.3" y="405" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="473.31" y="415.5" ></text>
</g>
<g >
<title>AllocSetFree (1,237,661,670 samples, 0.01%)</title><rect x="232.9" y="501" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="235.94" y="511.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (1,732,564,165 samples, 0.02%)</title><rect x="114.1" y="741" width="0.2" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="117.09" y="751.5" ></text>
</g>
<g >
<title>pgstat_report_stat (1,118,400,627 samples, 0.01%)</title><rect x="1171.4" y="757" width="0.1" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1174.39" y="767.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,302,054,821 samples, 0.01%)</title><rect x="1030.8" y="261" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1033.77" y="271.5" ></text>
</g>
<g >
<title>pg_leftmost_one_pos32 (2,672,744,073 samples, 0.03%)</title><rect x="1168.8" y="757" width="0.3" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1171.79" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,376,352,590 samples, 0.01%)</title><rect x="967.2" y="325" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="970.23" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (959,126,077 samples, 0.01%)</title><rect x="832.7" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="835.67" y="431.5" ></text>
</g>
<g >
<title>hash_bytes (2,410,732,083 samples, 0.03%)</title><rect x="355.0" y="229" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="358.03" y="239.5" ></text>
</g>
<g >
<title>btgettuple (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="309" width="1.9" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="127.59" y="319.5" ></text>
</g>
<g >
<title>XLogCheckBufferNeedsBackup (1,136,967,437 samples, 0.01%)</title><rect x="109.3" y="757" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="112.30" y="767.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="501" width="6.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="97.67" y="511.5" ></text>
</g>
<g >
<title>slot_getattr (986,363,061 samples, 0.01%)</title><rect x="265.9" y="421" width="0.1" height="15.0" fill="rgb(214,41,9)" rx="2" ry="2" />
<text  x="268.87" y="431.5" ></text>
</g>
<g >
<title>__sysvec_thermal (1,038,775,425 samples, 0.01%)</title><rect x="791.7" y="485" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="794.69" y="495.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,075,132,847 samples, 0.01%)</title><rect x="296.8" y="197" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="299.84" y="207.5" ></text>
</g>
<g >
<title>hash_search (4,801,520,088 samples, 0.05%)</title><rect x="942.5" y="341" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="945.55" y="351.5" ></text>
</g>
<g >
<title>ResourceOwnerRelease (122,164,796,779 samples, 1.29%)</title><rect x="510.7" y="517" width="15.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="513.66" y="527.5" ></text>
</g>
<g >
<title>__wake_up_common (62,188,788,410 samples, 0.66%)</title><rect x="197.9" y="373" width="7.8" height="15.0" fill="rgb(248,197,47)" rx="2" ry="2" />
<text  x="200.91" y="383.5" ></text>
</g>
<g >
<title>ReadBufferExtended (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="277" width="4.3" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="97.67" y="287.5" ></text>
</g>
<g >
<title>llseek@GLIBC_2.2.5 (7,232,093,334 samples, 0.08%)</title><rect x="932.3" y="325" width="0.9" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="935.26" y="335.5" ></text>
</g>
<g >
<title>StartReadBuffer (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="245" width="4.3" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="97.67" y="255.5" ></text>
</g>
<g >
<title>ExecIndexScan (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="485" width="6.6" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="97.67" y="495.5" ></text>
</g>
<g >
<title>enqueue_task (1,868,377,183 samples, 0.02%)</title><rect x="199.2" y="325" width="0.2" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="202.21" y="335.5" ></text>
</g>
<g >
<title>get_hash_value (2,963,968,979 samples, 0.03%)</title><rect x="355.0" y="261" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="357.96" y="271.5" ></text>
</g>
<g >
<title>CreateExprContextInternal (10,053,786,576 samples, 0.11%)</title><rect x="414.5" y="389" width="1.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="417.45" y="399.5" ></text>
</g>
<g >
<title>new_head_cell (822,235,130 samples, 0.01%)</title><rect x="269.8" y="357" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="272.80" y="367.5" ></text>
</g>
<g >
<title>tag_hash (2,761,332,980 samples, 0.03%)</title><rect x="399.8" y="245" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="402.78" y="255.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (3,468,236,666 samples, 0.04%)</title><rect x="946.7" y="373" width="0.4" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="949.70" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (966,912,521 samples, 0.01%)</title><rect x="415.6" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="418.59" y="351.5" ></text>
</g>
<g >
<title>dlist_is_empty (3,339,654,089 samples, 0.04%)</title><rect x="1132.1" y="757" width="0.4" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="1135.10" y="767.5" ></text>
</g>
<g >
<title>palloc (3,894,360,035 samples, 0.04%)</title><rect x="872.4" y="533" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="875.37" y="543.5" ></text>
</g>
<g >
<title>VARDATA (821,638,922 samples, 0.01%)</title><rect x="108.4" y="757" width="0.1" height="15.0" fill="rgb(236,145,34)" rx="2" ry="2" />
<text  x="111.43" y="767.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,113,826,450 samples, 0.01%)</title><rect x="830.8" y="357" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="833.84" y="367.5" ></text>
</g>
<g >
<title>palloc (1,554,090,025 samples, 0.02%)</title><rect x="956.7" y="277" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="959.65" y="287.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,249,424,932 samples, 0.01%)</title><rect x="1030.8" y="245" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1033.78" y="255.5" ></text>
</g>
<g >
<title>make_one_rel (1,211,603,195 samples, 0.01%)</title><rect x="1159.1" y="757" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1162.14" y="767.5" ></text>
</g>
<g >
<title>AtEOXact_LargeObject (1,534,045,069 samples, 0.02%)</title><rect x="31.5" y="757" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="34.46" y="767.5" ></text>
</g>
<g >
<title>pg_plan_query (2,495,599,539 samples, 0.03%)</title><rect x="126.5" y="581" width="0.3" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="129.52" y="591.5" ></text>
</g>
<g >
<title>PinBuffer (8,704,603,201 samples, 0.09%)</title><rect x="96.9" y="181" width="1.1" height="15.0" fill="rgb(219,64,15)" rx="2" ry="2" />
<text  x="99.91" y="191.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="517" width="3.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="104.45" y="527.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (956,538,273 samples, 0.01%)</title><rect x="963.3" y="341" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="966.31" y="351.5" ></text>
</g>
<g >
<title>palloc0 (2,023,711,926 samples, 0.02%)</title><rect x="448.3" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="451.27" y="447.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,878,371,228 samples, 0.02%)</title><rect x="234.1" y="469" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="237.10" y="479.5" ></text>
</g>
<g >
<title>ExecScanFetch (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="421" width="2.8" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="85.62" y="431.5" ></text>
</g>
<g >
<title>[[vdso]] (1,517,424,091 samples, 0.02%)</title><rect x="468.9" y="469" width="0.2" height="15.0" fill="rgb(218,60,14)" rx="2" ry="2" />
<text  x="471.92" y="479.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,022,981,844 samples, 0.01%)</title><rect x="834.8" y="357" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="837.84" y="367.5" ></text>
</g>
<g >
<title>int4eqfast (804,740,842 samples, 0.01%)</title><rect x="37.7" y="741" width="0.1" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="40.66" y="751.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,262,678,273 samples, 0.02%)</title><rect x="974.8" y="293" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="977.78" y="303.5" ></text>
</g>
<g >
<title>hash_search (3,373,068,048 samples, 0.04%)</title><rect x="1013.4" y="261" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1016.36" y="271.5" ></text>
</g>
<g >
<title>mutex_unlock (1,414,277,745 samples, 0.01%)</title><rect x="158.0" y="373" width="0.2" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="161.01" y="383.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (1,002,586,792 samples, 0.01%)</title><rect x="342.3" y="437" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="345.32" y="447.5" ></text>
</g>
<g >
<title>lappend (1,699,899,131 samples, 0.02%)</title><rect x="896.4" y="453" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="899.38" y="463.5" ></text>
</g>
<g >
<title>find_oper_cache_entry (19,071,631,479 samples, 0.20%)</title><rect x="849.0" y="389" width="2.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="852.00" y="399.5" ></text>
</g>
<g >
<title>pick_task_fair (1,561,558,570 samples, 0.02%)</title><rect x="476.7" y="245" width="0.2" height="15.0" fill="rgb(243,176,42)" rx="2" ry="2" />
<text  x="479.73" y="255.5" ></text>
</g>
<g >
<title>new_list (1,650,770,512 samples, 0.02%)</title><rect x="1054.6" y="421" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1057.63" y="431.5" ></text>
</g>
<g >
<title>hash_search (3,365,147,629 samples, 0.04%)</title><rect x="862.8" y="469" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="865.81" y="479.5" ></text>
</g>
<g >
<title>sched_balance_softirq (1,277,417,898 samples, 0.01%)</title><rect x="750.9" y="469" width="0.2" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="753.91" y="479.5" ></text>
</g>
<g >
<title>sentinel_ok (318,324,775,795 samples, 3.37%)</title><rect x="752.0" y="533" width="39.8" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="755.02" y="543.5" >sen..</text>
</g>
<g >
<title>BTreeTupleGetHeapTID (1,899,442,881 samples, 0.02%)</title><rect x="333.1" y="197" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="336.14" y="207.5" ></text>
</g>
<g >
<title>ExecScan (27,732,329,336 samples, 0.29%)</title><rect x="278.8" y="405" width="3.5" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="281.85" y="415.5" ></text>
</g>
<g >
<title>tag_hash (2,575,012,083 samples, 0.03%)</title><rect x="394.7" y="309" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="397.69" y="319.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="373" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="88.40" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (815,075,979 samples, 0.01%)</title><rect x="931.5" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="934.47" y="351.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (13,567,951,734 samples, 0.14%)</title><rect x="827.9" y="405" width="1.7" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="830.89" y="415.5" ></text>
</g>
<g >
<title>_bt_compare (55,555,975,992 samples, 0.59%)</title><rect x="314.6" y="245" width="6.9" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="317.55" y="255.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,933,992,478 samples, 0.03%)</title><rect x="943.6" y="341" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="946.55" y="351.5" ></text>
</g>
<g >
<title>PageGetItemId (5,732,532,790 samples, 0.06%)</title><rect x="373.2" y="325" width="0.7" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="376.16" y="335.5" ></text>
</g>
<g >
<title>UnlockRelationId (11,222,981,045 samples, 0.12%)</title><rect x="236.3" y="437" width="1.5" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="239.35" y="447.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (848,207,855 samples, 0.01%)</title><rect x="825.8" y="261" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="828.85" y="271.5" ></text>
</g>
<g >
<title>heap_page_prune_execute (2,161,795,325 samples, 0.02%)</title><rect x="1145.4" y="741" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="1148.39" y="751.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (1,644,656,066 samples, 0.02%)</title><rect x="352.6" y="261" width="0.2" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="355.58" y="271.5" ></text>
</g>
<g >
<title>list_nth_cell (870,447,626 samples, 0.01%)</title><rect x="1020.9" y="309" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1023.93" y="319.5" ></text>
</g>
<g >
<title>PageGetItem (2,722,550,957 samples, 0.03%)</title><rect x="318.9" y="213" width="0.4" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="321.94" y="223.5" ></text>
</g>
<g >
<title>VARSIZE_SHORT (945,444,494 samples, 0.01%)</title><rect x="108.5" y="757" width="0.2" height="15.0" fill="rgb(225,96,22)" rx="2" ry="2" />
<text  x="111.55" y="767.5" ></text>
</g>
<g >
<title>palloc (1,538,721,179 samples, 0.02%)</title><rect x="430.3" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="433.25" y="367.5" ></text>
</g>
<g >
<title>MemoryChunkSetHdrMask (1,171,761,604 samples, 0.01%)</title><rect x="946.9" y="357" width="0.2" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="949.92" y="367.5" ></text>
</g>
<g >
<title>add_path (12,463,142,138 samples, 0.13%)</title><rect x="993.1" y="373" width="1.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="996.08" y="383.5" ></text>
</g>
<g >
<title>finalize_primnode (1,861,682,779 samples, 0.02%)</title><rect x="881.6" y="357" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="884.55" y="367.5" ></text>
</g>
<g >
<title>selinux_socket_getpeersec_dgram (3,125,824,042 samples, 0.03%)</title><rect x="192.5" y="389" width="0.4" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="195.46" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (805,108,812 samples, 0.01%)</title><rect x="897.8" y="421" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="900.75" y="431.5" ></text>
</g>
<g >
<title>RelationGetIndexExpressions (1,254,054,701 samples, 0.01%)</title><rect x="392.2" y="373" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="395.23" y="383.5" ></text>
</g>
<g >
<title>table_block_relation_estimate_size (1,043,067,790 samples, 0.01%)</title><rect x="1185.4" y="757" width="0.2" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="1188.43" y="767.5" ></text>
</g>
<g >
<title>makeTargetEntry (3,096,038,524 samples, 0.03%)</title><rect x="934.0" y="389" width="0.4" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="937.03" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,212,317,728 samples, 0.01%)</title><rect x="367.9" y="245" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="370.92" y="255.5" ></text>
</g>
<g >
<title>FunctionCall4Coll (803,901,079 samples, 0.01%)</title><rect x="126.6" y="325" width="0.1" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="129.56" y="335.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (953,009,696 samples, 0.01%)</title><rect x="162.1" y="293" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="165.07" y="303.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (1,552,085,421 samples, 0.02%)</title><rect x="273.3" y="373" width="0.2" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="276.35" y="383.5" ></text>
</g>
<g >
<title>ExecIndexScan (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="437" width="1.7" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="125.45" y="447.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,594,781,025 samples, 0.04%)</title><rect x="973.5" y="373" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="976.53" y="383.5" ></text>
</g>
<g >
<title>palloc0 (3,406,202,238 samples, 0.04%)</title><rect x="1118.4" y="709" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1121.39" y="719.5" ></text>
</g>
<g >
<title>expr_setup_walker (4,544,951,952 samples, 0.05%)</title><rect x="430.4" y="389" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="433.44" y="399.5" ></text>
</g>
<g >
<title>do_futex (1,079,025,424 samples, 0.01%)</title><rect x="354.4" y="165" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="357.39" y="175.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,099,441,429 samples, 0.01%)</title><rect x="1071.8" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1074.76" y="447.5" ></text>
</g>
<g >
<title>palloc0 (7,746,966,551 samples, 0.08%)</title><rect x="450.3" y="437" width="0.9" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="453.26" y="447.5" ></text>
</g>
<g >
<title>bms_is_valid_set (1,755,080,258 samples, 0.02%)</title><rect x="369.4" y="325" width="0.3" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="372.44" y="335.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (167,080,284,196 samples, 1.77%)</title><rect x="674.6" y="533" width="20.8" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="677.56" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,193,615,800 samples, 0.01%)</title><rect x="293.2" y="261" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="296.20" y="271.5" ></text>
</g>
<g >
<title>BufferGetBlock (878,085,993 samples, 0.01%)</title><rect x="327.1" y="213" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="330.12" y="223.5" ></text>
</g>
<g >
<title>_bt_num_array_keys (2,768,473,694 samples, 0.03%)</title><rect x="82.6" y="293" width="0.4" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="85.62" y="303.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,134,240,196 samples, 0.01%)</title><rect x="354.4" y="197" width="0.1" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="357.39" y="207.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (5,772,297,600 samples, 0.06%)</title><rect x="445.7" y="389" width="0.8" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="448.73" y="399.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (869,290,058 samples, 0.01%)</title><rect x="460.4" y="437" width="0.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="463.40" y="447.5" ></text>
</g>
<g >
<title>palloc (1,233,028,346 samples, 0.01%)</title><rect x="931.4" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="934.43" y="367.5" ></text>
</g>
<g >
<title>newNode (2,056,792,506 samples, 0.02%)</title><rect x="808.6" y="469" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="811.58" y="479.5" ></text>
</g>
<g >
<title>BufferGetLSNAtomic (11,190,923,912 samples, 0.12%)</title><rect x="323.3" y="229" width="1.4" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="326.30" y="239.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,340,154,164 samples, 0.01%)</title><rect x="1147.4" y="613" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1150.39" y="623.5" ></text>
</g>
<g >
<title>newNode (3,073,842,936 samples, 0.03%)</title><rect x="991.3" y="373" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="994.30" y="383.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,503,530,958 samples, 0.02%)</title><rect x="1083.3" y="565" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1086.34" y="575.5" ></text>
</g>
<g >
<title>ServerLoop (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="725" width="0.6" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="1160.94" y="735.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,119,024,904 samples, 0.01%)</title><rect x="84.8" y="165" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="87.76" y="175.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,239,181,810 samples, 0.01%)</title><rect x="975.7" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="978.70" y="399.5" ></text>
</g>
<g >
<title>get_tablespace_page_costs (7,013,960,192 samples, 0.07%)</title><rect x="1022.9" y="373" width="0.9" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1025.91" y="383.5" ></text>
</g>
<g >
<title>GetTransactionSnapshot (28,547,043,109 samples, 0.30%)</title><rect x="229.2" y="549" width="3.6" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="232.20" y="559.5" ></text>
</g>
<g >
<title>index_getattr (2,380,285,561 samples, 0.03%)</title><rect x="122.9" y="229" width="0.3" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="125.85" y="239.5" ></text>
</g>
<g >
<title>clause_selectivity_ext (840,174,730 samples, 0.01%)</title><rect x="1127.8" y="757" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="1130.81" y="767.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (1,355,094,163 samples, 0.01%)</title><rect x="368.4" y="245" width="0.2" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="371.44" y="255.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (2,118,089,870 samples, 0.02%)</title><rect x="388.0" y="293" width="0.3" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="391.03" y="303.5" ></text>
</g>
<g >
<title>FunctionCall4Coll (53,870,020,065 samples, 0.57%)</title><rect x="1030.2" y="309" width="6.8" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1033.22" y="319.5" ></text>
</g>
<g >
<title>MarkBufferDirty (7,652,953,874 samples, 0.08%)</title><rect x="364.0" y="357" width="1.0" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="367.01" y="367.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,508,460,386 samples, 0.02%)</title><rect x="470.9" y="437" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="473.87" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,342,893,524 samples, 0.01%)</title><rect x="815.8" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="818.84" y="415.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (3,306,067,574 samples, 0.04%)</title><rect x="363.6" y="325" width="0.4" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="366.59" y="335.5" ></text>
</g>
<g >
<title>avc_has_perm_noaudit (1,773,331,032 samples, 0.02%)</title><rect x="191.4" y="373" width="0.3" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="194.44" y="383.5" ></text>
</g>
<g >
<title>unix_maybe_add_creds (2,197,643,037 samples, 0.02%)</title><rect x="205.8" y="405" width="0.3" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="208.78" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,088,317,518 samples, 0.01%)</title><rect x="430.3" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="433.30" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,971,005,738 samples, 0.02%)</title><rect x="1077.1" y="485" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1080.10" y="495.5" ></text>
</g>
<g >
<title>gup_fast (1,111,388,787 samples, 0.01%)</title><rect x="352.1" y="117" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="355.15" y="127.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,102,549,791 samples, 0.04%)</title><rect x="381.2" y="277" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="384.22" y="287.5" ></text>
</g>
<g >
<title>set_plain_rel_pathlist (1,116,431,889 samples, 0.01%)</title><rect x="1181.6" y="757" width="0.2" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="1184.64" y="767.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (9,483,328,113 samples, 0.10%)</title><rect x="351.6" y="293" width="1.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="354.61" y="303.5" ></text>
</g>
<g >
<title>verify_compact_attribute (10,120,526,468 samples, 0.11%)</title><rect x="320.1" y="197" width="1.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="323.07" y="207.5" ></text>
</g>
<g >
<title>RelationGetNumberOfBlocksInFork (1,145,365,047 samples, 0.01%)</title><rect x="89.0" y="757" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="91.99" y="767.5" ></text>
</g>
<g >
<title>BufferGetPage (1,467,145,617 samples, 0.02%)</title><rect x="347.7" y="357" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="350.73" y="367.5" ></text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (4,809,556,842 samples, 0.05%)</title><rect x="968.2" y="357" width="0.6" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="971.24" y="367.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,830,424,723 samples, 0.02%)</title><rect x="437.3" y="341" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="440.25" y="351.5" ></text>
</g>
<g >
<title>__strncmp_avx2 (1,559,496,350 samples, 0.02%)</title><rect x="824.7" y="277" width="0.2" height="15.0" fill="rgb(229,110,26)" rx="2" ry="2" />
<text  x="827.73" y="287.5" ></text>
</g>
<g >
<title>ProcessClientReadInterrupt (1,506,740,603 samples, 0.02%)</title><rect x="86.7" y="757" width="0.1" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="89.65" y="767.5" ></text>
</g>
<g >
<title>WALInsertLockAcquire (3,473,893,716 samples, 0.04%)</title><rect x="508.0" y="453" width="0.4" height="15.0" fill="rgb(226,98,23)" rx="2" ry="2" />
<text  x="511.00" y="463.5" ></text>
</g>
<g >
<title>makeString (4,124,113,156 samples, 0.04%)</title><rect x="814.2" y="437" width="0.5" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="817.23" y="447.5" ></text>
</g>
<g >
<title>ExecProcNode (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="549" width="2.8" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="85.62" y="559.5" ></text>
</g>
<g >
<title>FullXidRelativeTo (1,860,822,190 samples, 0.02%)</title><rect x="310.0" y="229" width="0.3" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="313.02" y="239.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,172,775,283 samples, 0.01%)</title><rect x="363.4" y="293" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="366.39" y="303.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (9,144,438,755 samples, 0.10%)</title><rect x="11.7" y="757" width="1.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="14.66" y="767.5" ></text>
</g>
<g >
<title>__perf_event_task_sched_out (3,600,778,051 samples, 0.04%)</title><rect x="477.8" y="261" width="0.5" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="480.83" y="271.5" ></text>
</g>
<g >
<title>tag_hash (2,931,740,284 samples, 0.03%)</title><rect x="821.6" y="357" width="0.4" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="824.63" y="367.5" ></text>
</g>
<g >
<title>GlobalVisTestIsRemovableXid (4,249,457,617 samples, 0.05%)</title><rect x="310.0" y="245" width="0.5" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="312.97" y="255.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,246,889,054 samples, 0.01%)</title><rect x="123.9" y="213" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="126.93" y="223.5" ></text>
</g>
<g >
<title>table_block_relation_size (1,110,061,192 samples, 0.01%)</title><rect x="1185.6" y="757" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="1188.56" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (1,451,903,061 samples, 0.02%)</title><rect x="222.0" y="533" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="224.97" y="543.5" ></text>
</g>
<g >
<title>max_parallel_hazard_walker (864,717,797 samples, 0.01%)</title><rect x="1160.7" y="757" width="0.1" height="15.0" fill="rgb(239,157,37)" rx="2" ry="2" />
<text  x="1163.66" y="767.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,520,211,729 samples, 0.03%)</title><rect x="100.2" y="165" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="103.23" y="175.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,752,051,379 samples, 0.04%)</title><rect x="1038.9" y="309" width="0.5" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1041.91" y="319.5" ></text>
</g>
<g >
<title>__rseq_handle_notify_resume (1,974,690,579 samples, 0.02%)</title><rect x="483.7" y="373" width="0.3" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="486.74" y="383.5" ></text>
</g>
<g >
<title>heap_getattr (1,707,156,252 samples, 0.02%)</title><rect x="1145.0" y="757" width="0.2" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1147.98" y="767.5" ></text>
</g>
<g >
<title>schedule (1,166,527,053 samples, 0.01%)</title><rect x="174.2" y="421" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="177.15" y="431.5" ></text>
</g>
<g >
<title>__libc_pwrite (69,987,824,501 samples, 0.74%)</title><rect x="493.0" y="469" width="8.8" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="496.05" y="479.5" ></text>
</g>
<g >
<title>sock_wfree (9,057,113,750 samples, 0.10%)</title><rect x="180.2" y="325" width="1.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="183.23" y="335.5" ></text>
</g>
<g >
<title>finalize_plan (47,339,126,318 samples, 0.50%)</title><rect x="876.0" y="501" width="5.9" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="878.95" y="511.5" ></text>
</g>
<g >
<title>subquery_planner (1,345,421,384,195 samples, 14.26%)</title><rect x="903.8" y="517" width="168.2" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="906.82" y="527.5" >subquery_planner</text>
</g>
<g >
<title>ServerLoop (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="693" width="2.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="127.59" y="703.5" ></text>
</g>
<g >
<title>SearchSysCache (886,081,989 samples, 0.01%)</title><rect x="93.8" y="757" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="96.84" y="767.5" ></text>
</g>
<g >
<title>AllocSetAllocFromNewBlock (7,087,497,772 samples, 0.08%)</title><rect x="277.3" y="341" width="0.8" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="280.26" y="351.5" ></text>
</g>
<g >
<title>GETSTRUCT (3,250,807,967 samples, 0.03%)</title><rect x="961.9" y="373" width="0.4" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="964.89" y="383.5" ></text>
</g>
<g >
<title>new_list (1,683,195,784 samples, 0.02%)</title><rect x="893.1" y="437" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="896.13" y="447.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,520,156,621 samples, 0.04%)</title><rect x="1034.8" y="117" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1037.76" y="127.5" ></text>
</g>
<g >
<title>ReleaseCatCache (837,727,980 samples, 0.01%)</title><rect x="1056.5" y="405" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1059.53" y="415.5" ></text>
</g>
<g >
<title>hash_initial_lookup (866,785,177 samples, 0.01%)</title><rect x="924.9" y="389" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="927.93" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,386,984,337 samples, 0.01%)</title><rect x="449.8" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="452.79" y="415.5" ></text>
</g>
<g >
<title>PostmasterMain (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="693" width="953.9" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="133.07" y="703.5" >PostmasterMain</text>
</g>
<g >
<title>index_getnext_tid (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="357" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1160.94" y="367.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (2,999,973,500 samples, 0.03%)</title><rect x="1037.6" y="277" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="1040.63" y="287.5" ></text>
</g>
<g >
<title>rseq_ip_fixup (4,804,340,218 samples, 0.05%)</title><rect x="173.4" y="405" width="0.6" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="176.38" y="415.5" ></text>
</g>
<g >
<title>IncrBufferRefCount (4,127,395,913 samples, 0.04%)</title><rect x="295.4" y="229" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="298.40" y="239.5" ></text>
</g>
<g >
<title>bms_free (5,557,029,676 samples, 0.06%)</title><rect x="375.4" y="357" width="0.7" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="378.36" y="367.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,284,346,325 samples, 0.02%)</title><rect x="84.4" y="181" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="87.42" y="191.5" ></text>
</g>
<g >
<title>gup_fast_fallback (1,268,375,462 samples, 0.01%)</title><rect x="352.1" y="133" width="0.2" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="355.13" y="143.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,223,679,891 samples, 0.02%)</title><rect x="947.7" y="309" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="950.68" y="319.5" ></text>
</g>
<g >
<title>BackendStartup (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="693" width="2.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="125.45" y="703.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,037,590,164 samples, 0.02%)</title><rect x="924.8" y="405" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="927.78" y="415.5" ></text>
</g>
<g >
<title>palloc0 (1,471,663,141 samples, 0.02%)</title><rect x="452.5" y="501" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="455.52" y="511.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (1,357,583,768 samples, 0.01%)</title><rect x="126.2" y="229" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="129.24" y="239.5" ></text>
</g>
<g >
<title>ensure_tabstat_xact_level (8,660,567,545 samples, 0.09%)</title><rect x="387.3" y="341" width="1.0" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="390.26" y="351.5" ></text>
</g>
<g >
<title>BufferIsLockedByMeInMode (1,762,019,688 samples, 0.02%)</title><rect x="386.6" y="325" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="389.55" y="335.5" ></text>
</g>
<g >
<title>OidFunctionCall4Coll (803,901,079 samples, 0.01%)</title><rect x="126.6" y="341" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="129.56" y="351.5" ></text>
</g>
<g >
<title>XLogRegisterData (1,594,816,463 samples, 0.02%)</title><rect x="386.8" y="341" width="0.2" height="15.0" fill="rgb(222,82,19)" rx="2" ry="2" />
<text  x="389.80" y="351.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (38,403,461,072 samples, 0.41%)</title><rect x="257.3" y="437" width="4.8" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="260.30" y="447.5" ></text>
</g>
<g >
<title>is_valid_ascii (5,027,088,524 samples, 0.05%)</title><rect x="1082.4" y="517" width="0.7" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1085.45" y="527.5" ></text>
</g>
<g >
<title>_bt_parallel_done (1,464,455,871 samples, 0.02%)</title><rect x="281.5" y="245" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="284.53" y="255.5" ></text>
</g>
<g >
<title>LWLockRelease (1,471,978,897 samples, 0.02%)</title><rect x="232.4" y="517" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="235.44" y="527.5" ></text>
</g>
<g >
<title>get_typlen (6,012,431,094 samples, 0.06%)</title><rect x="1047.4" y="437" width="0.8" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="1050.43" y="447.5" ></text>
</g>
<g >
<title>BufferIsValid (980,181,183 samples, 0.01%)</title><rect x="341.5" y="293" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="344.47" y="303.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,916,319,705 samples, 0.02%)</title><rect x="1067.5" y="389" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1070.54" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (9,179,966,392 samples, 0.10%)</title><rect x="10.5" y="757" width="1.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="13.51" y="767.5" ></text>
</g>
<g >
<title>hash_bytes (3,835,043,577 samples, 0.04%)</title><rect x="861.6" y="405" width="0.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="864.62" y="415.5" ></text>
</g>
<g >
<title>fput (931,918,507 samples, 0.01%)</title><rect x="156.7" y="357" width="0.2" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="159.75" y="367.5" ></text>
</g>
<g >
<title>ScanKeyEntryInitialize (2,252,182,177 samples, 0.02%)</title><rect x="426.7" y="405" width="0.3" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="429.73" y="415.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,279,621,137 samples, 0.01%)</title><rect x="381.8" y="229" width="0.1" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="384.77" y="239.5" ></text>
</g>
<g >
<title>select_idle_sibling (839,381,015 samples, 0.01%)</title><rect x="488.1" y="261" width="0.1" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="491.08" y="271.5" ></text>
</g>
<g >
<title>preprocess_expression (975,983,726 samples, 0.01%)</title><rect x="126.7" y="501" width="0.1" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="129.71" y="511.5" ></text>
</g>
<g >
<title>MemoryContextTraverseNext (1,425,007,023 samples, 0.02%)</title><rect x="77.6" y="741" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="80.58" y="751.5" ></text>
</g>
<g >
<title>heap_prune_satisfies_vacuum (11,533,096,343 samples, 0.12%)</title><rect x="1146.4" y="725" width="1.5" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1149.44" y="735.5" ></text>
</g>
<g >
<title>heap_getattr (47,243,808,118 samples, 0.50%)</title><rect x="1002.4" y="245" width="5.9" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="1005.39" y="255.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="485" width="2.8" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="85.62" y="495.5" ></text>
</g>
<g >
<title>kmalloc_size_roundup (1,253,585,365 samples, 0.01%)</title><rect x="195.7" y="341" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="198.70" y="351.5" ></text>
</g>
<g >
<title>kmalloc_reserve (11,694,983,857 samples, 0.12%)</title><rect x="194.4" y="357" width="1.5" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="197.40" y="367.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,360,268,100 samples, 0.01%)</title><rect x="938.8" y="373" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="941.78" y="383.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,321,623,581 samples, 0.01%)</title><rect x="1034.5" y="133" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1037.50" y="143.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,337,615,432 samples, 0.04%)</title><rect x="390.8" y="341" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="393.76" y="351.5" ></text>
</g>
<g >
<title>ExecutePlan (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="581" width="6.7" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="97.67" y="591.5" ></text>
</g>
<g >
<title>XLogCheckBufferNeedsBackup (2,234,573,198 samples, 0.02%)</title><rect x="378.1" y="341" width="0.3" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="381.10" y="351.5" ></text>
</g>
<g >
<title>eval_const_expressions (13,902,874,045 samples, 0.15%)</title><rect x="1053.1" y="485" width="1.7" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1056.11" y="495.5" ></text>
</g>
<g >
<title>schedule_hrtimeout_range_clock (109,841,789,525 samples, 1.16%)</title><rect x="158.3" y="389" width="13.7" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="161.28" y="399.5" ></text>
</g>
<g >
<title>RegisterSnapshot (4,696,858,488 samples, 0.05%)</title><rect x="233.9" y="517" width="0.6" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="236.88" y="527.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (864,820,239 samples, 0.01%)</title><rect x="220.6" y="501" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="223.61" y="511.5" ></text>
</g>
<g >
<title>native_queued_spin_lock_slowpath (989,306,897 samples, 0.01%)</title><rect x="199.1" y="277" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="202.07" y="287.5" ></text>
</g>
<g >
<title>kmem_cache_alloc_node_noprof (13,247,311,896 samples, 0.14%)</title><rect x="195.9" y="357" width="1.6" height="15.0" fill="rgb(208,17,4)" rx="2" ry="2" />
<text  x="198.86" y="367.5" ></text>
</g>
<g >
<title>LockAcquireExtended (6,922,140,874 samples, 0.07%)</title><rect x="394.2" y="341" width="0.8" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="397.16" y="351.5" ></text>
</g>
<g >
<title>_bt_moveright (1,617,278,417 samples, 0.02%)</title><rect x="126.2" y="261" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="129.24" y="271.5" ></text>
</g>
<g >
<title>bms_add_member (2,498,163,941 samples, 0.03%)</title><rect x="968.4" y="325" width="0.3" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="971.37" y="335.5" ></text>
</g>
<g >
<title>table_open (1,178,368,685 samples, 0.01%)</title><rect x="1185.9" y="757" width="0.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1188.86" y="767.5" ></text>
</g>
<g >
<title>palloc0 (1,531,836,466 samples, 0.02%)</title><rect x="1059.8" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1062.78" y="447.5" ></text>
</g>
<g >
<title>equal (3,873,766,005 samples, 0.04%)</title><rect x="912.9" y="485" width="0.5" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="915.92" y="495.5" ></text>
</g>
<g >
<title>wake_up_q (1,004,809,256 samples, 0.01%)</title><rect x="354.7" y="117" width="0.1" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="357.71" y="127.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,032,666,917 samples, 0.01%)</title><rect x="964.6" y="373" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="967.59" y="383.5" ></text>
</g>
<g >
<title>hash_bytes (2,495,549,889 samples, 0.03%)</title><rect x="298.1" y="85" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="301.09" y="95.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,022,483,099 samples, 0.01%)</title><rect x="451.3" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="454.28" y="447.5" ></text>
</g>
<g >
<title>MemoryContextDelete (8,167,195,187 samples, 0.09%)</title><rect x="223.0" y="565" width="1.0" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="226.00" y="575.5" ></text>
</g>
<g >
<title>__vdso_gettimeofday (1,164,780,606 samples, 0.01%)</title><rect x="469.1" y="469" width="0.2" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="472.11" y="479.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,126,834,494 samples, 0.01%)</title><rect x="940.0" y="373" width="0.1" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="942.95" y="383.5" ></text>
</g>
<g >
<title>ExecClearTuple (1,434,294,997 samples, 0.02%)</title><rect x="279.0" y="373" width="0.2" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="281.99" y="383.5" ></text>
</g>
<g >
<title>distribute_qual_to_rels (132,873,652,192 samples, 1.41%)</title><rect x="958.6" y="421" width="16.7" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="961.64" y="431.5" ></text>
</g>
<g >
<title>subquery_planner (1,404,193,528 samples, 0.01%)</title><rect x="124.4" y="549" width="0.2" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="127.42" y="559.5" ></text>
</g>
<g >
<title>RelationClose (1,451,478,268 samples, 0.02%)</title><rect x="238.8" y="405" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="241.78" y="415.5" ></text>
</g>
<g >
<title>MemoryChunkGetBlock (45,971,469,220 samples, 0.49%)</title><rect x="13.2" y="741" width="5.8" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="16.21" y="751.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (925,284,563 samples, 0.01%)</title><rect x="324.0" y="213" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="327.01" y="223.5" ></text>
</g>
<g >
<title>SS_finalize_plan (48,086,592,362 samples, 0.51%)</title><rect x="875.9" y="517" width="6.0" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="878.93" y="527.5" ></text>
</g>
<g >
<title>FastPathUnGrantRelationLock (17,659,294,579 samples, 0.19%)</title><rect x="515.4" y="453" width="2.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="518.44" y="463.5" ></text>
</g>
<g >
<title>fdget (1,773,854,469 samples, 0.02%)</title><rect x="493.8" y="405" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="496.75" y="415.5" ></text>
</g>
<g >
<title>assign_collations_walker (2,717,191,405 samples, 0.03%)</title><rect x="803.1" y="341" width="0.4" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="806.11" y="351.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetRelationRef (812,137,539 samples, 0.01%)</title><rect x="940.0" y="357" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="942.99" y="367.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,032,208,190 samples, 0.01%)</title><rect x="948.3" y="277" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="951.27" y="287.5" ></text>
</g>
<g >
<title>palloc (2,255,067,098 samples, 0.02%)</title><rect x="1116.2" y="693" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1119.24" y="703.5" ></text>
</g>
<g >
<title>op_input_types (5,448,779,319 samples, 0.06%)</title><rect x="973.3" y="389" width="0.7" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="976.30" y="399.5" ></text>
</g>
<g >
<title>lappend_oid (2,474,978,153 samples, 0.03%)</title><rect x="910.8" y="469" width="0.3" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="913.79" y="479.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,360,727,340 samples, 0.01%)</title><rect x="934.2" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="937.24" y="351.5" ></text>
</g>
<g >
<title>avg_vruntime (1,393,734,010 samples, 0.01%)</title><rect x="169.6" y="261" width="0.2" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="172.61" y="271.5" ></text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (1,076,457,069 samples, 0.01%)</title><rect x="1174.6" y="757" width="0.1" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1177.59" y="767.5" ></text>
</g>
<g >
<title>PageGetHeapFreeSpace (838,173,200 samples, 0.01%)</title><rect x="80.0" y="757" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="82.97" y="767.5" ></text>
</g>
<g >
<title>get_attstatsslot (60,209,116,702 samples, 0.64%)</title><rect x="1001.8" y="293" width="7.5" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="1004.75" y="303.5" ></text>
</g>
<g >
<title>AllocSetAlloc (922,217,666 samples, 0.01%)</title><rect x="884.9" y="373" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="887.88" y="383.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (1,113,341,493 samples, 0.01%)</title><rect x="829.8" y="389" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="832.78" y="399.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,216,344,804 samples, 0.04%)</title><rect x="103.0" y="421" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="106.01" y="431.5" ></text>
</g>
<g >
<title>GrantLockLocal (1,365,826,106 samples, 0.01%)</title><rect x="353.7" y="277" width="0.2" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="356.74" y="287.5" ></text>
</g>
<g >
<title>VARATT_IS_EXTENDED (931,711,507 samples, 0.01%)</title><rect x="108.2" y="757" width="0.2" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="111.25" y="767.5" ></text>
</g>
<g >
<title>_bt_check_natts (18,004,565,380 samples, 0.19%)</title><rect x="317.3" y="229" width="2.2" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="320.29" y="239.5" ></text>
</g>
<g >
<title>GetSysCacheOid (39,537,016,462 samples, 0.42%)</title><rect x="822.6" y="373" width="4.9" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="825.58" y="383.5" ></text>
</g>
<g >
<title>prepare_task_switch (3,742,348,042 samples, 0.04%)</title><rect x="477.8" y="277" width="0.5" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="480.81" y="287.5" ></text>
</g>
<g >
<title>pfree (2,840,840,348 samples, 0.03%)</title><rect x="798.8" y="533" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="801.75" y="543.5" ></text>
</g>
<g >
<title>simplify_function (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="485" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="1161.36" y="495.5" ></text>
</g>
<g >
<title>BackendStartup (81,441,377,880 samples, 0.86%)</title><rect x="94.7" y="741" width="10.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="97.67" y="751.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,691,907,831 samples, 0.03%)</title><rect x="237.1" y="389" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="240.07" y="399.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (9,383,446,031 samples, 0.10%)</title><rect x="288.0" y="261" width="1.2" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="290.98" y="271.5" ></text>
</g>
<g >
<title>dispatch_compare_ptr (1,824,945,329 samples, 0.02%)</title><rect x="1131.6" y="757" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="1134.59" y="767.5" ></text>
</g>
<g >
<title>coerce_to_target_type (6,025,800,390 samples, 0.06%)</title><rect x="841.4" y="437" width="0.8" height="15.0" fill="rgb(232,124,29)" rx="2" ry="2" />
<text  x="844.43" y="447.5" ></text>
</g>
<g >
<title>uint32_hash (1,626,122,712 samples, 0.02%)</title><rect x="1013.6" y="245" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="1016.57" y="255.5" ></text>
</g>
<g >
<title>makeRangeVar (4,914,425,621 samples, 0.05%)</title><rect x="1118.2" y="741" width="0.6" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1121.20" y="751.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,500,019,112 samples, 0.02%)</title><rect x="932.3" y="309" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="935.34" y="319.5" ></text>
</g>
<g >
<title>table_block_relation_size (21,190,517,531 samples, 0.22%)</title><rect x="935.6" y="309" width="2.6" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="938.58" y="319.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (1,714,153,388 samples, 0.02%)</title><rect x="751.8" y="533" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="754.81" y="543.5" ></text>
</g>
<g >
<title>newNode (3,226,643,528 samples, 0.03%)</title><rect x="1120.0" y="709" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1122.98" y="719.5" ></text>
</g>
<g >
<title>HeapTupleHeaderGetRawXmax (891,152,564 samples, 0.01%)</title><rect x="51.0" y="757" width="0.1" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="54.02" y="767.5" ></text>
</g>
<g >
<title>create_modifytable_plan (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="549" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="88.40" y="559.5" ></text>
</g>
<g >
<title>bms_is_member (966,378,196 samples, 0.01%)</title><rect x="439.7" y="389" width="0.2" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="442.75" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,018,166,718 samples, 0.01%)</title><rect x="996.4" y="293" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="999.44" y="303.5" ></text>
</g>
<g >
<title>new_list (1,736,227,660 samples, 0.02%)</title><rect x="933.8" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="936.79" y="383.5" ></text>
</g>
<g >
<title>CreateDestReceiver (4,861,949,521 samples, 0.05%)</title><rect x="210.2" y="581" width="0.6" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="213.21" y="591.5" ></text>
</g>
<g >
<title>AllocSetDelete (75,351,168,362 samples, 0.80%)</title><rect x="252.7" y="453" width="9.4" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="255.68" y="463.5" ></text>
</g>
<g >
<title>wipe_mem (7,628,101,831 samples, 0.08%)</title><rect x="147.9" y="549" width="1.0" height="15.0" fill="rgb(216,51,12)" rx="2" ry="2" />
<text  x="150.92" y="559.5" ></text>
</g>
<g >
<title>heapam_index_fetch_end (2,222,630,430 samples, 0.02%)</title><rect x="246.5" y="389" width="0.3" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="249.51" y="399.5" ></text>
</g>
<g >
<title>hash_search (8,610,128,300 samples, 0.09%)</title><rect x="820.9" y="373" width="1.1" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="823.92" y="383.5" ></text>
</g>
<g >
<title>markRTEForSelectPriv (5,530,332,870 samples, 0.06%)</title><rect x="855.4" y="341" width="0.7" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="858.43" y="351.5" ></text>
</g>
<g >
<title>palloc (1,008,529,417 samples, 0.01%)</title><rect x="118.0" y="741" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="121.01" y="751.5" ></text>
</g>
<g >
<title>relation_open (14,773,218,659 samples, 0.16%)</title><rect x="440.1" y="405" width="1.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="443.10" y="415.5" ></text>
</g>
<g >
<title>_raw_spin_lock (811,735,690 samples, 0.01%)</title><rect x="354.7" y="53" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="357.71" y="63.5" ></text>
</g>
<g >
<title>add_rte_to_flat_rtable (18,292,388,459 samples, 0.19%)</title><rect x="895.8" y="485" width="2.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="898.84" y="495.5" ></text>
</g>
<g >
<title>AllocSetCheck (3,481,913,958 samples, 0.04%)</title><rect x="250.6" y="421" width="0.5" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="253.62" y="431.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (1,226,655,887 samples, 0.01%)</title><rect x="132.5" y="501" width="0.2" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="135.53" y="511.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,282,097,328 samples, 0.03%)</title><rect x="916.6" y="357" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="919.64" y="367.5" ></text>
</g>
<g >
<title>palloc0 (3,184,248,718 samples, 0.03%)</title><rect x="269.9" y="357" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="272.94" y="367.5" ></text>
</g>
<g >
<title>_bt_binsrch (6,445,443,762 samples, 0.07%)</title><rect x="125.2" y="261" width="0.8" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="128.19" y="271.5" ></text>
</g>
<g >
<title>do_futex (925,058,534 samples, 0.01%)</title><rect x="296.8" y="133" width="0.2" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="299.85" y="143.5" ></text>
</g>
<g >
<title>ExecInterpExprStillValid (1,019,356,492 samples, 0.01%)</title><rect x="44.5" y="757" width="0.1" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="47.48" y="767.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,418,667,084 samples, 0.02%)</title><rect x="354.7" y="165" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="357.65" y="175.5" ></text>
</g>
<g >
<title>MarkPortalDone (1,682,724,631 samples, 0.02%)</title><rect x="228.0" y="565" width="0.3" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="231.05" y="575.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (36,330,518,537 samples, 0.38%)</title><rect x="484.6" y="469" width="4.5" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="487.60" y="479.5" ></text>
</g>
<g >
<title>new_list (1,674,434,305 samples, 0.02%)</title><rect x="415.1" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="418.13" y="367.5" ></text>
</g>
<g >
<title>assign_collations_walker (24,563,385,406 samples, 0.26%)</title><rect x="805.4" y="373" width="3.0" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="808.37" y="383.5" ></text>
</g>
<g >
<title>do_syscall_64 (2,114,434,518 samples, 0.02%)</title><rect x="363.7" y="245" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="366.66" y="255.5" ></text>
</g>
<g >
<title>get_relids_in_jointree (4,518,467,576 samples, 0.05%)</title><rect x="1070.0" y="485" width="0.6" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="1073.01" y="495.5" ></text>
</g>
<g >
<title>ReadBufferExtended (18,083,246,842 samples, 0.19%)</title><rect x="99.0" y="277" width="2.3" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="102.04" y="287.5" ></text>
</g>
<g >
<title>_bt_check_natts (2,294,973,762 samples, 0.02%)</title><rect x="127.5" y="757" width="0.3" height="15.0" fill="rgb(216,54,12)" rx="2" ry="2" />
<text  x="130.50" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,071,993,106 samples, 0.01%)</title><rect x="1070.4" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1073.44" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,223,770,746 samples, 0.01%)</title><rect x="808.7" y="437" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="811.69" y="447.5" ></text>
</g>
<g >
<title>ExecScan (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="437" width="0.5" height="15.0" fill="rgb(237,150,36)" rx="2" ry="2" />
<text  x="1160.94" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,419,698,004 samples, 0.03%)</title><rect x="421.2" y="309" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="424.20" y="319.5" ></text>
</g>
<g >
<title>SearchCatCache1 (2,701,915,710 samples, 0.03%)</title><rect x="1046.2" y="405" width="0.3" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1049.15" y="415.5" ></text>
</g>
<g >
<title>scanner_init (1,516,220,360 samples, 0.02%)</title><rect x="1176.7" y="757" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1179.74" y="767.5" ></text>
</g>
<g >
<title>palloc (4,332,857,497 samples, 0.05%)</title><rect x="369.7" y="325" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="372.67" y="335.5" ></text>
</g>
<g >
<title>ExecInitIndexScan (230,908,948,384 samples, 2.45%)</title><rect x="413.9" y="437" width="28.9" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="416.94" y="447.5" >Ex..</text>
</g>
<g >
<title>FunctionCall2Coll (3,721,206,049 samples, 0.04%)</title><rect x="331.6" y="213" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="334.58" y="223.5" ></text>
</g>
<g >
<title>AtStart_GUC (895,708,057 samples, 0.01%)</title><rect x="33.2" y="757" width="0.1" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="36.15" y="767.5" ></text>
</g>
<g >
<title>fmgr_info (3,991,174,987 samples, 0.04%)</title><rect x="420.2" y="325" width="0.5" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="423.21" y="335.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,195,694,786 samples, 0.01%)</title><rect x="1034.5" y="117" width="0.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1037.51" y="127.5" ></text>
</g>
<g >
<title>_int_free_create_chunk (892,490,724 samples, 0.01%)</title><rect x="261.4" y="373" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="264.44" y="383.5" ></text>
</g>
<g >
<title>fmgr_isbuiltin (1,362,303,880 samples, 0.01%)</title><rect x="1037.2" y="277" width="0.2" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1040.21" y="287.5" ></text>
</g>
<g >
<title>nocachegetattr (7,155,631,495 samples, 0.08%)</title><rect x="826.6" y="325" width="0.9" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="829.62" y="335.5" ></text>
</g>
<g >
<title>fetch_upper_rel (1,226,832,055 samples, 0.01%)</title><rect x="906.6" y="501" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="909.61" y="511.5" ></text>
</g>
<g >
<title>OidFunctionCall4Coll (1,285,361,396 samples, 0.01%)</title><rect x="79.5" y="757" width="0.1" height="15.0" fill="rgb(229,113,27)" rx="2" ry="2" />
<text  x="82.47" y="767.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (1,596,644,385 samples, 0.02%)</title><rect x="1064.9" y="421" width="0.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1067.88" y="431.5" ></text>
</g>
<g >
<title>CreateQueryDesc (8,797,330,177 samples, 0.09%)</title><rect x="233.7" y="533" width="1.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="236.65" y="543.5" ></text>
</g>
<g >
<title>heapam_index_fetch_reset (1,097,845,855 samples, 0.01%)</title><rect x="341.8" y="293" width="0.1" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="344.78" y="303.5" ></text>
</g>
<g >
<title>pg_class_aclmask_ext (1,005,775,157 samples, 0.01%)</title><rect x="1168.1" y="757" width="0.1" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="1171.11" y="767.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (9,444,224,198 samples, 0.10%)</title><rect x="273.0" y="405" width="1.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="275.96" y="415.5" ></text>
</g>
<g >
<title>make_one_rel (482,777,517,261 samples, 5.12%)</title><rect x="982.6" y="469" width="60.4" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="985.61" y="479.5" >make_o..</text>
</g>
<g >
<title>pg_class_aclmask_ext (6,604,130,734 samples, 0.07%)</title><rect x="1034.4" y="165" width="0.8" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="1037.40" y="175.5" ></text>
</g>
<g >
<title>palloc0 (2,964,284,389 samples, 0.03%)</title><rect x="976.2" y="389" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="979.22" y="399.5" ></text>
</g>
<g >
<title>get_hash_entry (1,231,651,313 samples, 0.01%)</title><rect x="821.4" y="341" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="824.35" y="351.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVacuumHorizon (10,946,470,478 samples, 0.12%)</title><rect x="1146.5" y="709" width="1.4" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="1149.52" y="719.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (804,332,464 samples, 0.01%)</title><rect x="1053.0" y="341" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1055.98" y="351.5" ></text>
</g>
<g >
<title>BufferGetPage (1,005,017,083 samples, 0.01%)</title><rect x="378.2" y="325" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="381.18" y="335.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (1,041,756,913 samples, 0.01%)</title><rect x="941.8" y="309" width="0.2" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="944.85" y="319.5" ></text>
</g>
<g >
<title>fmgr_info (869,310,140 samples, 0.01%)</title><rect x="1138.8" y="757" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="1141.78" y="767.5" ></text>
</g>
<g >
<title>SearchCatCache1 (3,173,949,865 samples, 0.03%)</title><rect x="1037.6" y="293" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="1040.61" y="303.5" ></text>
</g>
<g >
<title>gup_fast_pte_range (3,941,406,536 samples, 0.04%)</title><rect x="482.9" y="245" width="0.5" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="485.92" y="255.5" ></text>
</g>
<g >
<title>StartReadBuffer (1,217,297,839 samples, 0.01%)</title><rect x="124.1" y="197" width="0.1" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="127.08" y="207.5" ></text>
</g>
<g >
<title>lnext (809,730,767 samples, 0.01%)</title><rect x="454.5" y="549" width="0.1" height="15.0" fill="rgb(228,107,25)" rx="2" ry="2" />
<text  x="457.48" y="559.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,434,334,585 samples, 0.02%)</title><rect x="991.1" y="293" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="994.05" y="303.5" ></text>
</g>
<g >
<title>bms_copy (1,960,766,087 samples, 0.02%)</title><rect x="966.5" y="357" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="969.48" y="367.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (935,801,654 samples, 0.01%)</title><rect x="1056.9" y="373" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1059.94" y="383.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (1,393,708,179 samples, 0.01%)</title><rect x="122.5" y="293" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="125.45" y="303.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,516,998,230 samples, 0.02%)</title><rect x="811.0" y="405" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="814.01" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,202,010,655 samples, 0.01%)</title><rect x="942.4" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="945.37" y="335.5" ></text>
</g>
<g >
<title>new_list (1,331,232,999 samples, 0.01%)</title><rect x="811.7" y="421" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="814.73" y="431.5" ></text>
</g>
<g >
<title>ep_poll (141,002,399,968 samples, 1.49%)</title><rect x="154.4" y="405" width="17.6" height="15.0" fill="rgb(238,151,36)" rx="2" ry="2" />
<text  x="157.38" y="415.5" ></text>
</g>
<g >
<title>AtEOXact_GUC (1,655,999,464 samples, 0.02%)</title><rect x="461.7" y="517" width="0.2" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="464.70" y="527.5" ></text>
</g>
<g >
<title>bms_is_member (1,224,220,433 samples, 0.01%)</title><rect x="449.0" y="453" width="0.1" height="15.0" fill="rgb(252,217,51)" rx="2" ry="2" />
<text  x="451.99" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (968,120,258 samples, 0.01%)</title><rect x="858.6" y="469" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="861.59" y="479.5" ></text>
</g>
<g >
<title>VirtualXactLockTableInsert (6,486,498,325 samples, 0.07%)</title><rect x="1077.8" y="533" width="0.8" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="1080.81" y="543.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,255,639,674 samples, 0.01%)</title><rect x="1010.1" y="229" width="0.2" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1013.09" y="239.5" ></text>
</g>
<g >
<title>CatalogCacheCompareTuple (1,093,830,555 samples, 0.01%)</title><rect x="1011.1" y="245" width="0.2" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="1014.14" y="255.5" ></text>
</g>
<g >
<title>assign_special_exec_param (3,048,605,943 samples, 0.03%)</title><rect x="910.7" y="485" width="0.4" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="913.74" y="495.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (2,236,571,662 samples, 0.02%)</title><rect x="363.7" y="277" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="366.65" y="287.5" ></text>
</g>
<g >
<title>MultiXactIdSetOldestMember (6,019,505,715 samples, 0.06%)</title><rect x="365.0" y="357" width="0.7" height="15.0" fill="rgb(220,73,17)" rx="2" ry="2" />
<text  x="367.96" y="367.5" ></text>
</g>
<g >
<title>exec_simple_query (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="661" width="3.0" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="85.62" y="671.5" ></text>
</g>
<g >
<title>MakeTupleTableSlot (4,464,836,356 samples, 0.05%)</title><rect x="424.8" y="357" width="0.5" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="427.76" y="367.5" ></text>
</g>
<g >
<title>query_or_expression_tree_walker_impl (1,234,888,131 samples, 0.01%)</title><rect x="1036.2" y="213" width="0.2" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="1039.21" y="223.5" ></text>
</g>
<g >
<title>AtEOXact_Files (1,061,367,221 samples, 0.01%)</title><rect x="461.6" y="517" width="0.1" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="464.57" y="527.5" ></text>
</g>
<g >
<title>bms_is_subset (980,688,390 samples, 0.01%)</title><rect x="879.2" y="469" width="0.2" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="882.25" y="479.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,017,045,942 samples, 0.01%)</title><rect x="1038.7" y="325" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1041.69" y="335.5" ></text>
</g>
<g >
<title>__irq_exit_rcu (960,397,057 samples, 0.01%)</title><rect x="791.2" y="485" width="0.1" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="794.16" y="495.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="677" width="0.3" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1187.99" y="687.5" ></text>
</g>
<g >
<title>skb_release_data (9,090,453,179 samples, 0.10%)</title><rect x="178.9" y="357" width="1.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="181.93" y="367.5" ></text>
</g>
<g >
<title>palloc (1,119,282,699 samples, 0.01%)</title><rect x="104.4" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="107.42" y="415.5" ></text>
</g>
<g >
<title>ExecInitExprRec (860,829,043 samples, 0.01%)</title><rect x="43.2" y="757" width="0.1" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="46.15" y="767.5" ></text>
</g>
<g >
<title>mdnblocks (8,875,171,583 samples, 0.09%)</title><rect x="932.1" y="373" width="1.1" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="935.08" y="383.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVisibility (34,915,667,061 samples, 0.37%)</title><rect x="303.9" y="245" width="4.4" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="306.91" y="255.5" ></text>
</g>
<g >
<title>examine_variable (1,174,540,159 samples, 0.01%)</title><rect x="1134.5" y="757" width="0.1" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="1137.47" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (977,226,607 samples, 0.01%)</title><rect x="813.1" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="816.14" y="431.5" ></text>
</g>
<g >
<title>bms_make_singleton (3,891,133,804 samples, 0.04%)</title><rect x="976.1" y="405" width="0.5" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="979.10" y="415.5" ></text>
</g>
<g >
<title>AllocSetFree (958,694,277 samples, 0.01%)</title><rect x="944.6" y="357" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="947.62" y="367.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,607,980,346 samples, 0.04%)</title><rect x="827.0" y="293" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="829.99" y="303.5" ></text>
</g>
<g >
<title>TupleDescAttr (890,177,052 samples, 0.01%)</title><rect x="320.3" y="181" width="0.2" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="323.34" y="191.5" ></text>
</g>
<g >
<title>__irq_exit_rcu (1,625,320,832 samples, 0.02%)</title><rect x="750.9" y="501" width="0.2" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="753.87" y="511.5" ></text>
</g>
<g >
<title>RelationClose (1,338,501,413 samples, 0.01%)</title><rect x="236.2" y="437" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="239.17" y="447.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,181,827,449 samples, 0.01%)</title><rect x="306.1" y="165" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="309.09" y="175.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (2,196,687,430 samples, 0.02%)</title><rect x="1041.7" y="309" width="0.3" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1044.68" y="319.5" ></text>
</g>
<g >
<title>get_relname_relid (843,644,661 samples, 0.01%)</title><rect x="1140.5" y="757" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1143.48" y="767.5" ></text>
</g>
<g >
<title>palloc (1,441,077,346 samples, 0.02%)</title><rect x="1013.1" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1016.10" y="255.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,025,796,860 samples, 0.01%)</title><rect x="359.9" y="309" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="362.94" y="319.5" ></text>
</g>
<g >
<title>lappend_int (2,846,315,437 samples, 0.03%)</title><rect x="922.4" y="453" width="0.4" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="925.42" y="463.5" ></text>
</g>
<g >
<title>RelnameGetRelid (43,570,456,292 samples, 0.46%)</title><rect x="822.3" y="405" width="5.4" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="825.26" y="415.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,504,394,184 samples, 0.03%)</title><rect x="85.1" y="181" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="88.08" y="191.5" ></text>
</g>
<g >
<title>slot_deform_heap_tuple_internal (2,668,417,107 samples, 0.03%)</title><rect x="344.0" y="309" width="0.3" height="15.0" fill="rgb(221,77,18)" rx="2" ry="2" />
<text  x="346.98" y="319.5" ></text>
</g>
<g >
<title>stack_is_too_deep (3,433,709,608 samples, 0.04%)</title><rect x="1127.3" y="741" width="0.5" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1130.32" y="751.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (857,285,167 samples, 0.01%)</title><rect x="94.7" y="181" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="97.67" y="191.5" ></text>
</g>
<g >
<title>scanNSItemForColumn (10,259,710,974 samples, 0.11%)</title><rect x="839.5" y="341" width="1.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="842.51" y="351.5" ></text>
</g>
<g >
<title>__errno_location (2,233,070,645 samples, 0.02%)</title><rect x="188.7" y="501" width="0.3" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="191.69" y="511.5" ></text>
</g>
<g >
<title>RecoveryInProgress (1,464,803,378 samples, 0.02%)</title><rect x="87.9" y="757" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="90.86" y="767.5" ></text>
</g>
<g >
<title>lcons (2,325,099,749 samples, 0.02%)</title><rect x="1071.6" y="485" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1074.63" y="495.5" ></text>
</g>
<g >
<title>newNode (8,363,906,470 samples, 0.09%)</title><rect x="450.2" y="453" width="1.0" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="453.19" y="463.5" ></text>
</g>
<g >
<title>pg_any_to_server (10,751,057,806 samples, 0.11%)</title><rect x="1081.7" y="565" width="1.4" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="1084.74" y="575.5" ></text>
</g>
<g >
<title>pgstat_count_io_op (2,258,338,093 samples, 0.02%)</title><rect x="300.5" y="149" width="0.3" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="303.52" y="159.5" ></text>
</g>
<g >
<title>list_length (10,499,714,305 samples, 0.11%)</title><rect x="1155.0" y="757" width="1.4" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="1158.04" y="767.5" ></text>
</g>
<g >
<title>verify_compact_attribute (3,093,823,231 samples, 0.03%)</title><rect x="361.0" y="293" width="0.4" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="364.00" y="303.5" ></text>
</g>
<g >
<title>restore_fpregs_from_fpstate (4,662,485,439 samples, 0.05%)</title><rect x="172.5" y="405" width="0.6" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="175.47" y="415.5" ></text>
</g>
<g >
<title>palloc (1,661,973,288 samples, 0.02%)</title><rect x="1058.2" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1061.19" y="431.5" ></text>
</g>
<g >
<title>LWLockWaitForVar (9,721,806,392 samples, 0.10%)</title><rect x="489.6" y="469" width="1.2" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="492.58" y="479.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (6,337,892,178 samples, 0.07%)</title><rect x="1039.5" y="325" width="0.7" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1042.45" y="335.5" ></text>
</g>
<g >
<title>newNode (2,702,472,380 samples, 0.03%)</title><rect x="1021.1" y="309" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1024.09" y="319.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (1,181,126,048 samples, 0.01%)</title><rect x="1114.1" y="645" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1117.06" y="655.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (4,756,027,121 samples, 0.05%)</title><rect x="862.6" y="485" width="0.6" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="865.64" y="495.5" ></text>
</g>
<g >
<title>hash_create (41,025,570,845 samples, 0.43%)</title><rect x="1060.0" y="469" width="5.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="1062.98" y="479.5" ></text>
</g>
<g >
<title>is_parallel_safe (20,579,529,320 samples, 0.22%)</title><rect x="915.2" y="485" width="2.6" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="918.18" y="495.5" ></text>
</g>
<g >
<title>DynaHashAlloc (1,823,079,270 samples, 0.02%)</title><rect x="1066.5" y="405" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1069.49" y="415.5" ></text>
</g>
<g >
<title>LWLockRelease (1,471,356,822 samples, 0.02%)</title><rect x="941.8" y="341" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="944.79" y="351.5" ></text>
</g>
<g >
<title>index_insert_cleanup (1,570,895,061 samples, 0.02%)</title><rect x="237.8" y="453" width="0.1" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="240.75" y="463.5" ></text>
</g>
<g >
<title>replace_nestloop_params_mutator (1,732,520,149 samples, 0.02%)</title><rect x="890.3" y="277" width="0.2" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="893.27" y="287.5" ></text>
</g>
<g >
<title>hdefault (11,155,715,940 samples, 0.12%)</title><rect x="1062.3" y="453" width="1.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="1065.34" y="463.5" ></text>
</g>
<g >
<title>BufTableHashCode (4,256,920,520 samples, 0.05%)</title><rect x="94.9" y="181" width="0.5" height="15.0" fill="rgb(215,47,11)" rx="2" ry="2" />
<text  x="97.92" y="191.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (1,206,044,775 samples, 0.01%)</title><rect x="441.7" y="341" width="0.2" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="444.75" y="351.5" ></text>
</g>
<g >
<title>ExecBuildProjectionInfo (70,055,333,139 samples, 0.74%)</title><rect x="415.9" y="373" width="8.7" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="418.86" y="383.5" ></text>
</g>
<g >
<title>pg_plan_queries (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="661" width="3.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="104.45" y="671.5" ></text>
</g>
<g >
<title>palloc (1,100,323,812 samples, 0.01%)</title><rect x="908.7" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="911.71" y="431.5" ></text>
</g>
<g >
<title>set_ps_display_with_len (5,072,531,273 samples, 0.05%)</title><rect x="1083.3" y="581" width="0.6" height="15.0" fill="rgb(222,79,18)" rx="2" ry="2" />
<text  x="1086.28" y="591.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,506,029,425 samples, 0.02%)</title><rect x="407.2" y="421" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="410.25" y="431.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,005,568,923 samples, 0.01%)</title><rect x="210.7" y="533" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="213.69" y="543.5" ></text>
</g>
<g >
<title>transformExprRecurse (53,813,604,045 samples, 0.57%)</title><rect x="834.1" y="421" width="6.7" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="837.11" y="431.5" ></text>
</g>
<g >
<title>newNode (4,496,080,340 samples, 0.05%)</title><rect x="1121.1" y="741" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1124.07" y="751.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (5,504,863,843 samples, 0.06%)</title><rect x="482.7" y="261" width="0.7" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="485.72" y="271.5" ></text>
</g>
<g >
<title>ExecTypeFromTLInternal (36,342,002,276 samples, 0.39%)</title><rect x="433.8" y="389" width="4.5" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="436.75" y="399.5" ></text>
</g>
<g >
<title>LWLockRelease (1,385,036,439 samples, 0.01%)</title><rect x="522.7" y="437" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="525.73" y="447.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (2,255,619,491 samples, 0.02%)</title><rect x="220.1" y="533" width="0.3" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="223.07" y="543.5" ></text>
</g>
<g >
<title>ksys_lseek (3,234,695,302 samples, 0.03%)</title><rect x="932.7" y="277" width="0.4" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="935.69" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,591,135,079 samples, 0.04%)</title><rect x="214.4" y="549" width="0.4" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="217.36" y="559.5" ></text>
</g>
<g >
<title>__intel_pmu_enable_all.isra.0 (1,056,607,369 samples, 0.01%)</title><rect x="477.5" y="229" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="480.53" y="239.5" ></text>
</g>
<g >
<title>palloc0 (1,612,231,302 samples, 0.02%)</title><rect x="897.1" y="437" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="900.05" y="447.5" ></text>
</g>
<g >
<title>palloc (1,251,059,193 samples, 0.01%)</title><rect x="434.3" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="437.27" y="367.5" ></text>
</g>
<g >
<title>BufferGetBlock (926,439,335 samples, 0.01%)</title><rect x="335.8" y="181" width="0.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="338.76" y="191.5" ></text>
</g>
<g >
<title>copyObjectImpl (4,311,438,438 samples, 0.05%)</title><rect x="888.1" y="341" width="0.5" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="891.09" y="351.5" ></text>
</g>
<g >
<title>[postgres] (3,936,690,682 samples, 0.04%)</title><rect x="110.5" y="757" width="0.5" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="113.53" y="767.5" ></text>
</g>
<g >
<title>hash_bytes (2,813,490,183 samples, 0.03%)</title><rect x="821.6" y="341" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="824.64" y="351.5" ></text>
</g>
<g >
<title>check_mergejoinable (46,769,076,623 samples, 0.50%)</title><rect x="959.5" y="405" width="5.8" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="962.49" y="415.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (31,289,589,272 samples, 0.33%)</title><rect x="484.9" y="437" width="3.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="487.91" y="447.5" ></text>
</g>
<g >
<title>ExecUpdate (999,289,278 samples, 0.01%)</title><rect x="46.0" y="757" width="0.2" height="15.0" fill="rgb(253,223,53)" rx="2" ry="2" />
<text  x="49.04" y="767.5" ></text>
</g>
<g >
<title>ExecScanExtended (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="437" width="2.8" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="85.62" y="447.5" ></text>
</g>
<g >
<title>palloc0 (1,903,110,252 samples, 0.02%)</title><rect x="391.4" y="357" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="394.40" y="367.5" ></text>
</g>
<g >
<title>palloc0 (2,439,698,391 samples, 0.03%)</title><rect x="811.9" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="814.94" y="431.5" ></text>
</g>
<g >
<title>transformTargetEntry (58,327,368,592 samples, 0.62%)</title><rect x="833.5" y="453" width="7.3" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="836.55" y="463.5" ></text>
</g>
<g >
<title>palloc0 (2,484,395,539 samples, 0.03%)</title><rect x="102.1" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="105.06" y="463.5" ></text>
</g>
<g >
<title>planner (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="597" width="0.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1161.36" y="607.5" ></text>
</g>
<g >
<title>finish_task_switch.isra.0 (8,742,858,036 samples, 0.09%)</title><rect x="161.2" y="341" width="1.0" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="164.15" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (914,549,661 samples, 0.01%)</title><rect x="954.1" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="957.06" y="399.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,838,065,923 samples, 0.02%)</title><rect x="867.4" y="437" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="870.38" y="447.5" ></text>
</g>
<g >
<title>expr_setup_walker (874,090,689 samples, 0.01%)</title><rect x="430.9" y="309" width="0.1" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="433.89" y="319.5" ></text>
</g>
<g >
<title>CopySnapshot (3,701,515,912 samples, 0.04%)</title><rect x="453.6" y="533" width="0.5" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="456.62" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,152,214,287 samples, 0.01%)</title><rect x="415.2" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="418.18" y="335.5" ></text>
</g>
<g >
<title>PageGetItemId (889,307,227 samples, 0.01%)</title><rect x="366.1" y="341" width="0.1" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="369.07" y="351.5" ></text>
</g>
<g >
<title>new_list (1,208,373,140 samples, 0.01%)</title><rect x="835.5" y="357" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="838.48" y="367.5" ></text>
</g>
<g >
<title>lappend (5,410,175,796 samples, 0.06%)</title><rect x="1017.9" y="357" width="0.7" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="1020.91" y="367.5" ></text>
</g>
<g >
<title>exprType (1,653,173,154 samples, 0.02%)</title><rect x="1134.9" y="757" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="1137.93" y="767.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (3,689,814,070 samples, 0.04%)</title><rect x="351.9" y="229" width="0.5" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="354.91" y="239.5" ></text>
</g>
<g >
<title>LockBuffer (3,296,273,306 samples, 0.03%)</title><rect x="336.3" y="197" width="0.4" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="339.25" y="207.5" ></text>
</g>
<g >
<title>pq_recvbuf (280,886,205,764 samples, 2.98%)</title><rect x="149.7" y="549" width="35.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="152.73" y="559.5" >pq..</text>
</g>
<g >
<title>pg_leftmost_one_pos32 (1,785,216,773 samples, 0.02%)</title><rect x="119.1" y="741" width="0.2" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="122.07" y="751.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (961,419,103 samples, 0.01%)</title><rect x="401.6" y="277" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="404.60" y="287.5" ></text>
</g>
<g >
<title>TupleDescAttr (2,484,255,274 samples, 0.03%)</title><rect x="1005.1" y="165" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1008.07" y="175.5" ></text>
</g>
<g >
<title>bms_make_singleton (4,356,209,003 samples, 0.05%)</title><rect x="905.9" y="501" width="0.6" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="908.93" y="511.5" ></text>
</g>
<g >
<title>heap_fetch (1,100,636,289 samples, 0.01%)</title><rect x="1144.7" y="757" width="0.1" height="15.0" fill="rgb(253,222,53)" rx="2" ry="2" />
<text  x="1147.71" y="767.5" ></text>
</g>
<g >
<title>make_const (6,288,735,208 samples, 0.07%)</title><rect x="853.1" y="405" width="0.8" height="15.0" fill="rgb(216,50,12)" rx="2" ry="2" />
<text  x="856.09" y="415.5" ></text>
</g>
<g >
<title>btgettuple (234,028,705,296 samples, 2.48%)</title><rect x="311.5" y="293" width="29.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="314.51" y="303.5" >bt..</text>
</g>
<g >
<title>AllocSetAlloc (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="293" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="88.40" y="303.5" ></text>
</g>
<g >
<title>palloc0 (2,681,426,892 samples, 0.03%)</title><rect x="909.8" y="437" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="912.78" y="447.5" ></text>
</g>
<g >
<title>string_hash (3,165,199,763 samples, 0.03%)</title><rect x="226.9" y="549" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="229.86" y="559.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (9,618,278,310 samples, 0.10%)</title><rect x="880.6" y="421" width="1.2" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="883.58" y="431.5" ></text>
</g>
<g >
<title>BufferGetPage (1,254,151,724 samples, 0.01%)</title><rect x="302.1" y="245" width="0.2" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="305.10" y="255.5" ></text>
</g>
<g >
<title>pick_next_task_fair (1,896,845,424 samples, 0.02%)</title><rect x="206.5" y="389" width="0.2" height="15.0" fill="rgb(242,170,40)" rx="2" ry="2" />
<text  x="209.48" y="399.5" ></text>
</g>
<g >
<title>ExecEndModifyTable (69,721,116,207 samples, 0.74%)</title><rect x="238.1" y="469" width="8.8" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="241.13" y="479.5" ></text>
</g>
<g >
<title>hash_search (6,016,660,283 samples, 0.06%)</title><rect x="1067.5" y="405" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1070.51" y="415.5" ></text>
</g>
<g >
<title>list_delete_cell (4,387,246,543 samples, 0.05%)</title><rect x="251.6" y="453" width="0.5" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="254.57" y="463.5" ></text>
</g>
<g >
<title>x64_sys_call (1,438,734,394 samples, 0.02%)</title><rect x="174.3" y="437" width="0.2" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="177.30" y="447.5" ></text>
</g>
<g >
<title>palloc (3,800,294,745 samples, 0.04%)</title><rect x="290.8" y="261" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="293.81" y="271.5" ></text>
</g>
<g >
<title>ExtendSUBTRANS (925,739,288 samples, 0.01%)</title><rect x="349.4" y="309" width="0.1" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="352.43" y="319.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,148,972,874 samples, 0.01%)</title><rect x="238.8" y="389" width="0.2" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="241.82" y="399.5" ></text>
</g>
<g >
<title>SearchCatCache1 (5,746,488,329 samples, 0.06%)</title><rect x="807.7" y="325" width="0.7" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="810.65" y="335.5" ></text>
</g>
<g >
<title>pg_atomic_fetch_sub_u32_impl (858,149,740 samples, 0.01%)</title><rect x="221.3" y="485" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="224.26" y="495.5" ></text>
</g>
<g >
<title>bms_union (4,453,119,567 samples, 0.05%)</title><rect x="879.4" y="469" width="0.5" height="15.0" fill="rgb(247,195,46)" rx="2" ry="2" />
<text  x="882.38" y="479.5" ></text>
</g>
<g >
<title>RelationIncrementReferenceCount (874,682,629 samples, 0.01%)</title><rect x="943.4" y="357" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="946.41" y="367.5" ></text>
</g>
<g >
<title>apply_tlist_labeling (4,764,056,328 samples, 0.05%)</title><rect x="883.2" y="469" width="0.6" height="15.0" fill="rgb(253,225,53)" rx="2" ry="2" />
<text  x="886.20" y="479.5" ></text>
</g>
<g >
<title>evaluate_function (2,575,551,450 samples, 0.03%)</title><rect x="103.6" y="453" width="0.3" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="106.55" y="463.5" ></text>
</g>
<g >
<title>list_nth_cell (823,637,945 samples, 0.01%)</title><rect x="262.2" y="485" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="265.24" y="495.5" ></text>
</g>
<g >
<title>list_free_private (2,037,089,524 samples, 0.02%)</title><rect x="944.5" y="389" width="0.3" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="947.51" y="399.5" ></text>
</g>
<g >
<title>ScanKeyEntryInitializeWithInfo (1,345,467,311 samples, 0.01%)</title><rect x="313.7" y="261" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="316.74" y="271.5" ></text>
</g>
<g >
<title>printtup_destroy (2,550,622,642 samples, 0.03%)</title><rect x="1073.3" y="581" width="0.3" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="1076.26" y="591.5" ></text>
</g>
<g >
<title>MemoryContextStrdup (2,953,690,311 samples, 0.03%)</title><rect x="816.0" y="421" width="0.4" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="819.03" y="431.5" ></text>
</g>
<g >
<title>DatumGetPointer (1,223,835,236 samples, 0.01%)</title><rect x="40.6" y="757" width="0.2" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="43.61" y="767.5" ></text>
</g>
<g >
<title>do_futex (851,906,660 samples, 0.01%)</title><rect x="467.7" y="373" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="470.67" y="383.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,122,461,868 samples, 0.02%)</title><rect x="868.5" y="453" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="871.45" y="463.5" ></text>
</g>
<g >
<title>pg_class_aclcheck (880,198,309 samples, 0.01%)</title><rect x="1167.9" y="757" width="0.1" height="15.0" fill="rgb(239,158,37)" rx="2" ry="2" />
<text  x="1170.88" y="767.5" ></text>
</g>
<g >
<title>newNode (3,897,031,868 samples, 0.04%)</title><rect x="1118.3" y="725" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1121.33" y="735.5" ></text>
</g>
<g >
<title>dequeue_task_fair (19,261,683,145 samples, 0.20%)</title><rect x="479.3" y="261" width="2.4" height="15.0" fill="rgb(230,119,28)" rx="2" ry="2" />
<text  x="482.27" y="271.5" ></text>
</g>
<g >
<title>palloc (3,839,613,006 samples, 0.04%)</title><rect x="918.3" y="453" width="0.5" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="921.31" y="463.5" ></text>
</g>
<g >
<title>ReleaseCatCache (900,095,974 samples, 0.01%)</title><rect x="1009.4" y="261" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1012.45" y="271.5" ></text>
</g>
<g >
<title>new_list (2,014,667,349 samples, 0.02%)</title><rect x="1015.5" y="293" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1018.48" y="303.5" ></text>
</g>
<g >
<title>BackendStartup (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="661" width="953.9" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="133.07" y="671.5" >BackendStartup</text>
</g>
<g >
<title>resetStringInfo (1,106,757,066 samples, 0.01%)</title><rect x="186.1" y="549" width="0.2" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="189.12" y="559.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32 (889,502,709 samples, 0.01%)</title><rect x="522.8" y="405" width="0.1" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="525.80" y="415.5" ></text>
</g>
<g >
<title>futex_wait (60,114,843,274 samples, 0.64%)</title><rect x="475.9" y="357" width="7.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="478.90" y="367.5" ></text>
</g>
<g >
<title>palloc0 (2,178,242,736 samples, 0.02%)</title><rect x="839.9" y="293" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="842.95" y="303.5" ></text>
</g>
<g >
<title>extract_restriction_or_clauses (953,725,476 samples, 0.01%)</title><rect x="1136.6" y="757" width="0.1" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="1139.57" y="767.5" ></text>
</g>
<g >
<title>RewriteQuery (46,917,333,459 samples, 0.50%)</title><rect x="857.4" y="533" width="5.9" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="860.40" y="543.5" ></text>
</g>
<g >
<title>heap_page_prune_and_freeze (20,092,989,740 samples, 0.21%)</title><rect x="1145.4" y="757" width="2.5" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="1148.38" y="767.5" ></text>
</g>
<g >
<title>ExecStoreBufferHeapTuple (8,398,513,279 samples, 0.09%)</title><rect x="294.9" y="261" width="1.0" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="297.88" y="271.5" ></text>
</g>
<g >
<title>add_base_rels_to_query (189,863,408,076 samples, 2.01%)</title><rect x="926.6" y="453" width="23.7" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="929.55" y="463.5" >a..</text>
</g>
<g >
<title>MemoryChunkIsExternal (47,835,674,785 samples, 0.51%)</title><rect x="70.7" y="757" width="6.0" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="73.67" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,531,843,953 samples, 0.02%)</title><rect x="247.7" y="437" width="0.2" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="250.73" y="447.5" ></text>
</g>
<g >
<title>__futex_abstimed_wait_common (1,165,671,089 samples, 0.01%)</title><rect x="354.4" y="229" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="357.38" y="239.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (861,999,117 samples, 0.01%)</title><rect x="297.1" y="229" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="300.07" y="239.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64 (1,719,747,714 samples, 0.02%)</title><rect x="503.8" y="469" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="506.76" y="479.5" ></text>
</g>
<g >
<title>new_list (1,937,678,013 samples, 0.02%)</title><rect x="985.6" y="389" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="988.59" y="399.5" ></text>
</g>
<g >
<title>__schedule (3,461,512,841 samples, 0.04%)</title><rect x="206.5" y="421" width="0.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="209.46" y="431.5" ></text>
</g>
<g >
<title>_bt_readpage (4,784,563,854 samples, 0.05%)</title><rect x="124.6" y="261" width="0.6" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="127.59" y="271.5" ></text>
</g>
<g >
<title>hash_search (4,426,773,496 samples, 0.05%)</title><rect x="943.5" y="357" width="0.6" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="946.53" y="367.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,128,792,390 samples, 0.01%)</title><rect x="452.0" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="455.04" y="463.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,745,976,501 samples, 0.02%)</title><rect x="1070.4" y="453" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1073.36" y="463.5" ></text>
</g>
<g >
<title>hash_search (6,503,753,728 samples, 0.07%)</title><rect x="1023.0" y="341" width="0.8" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1025.98" y="351.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,033,696,341 samples, 0.01%)</title><rect x="296.5" y="229" width="0.1" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="299.46" y="239.5" ></text>
</g>
<g >
<title>bms_free (2,555,515,291 samples, 0.03%)</title><rect x="996.6" y="341" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="999.59" y="351.5" ></text>
</g>
<g >
<title>standard_planner (2,826,087,191 samples, 0.03%)</title><rect x="124.2" y="565" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="127.24" y="575.5" ></text>
</g>
<g >
<title>pull_var_clause_walker (11,766,540,323 samples, 0.12%)</title><rect x="955.5" y="405" width="1.5" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="958.50" y="415.5" ></text>
</g>
<g >
<title>check_functions_in_node (5,495,958,546 samples, 0.06%)</title><rect x="960.2" y="341" width="0.7" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="963.20" y="351.5" ></text>
</g>
<g >
<title>ExecIndexScan (477,858,855,501 samples, 5.06%)</title><rect x="282.4" y="405" width="59.8" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="285.42" y="415.5" >ExecIn..</text>
</g>
<g >
<title>tts_buffer_heap_materialize (21,918,547,447 samples, 0.23%)</title><rect x="388.9" y="389" width="2.7" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="391.90" y="399.5" ></text>
</g>
<g >
<title>fix_expr_common (839,107,237 samples, 0.01%)</title><rect x="902.0" y="357" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="904.97" y="367.5" ></text>
</g>
<g >
<title>MakeTupleTableSlot (19,808,450,222 samples, 0.21%)</title><rect x="275.8" y="389" width="2.5" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="278.77" y="399.5" ></text>
</g>
<g >
<title>obj_cgroup_uncharge_pages (827,414,203 samples, 0.01%)</title><rect x="179.5" y="309" width="0.1" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="182.54" y="319.5" ></text>
</g>
<g >
<title>PageGetItem (870,084,547 samples, 0.01%)</title><rect x="317.0" y="229" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="320.02" y="239.5" ></text>
</g>
<g >
<title>palloc (3,036,219,081 samples, 0.03%)</title><rect x="1018.2" y="325" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1021.18" y="335.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (939,207,866 samples, 0.01%)</title><rect x="862.0" y="389" width="0.1" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="864.98" y="399.5" ></text>
</g>
<g >
<title>do_syscall_64 (5,004,523,190 samples, 0.05%)</title><rect x="350.8" y="229" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="353.78" y="239.5" ></text>
</g>
<g >
<title>AllocSetContextCreateInternal (2,705,140,651 samples, 0.03%)</title><rect x="211.1" y="565" width="0.3" height="15.0" fill="rgb(211,30,7)" rx="2" ry="2" />
<text  x="214.10" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (930,486,125 samples, 0.01%)</title><rect x="897.1" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="900.13" y="431.5" ></text>
</g>
<g >
<title>index_getattr (4,424,567,389 samples, 0.05%)</title><rect x="334.3" y="213" width="0.6" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="337.33" y="223.5" ></text>
</g>
<g >
<title>LWLockRelease (1,008,536,212 samples, 0.01%)</title><rect x="400.7" y="277" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="403.71" y="287.5" ></text>
</g>
<g >
<title>enqueue_task (913,814,805 samples, 0.01%)</title><rect x="487.9" y="293" width="0.1" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="490.86" y="303.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (1,100,737,859,079 samples, 11.66%)</title><rect x="264.4" y="469" width="137.6" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="267.37" y="479.5" >ExecProcNodeFirst</text>
</g>
<g >
<title>palloc (4,988,367,562 samples, 0.05%)</title><rect x="1113.6" y="677" width="0.7" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1116.65" y="687.5" ></text>
</g>
<g >
<title>set_plan_refs (44,965,814,222 samples, 0.48%)</title><rect x="898.2" y="501" width="5.6" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="901.20" y="511.5" ></text>
</g>
<g >
<title>int4hashfast (902,471,193 samples, 0.01%)</title><rect x="1048.0" y="357" width="0.1" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="1050.96" y="367.5" ></text>
</g>
<g >
<title>AllocSetFreeIndex (961,228,820 samples, 0.01%)</title><rect x="1050.6" y="437" width="0.1" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="1053.59" y="447.5" ></text>
</g>
<g >
<title>fastgetattr (8,424,799,006 samples, 0.09%)</title><rect x="826.5" y="341" width="1.0" height="15.0" fill="rgb(206,8,2)" rx="2" ry="2" />
<text  x="829.46" y="351.5" ></text>
</g>
<g >
<title>MemoryContextResetOnly (4,404,461,859 samples, 0.05%)</title><rect x="223.4" y="517" width="0.6" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="226.41" y="527.5" ></text>
</g>
<g >
<title>eqsel (52,657,195,834 samples, 0.56%)</title><rect x="1030.4" y="293" width="6.5" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="1033.36" y="303.5" ></text>
</g>
<g >
<title>relation_open (15,036,200,314 samples, 0.16%)</title><rect x="947.4" y="389" width="1.9" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="950.44" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,801,926,966 samples, 0.03%)</title><rect x="895.0" y="485" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="897.96" y="495.5" ></text>
</g>
<g >
<title>expr_setup_walker (1,590,077,897 samples, 0.02%)</title><rect x="418.5" y="229" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="421.53" y="239.5" ></text>
</g>
<g >
<title>asm_sysvec_apic_timer_interrupt (1,337,256,247 samples, 0.01%)</title><rect x="695.3" y="517" width="0.1" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="698.26" y="527.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,051,087,349 samples, 0.02%)</title><rect x="923.4" y="373" width="0.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="926.43" y="383.5" ></text>
</g>
<g >
<title>AtEOXact_PgStat_Database (1,012,334,271 samples, 0.01%)</title><rect x="32.3" y="757" width="0.1" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="35.30" y="767.5" ></text>
</g>
<g >
<title>preprocess_expression (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="581" width="3.4" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="104.45" y="591.5" ></text>
</g>
<g >
<title>is_valid_ascii (1,188,519,138 samples, 0.01%)</title><rect x="1153.0" y="757" width="0.1" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1155.95" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (2,887,969,305 samples, 0.03%)</title><rect x="1055.5" y="437" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="1058.50" y="447.5" ></text>
</g>
<g >
<title>gup_fast (1,495,489,049 samples, 0.02%)</title><rect x="351.2" y="101" width="0.2" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="354.19" y="111.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (1,234,043,728 samples, 0.01%)</title><rect x="483.4" y="389" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="486.42" y="399.5" ></text>
</g>
<g >
<title>__futex_wait (3,952,774,475 samples, 0.04%)</title><rect x="350.9" y="165" width="0.5" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="353.88" y="175.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,123,994,504 samples, 0.01%)</title><rect x="1134.0" y="645" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1136.99" y="655.5" ></text>
</g>
<g >
<title>sock_recvmsg (57,749,508,135 samples, 0.61%)</title><rect x="177.1" y="421" width="7.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="180.12" y="431.5" ></text>
</g>
<g >
<title>list_free (2,282,165,441 samples, 0.02%)</title><rect x="944.5" y="405" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="947.49" y="415.5" ></text>
</g>
<g >
<title>__x64_sys_futex (61,678,228,900 samples, 0.65%)</title><rect x="475.7" y="389" width="7.7" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="478.71" y="399.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,580,837,477 samples, 0.02%)</title><rect x="453.8" y="517" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="456.80" y="527.5" ></text>
</g>
<g >
<title>table_index_fetch_tuple (135,069,562,019 samples, 1.43%)</title><rect x="294.2" y="293" width="16.9" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="297.22" y="303.5" ></text>
</g>
<g >
<title>finalize_primnode (1,400,088,792 samples, 0.01%)</title><rect x="1137.4" y="757" width="0.2" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="1140.38" y="767.5" ></text>
</g>
<g >
<title>make_op (63,894,143,982 samples, 0.68%)</title><rect x="844.8" y="421" width="8.0" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="847.80" y="431.5" ></text>
</g>
<g >
<title>PageAddItemExtended (1,666,744,002 samples, 0.02%)</title><rect x="79.7" y="757" width="0.2" height="15.0" fill="rgb(235,139,33)" rx="2" ry="2" />
<text  x="82.73" y="767.5" ></text>
</g>
<g >
<title>AllocSetFree (849,451,640 samples, 0.01%)</title><rect x="222.3" y="549" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="225.28" y="559.5" ></text>
</g>
<g >
<title>shmem_write_begin (12,724,011,618 samples, 0.13%)</title><rect x="498.5" y="357" width="1.6" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="501.46" y="367.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (922,509,514 samples, 0.01%)</title><rect x="221.3" y="501" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="224.26" y="511.5" ></text>
</g>
<g >
<title>palloc0 (4,235,448,399 samples, 0.04%)</title><rect x="945.7" y="405" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="948.73" y="415.5" ></text>
</g>
<g >
<title>expr_setup_walker (5,403,689,107 samples, 0.06%)</title><rect x="418.1" y="277" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="421.07" y="287.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,171,498,348 samples, 0.02%)</title><rect x="968.4" y="309" width="0.3" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="971.41" y="319.5" ></text>
</g>
<g >
<title>pgstat_count_backend_io_op (3,642,304,304 samples, 0.04%)</title><rect x="98.2" y="181" width="0.4" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="101.16" y="191.5" ></text>
</g>
<g >
<title>__get_user_8 (837,106,602 samples, 0.01%)</title><rect x="483.8" y="325" width="0.1" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="486.84" y="335.5" ></text>
</g>
<g >
<title>fix_scan_expr_walker (8,572,252,960 samples, 0.09%)</title><rect x="902.1" y="389" width="1.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="905.08" y="399.5" ></text>
</g>
<g >
<title>ExecComputeSlotInfo (3,470,457,015 samples, 0.04%)</title><rect x="271.5" y="389" width="0.5" height="15.0" fill="rgb(207,10,2)" rx="2" ry="2" />
<text  x="274.55" y="399.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (2,093,838,113 samples, 0.02%)</title><rect x="1157.9" y="245" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1160.94" y="255.5" ></text>
</g>
<g >
<title>table_open (18,097,741,671 samples, 0.19%)</title><rect x="861.0" y="517" width="2.3" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="864.01" y="527.5" ></text>
</g>
<g >
<title>palloc0 (2,678,236,028 samples, 0.03%)</title><rect x="415.4" y="357" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="418.37" y="367.5" ></text>
</g>
<g >
<title>palloc (1,533,540,832 samples, 0.02%)</title><rect x="890.0" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="893.04" y="255.5" ></text>
</g>
<g >
<title>new_list (2,429,924,176 samples, 0.03%)</title><rect x="449.7" y="437" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="452.67" y="447.5" ></text>
</g>
<g >
<title>finish_task_switch.isra.0 (3,186,459,733 samples, 0.03%)</title><rect x="477.4" y="277" width="0.4" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="480.40" y="287.5" ></text>
</g>
<g >
<title>match_restriction_clauses_to_index (27,642,273,364 samples, 0.29%)</title><rect x="1019.1" y="389" width="3.5" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1022.11" y="399.5" ></text>
</g>
<g >
<title>palloc (1,295,152,374 samples, 0.01%)</title><rect x="985.7" y="373" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="988.67" y="383.5" ></text>
</g>
<g >
<title>SearchSysCacheList (9,818,440,266 samples, 0.10%)</title><rect x="962.5" y="373" width="1.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="965.49" y="383.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (1,283,154,337 samples, 0.01%)</title><rect x="1068.1" y="357" width="0.2" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1071.10" y="367.5" ></text>
</g>
<g >
<title>can_coerce_type (1,729,552,018 samples, 0.02%)</title><rect x="841.7" y="421" width="0.2" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="844.67" y="431.5" ></text>
</g>
<g >
<title>palloc (2,435,729,580 samples, 0.03%)</title><rect x="509.7" y="485" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="512.72" y="495.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (857,285,167 samples, 0.01%)</title><rect x="94.7" y="213" width="0.1" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="97.67" y="223.5" ></text>
</g>
<g >
<title>hash_search (3,186,973,369 samples, 0.03%)</title><rect x="1068.3" y="421" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1071.27" y="431.5" ></text>
</g>
<g >
<title>postmaster_child_launch (7,629,157,056,107 samples, 80.84%)</title><rect x="130.1" y="645" width="953.9" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="133.07" y="655.5" >postmaster_child_launch</text>
</g>
<g >
<title>addRTEPermissionInfo (5,034,068,761 samples, 0.05%)</title><rect x="896.3" y="469" width="0.6" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="899.31" y="479.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (902,856,012 samples, 0.01%)</title><rect x="277.6" y="325" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="280.58" y="335.5" ></text>
</g>
<g >
<title>pfree (1,122,372,860 samples, 0.01%)</title><rect x="222.3" y="565" width="0.1" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="225.27" y="575.5" ></text>
</g>
<g >
<title>do_fsync (2,446,962,893 samples, 0.03%)</title><rect x="502.7" y="373" width="0.3" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="505.68" y="383.5" ></text>
</g>
<g >
<title>XLogInsertRecord (17,680,383,261 samples, 0.19%)</title><rect x="506.6" y="469" width="2.2" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="509.63" y="479.5" ></text>
</g>
<g >
<title>pg_detoast_datum_copy (1,394,870,975 samples, 0.01%)</title><rect x="1168.6" y="757" width="0.2" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1171.59" y="767.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (479,196,055,339 samples, 5.08%)</title><rect x="282.4" y="421" width="59.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="285.39" y="431.5" >ExecPr..</text>
</g>
<g >
<title>LWLockAcquireOrWait (87,352,600,581 samples, 0.93%)</title><rect x="473.6" y="485" width="10.9" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="476.58" y="495.5" ></text>
</g>
<g >
<title>ItemPointerGetOffsetNumber (1,043,818,130 samples, 0.01%)</title><rect x="377.8" y="341" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="380.81" y="351.5" ></text>
</g>
<g >
<title>make_fn_arguments (1,174,010,777 samples, 0.01%)</title><rect x="847.7" y="405" width="0.1" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="850.67" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetBuffer (972,150,709 samples, 0.01%)</title><rect x="324.9" y="181" width="0.1" height="15.0" fill="rgb(247,193,46)" rx="2" ry="2" />
<text  x="327.86" y="191.5" ></text>
</g>
<g >
<title>heapam_estimate_rel_size (27,497,432,430 samples, 0.29%)</title><rect x="935.2" y="373" width="3.4" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="938.17" y="383.5" ></text>
</g>
<g >
<title>palloc0 (1,796,226,883 samples, 0.02%)</title><rect x="451.6" y="469" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="454.64" y="479.5" ></text>
</g>
<g >
<title>DatumGetInt32 (4,519,568,908 samples, 0.05%)</title><rect x="40.0" y="757" width="0.5" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="42.97" y="767.5" ></text>
</g>
<g >
<title>update_process_times (2,116,501,659 samples, 0.02%)</title><rect x="791.3" y="421" width="0.3" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="794.33" y="431.5" ></text>
</g>
<g >
<title>cfree@GLIBC_2.2.5 (1,881,773,899 samples, 0.02%)</title><rect x="261.3" y="405" width="0.3" height="15.0" fill="rgb(233,131,31)" rx="2" ry="2" />
<text  x="264.32" y="415.5" ></text>
</g>
<g >
<title>HistoricSnapshotActive (1,052,735,155 samples, 0.01%)</title><rect x="52.3" y="757" width="0.1" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="55.28" y="767.5" ></text>
</g>
<g >
<title>MemoryContextDelete (10,537,669,606 samples, 0.11%)</title><rect x="250.0" y="469" width="1.3" height="15.0" fill="rgb(244,179,42)" rx="2" ry="2" />
<text  x="252.99" y="479.5" ></text>
</g>
<g >
<title>hash_search (2,882,314,602 samples, 0.03%)</title><rect x="948.9" y="357" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="951.94" y="367.5" ></text>
</g>
<g >
<title>bms_free (1,633,795,381 samples, 0.02%)</title><rect x="966.8" y="373" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="969.76" y="383.5" ></text>
</g>
<g >
<title>get_typcollation (852,696,924 samples, 0.01%)</title><rect x="1141.1" y="757" width="0.1" height="15.0" fill="rgb(244,181,43)" rx="2" ry="2" />
<text  x="1144.10" y="767.5" ></text>
</g>
<g >
<title>_bt_getbuf (3,010,407,211 samples, 0.03%)</title><rect x="123.5" y="261" width="0.4" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="126.54" y="271.5" ></text>
</g>
<g >
<title>palloc0 (1,927,976,310 samples, 0.02%)</title><rect x="998.1" y="245" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1001.14" y="255.5" ></text>
</g>
<g >
<title>get_hash_value (2,856,090,280 samples, 0.03%)</title><rect x="399.8" y="261" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="402.77" y="271.5" ></text>
</g>
<g >
<title>__smp_call_single_queue (2,018,613,615 samples, 0.02%)</title><rect x="205.1" y="309" width="0.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="208.14" y="319.5" ></text>
</g>
<g >
<title>PostmasterMain (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="757" width="3.0" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="85.62" y="767.5" ></text>
</g>
<g >
<title>bms_make_singleton (1,680,797,107 samples, 0.02%)</title><rect x="976.6" y="421" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="979.59" y="431.5" ></text>
</g>
<g >
<title>ProcessQuery (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="565" width="1.9" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="127.59" y="575.5" ></text>
</g>
<g >
<title>assign_collations_walker (29,652,077,228 samples, 0.31%)</title><rect x="804.8" y="405" width="3.7" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="807.83" y="415.5" ></text>
</g>
<g >
<title>ExecutorRun (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="597" width="2.8" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="85.62" y="607.5" ></text>
</g>
<g >
<title>_bt_num_array_keys (1,159,155,197 samples, 0.01%)</title><rect x="129.0" y="757" width="0.1" height="15.0" fill="rgb(229,114,27)" rx="2" ry="2" />
<text  x="131.97" y="767.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,242,331,221 samples, 0.01%)</title><rect x="300.3" y="117" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="303.28" y="127.5" ></text>
</g>
<g >
<title>AssertTransactionIdInAllowableRange (1,614,901,013 samples, 0.02%)</title><rect x="231.4" y="501" width="0.2" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="234.42" y="511.5" ></text>
</g>
<g >
<title>slot_attisnull (5,556,508,101 samples, 0.06%)</title><rect x="343.6" y="389" width="0.7" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="346.63" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (7,191,699,348 samples, 0.08%)</title><rect x="417.9" y="293" width="0.8" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="420.85" y="303.5" ></text>
</g>
<g >
<title>hash_bytes_uint32 (2,064,904,299 samples, 0.02%)</title><rect x="1143.1" y="757" width="0.3" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="1146.14" y="767.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (821,427,597 samples, 0.01%)</title><rect x="695.3" y="485" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="698.32" y="495.5" ></text>
</g>
<g >
<title>build_path_tlist (11,910,772,342 samples, 0.13%)</title><rect x="884.3" y="437" width="1.5" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="887.32" y="447.5" ></text>
</g>
<g >
<title>fmgr_info_copy (828,208,329 samples, 0.01%)</title><rect x="313.8" y="245" width="0.1" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="316.80" y="255.5" ></text>
</g>
<g >
<title>palloc0 (2,626,162,970 samples, 0.03%)</title><rect x="1044.5" y="453" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1047.50" y="463.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,091,880,343 samples, 0.02%)</title><rect x="976.3" y="373" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="979.31" y="383.5" ></text>
</g>
<g >
<title>socket_putmessage (5,690,722,704 samples, 0.06%)</title><rect x="216.4" y="565" width="0.7" height="15.0" fill="rgb(241,169,40)" rx="2" ry="2" />
<text  x="219.42" y="575.5" ></text>
</g>
<g >
<title>lcons (2,591,985,667 samples, 0.03%)</title><rect x="415.0" y="373" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="418.02" y="383.5" ></text>
</g>
<g >
<title>get_hash_entry (5,366,559,958 samples, 0.06%)</title><rect x="357.7" y="245" width="0.7" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="360.69" y="255.5" ></text>
</g>
<g >
<title>AllocSetFree (3,747,262,552 samples, 0.04%)</title><rect x="375.5" y="325" width="0.5" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="378.48" y="335.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (917,028,914 samples, 0.01%)</title><rect x="918.6" y="421" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="921.59" y="431.5" ></text>
</g>
<g >
<title>tag_hash (3,812,642,515 samples, 0.04%)</title><rect x="1067.8" y="389" width="0.5" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1070.78" y="399.5" ></text>
</g>
<g >
<title>PortalRunMulti (22,232,395,022 samples, 0.24%)</title><rect x="82.6" y="629" width="2.8" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="85.62" y="639.5" ></text>
</g>
<g >
<title>SearchSysCache4 (8,964,355,891 samples, 0.09%)</title><rect x="1009.6" y="277" width="1.1" height="15.0" fill="rgb(230,118,28)" rx="2" ry="2" />
<text  x="1012.56" y="287.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (4,767,953,797 samples, 0.05%)</title><rect x="443.3" y="421" width="0.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="446.26" y="431.5" ></text>
</g>
<g >
<title>attnameAttNum (4,030,504,920 samples, 0.04%)</title><rect x="832.0" y="469" width="0.5" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="834.97" y="479.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (1,696,312,215 samples, 0.02%)</title><rect x="366.6" y="277" width="0.3" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="369.65" y="287.5" ></text>
</g>
<g >
<title>free_attstatsslot (2,272,331,695 samples, 0.02%)</title><rect x="1001.5" y="293" width="0.3" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="1004.47" y="303.5" ></text>
</g>
<g >
<title>palloc0 (3,135,898,709 samples, 0.03%)</title><rect x="1024.0" y="373" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1026.97" y="383.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (3,348,210,553 samples, 0.04%)</title><rect x="358.6" y="261" width="0.5" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="361.64" y="271.5" ></text>
</g>
<g >
<title>SearchCatCache1 (7,538,946,710 samples, 0.08%)</title><rect x="435.7" y="341" width="0.9" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="438.67" y="351.5" ></text>
</g>
<g >
<title>palloc (1,435,602,989 samples, 0.02%)</title><rect x="858.5" y="485" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="861.54" y="495.5" ></text>
</g>
<g >
<title>UnpinBuffer (5,120,845,333 samples, 0.05%)</title><rect x="338.2" y="213" width="0.7" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="341.24" y="223.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,838,480,657 samples, 0.02%)</title><rect x="234.5" y="501" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="237.52" y="511.5" ></text>
</g>
<g >
<title>palloc (1,684,858,722 samples, 0.02%)</title><rect x="1009.1" y="245" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1012.07" y="255.5" ></text>
</g>
<g >
<title>tts_buffer_heap_getsomeattrs (3,885,974,569 samples, 0.04%)</title><rect x="343.8" y="341" width="0.5" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="346.84" y="351.5" ></text>
</g>
<g >
<title>newNode (2,265,882,676 samples, 0.02%)</title><rect x="921.1" y="421" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="924.11" y="431.5" ></text>
</g>
<g >
<title>lappend (953,395,222 samples, 0.01%)</title><rect x="977.7" y="437" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="980.75" y="447.5" ></text>
</g>
<g >
<title>LockBuffer (1,932,277,437 samples, 0.02%)</title><rect x="126.0" y="213" width="0.2" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="129.00" y="223.5" ></text>
</g>
<g >
<title>planner (2,495,599,539 samples, 0.03%)</title><rect x="126.5" y="565" width="0.3" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="129.52" y="575.5" ></text>
</g>
<g >
<title>tick_nohz_handler (2,260,228,205 samples, 0.02%)</title><rect x="791.3" y="437" width="0.3" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="794.33" y="447.5" ></text>
</g>
<g >
<title>planstate_tree_walker_impl (2,994,246,701 samples, 0.03%)</title><rect x="402.6" y="421" width="0.3" height="15.0" fill="rgb(226,97,23)" rx="2" ry="2" />
<text  x="405.55" y="431.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (1,989,678,386 samples, 0.02%)</title><rect x="297.6" y="165" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="300.58" y="175.5" ></text>
</g>
<g >
<title>GetPrivateRefCount (1,013,870,556 samples, 0.01%)</title><rect x="386.4" y="309" width="0.1" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="389.36" y="319.5" ></text>
</g>
<g >
<title>index_getnext_tid (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="389" width="6.6" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="97.67" y="399.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (22,832,631,250 samples, 0.24%)</title><rect x="900.5" y="437" width="2.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="903.55" y="447.5" ></text>
</g>
<g >
<title>create_empty_pathtarget (2,993,661,218 samples, 0.03%)</title><rect x="927.9" y="421" width="0.3" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="930.87" y="431.5" ></text>
</g>
<g >
<title>SysCacheGetAttr (47,799,417,030 samples, 0.51%)</title><rect x="1002.3" y="261" width="6.0" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1005.32" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,029,701,316 samples, 0.01%)</title><rect x="966.6" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="969.59" y="335.5" ></text>
</g>
<g >
<title>__strcmp_avx2 (2,525,157,861 samples, 0.03%)</title><rect x="413.2" y="437" width="0.3" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="416.18" y="447.5" ></text>
</g>
<g >
<title>core_yy_scan_buffer (8,323,205,451 samples, 0.09%)</title><rect x="870.5" y="533" width="1.0" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="873.48" y="543.5" ></text>
</g>
<g >
<title>__memcmp_avx2_movbe (6,554,665,414 samples, 0.07%)</title><rect x="1005.4" y="165" width="0.8" height="15.0" fill="rgb(224,91,21)" rx="2" ry="2" />
<text  x="1008.38" y="175.5" ></text>
</g>
<g >
<title>ExecProject (1,314,702,730 samples, 0.01%)</title><rect x="45.2" y="757" width="0.2" height="15.0" fill="rgb(254,226,54)" rx="2" ry="2" />
<text  x="48.22" y="767.5" ></text>
</g>
<g >
<title>__update_load_avg_se (3,356,660,221 samples, 0.04%)</title><rect x="170.7" y="261" width="0.4" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="173.72" y="271.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (27,222,510,426 samples, 0.29%)</title><rect x="101.5" y="549" width="3.4" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="104.45" y="559.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,709,111,775 samples, 0.02%)</title><rect x="367.7" y="245" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="370.71" y="255.5" ></text>
</g>
<g >
<title>exprType (1,208,948,897 samples, 0.01%)</title><rect x="422.7" y="357" width="0.1" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="425.68" y="367.5" ></text>
</g>
<g >
<title>AllocSetFreeIndex (1,167,135,924 samples, 0.01%)</title><rect x="111.5" y="741" width="0.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="114.53" y="751.5" ></text>
</g>
<g >
<title>hash_initial_lookup (880,744,112 samples, 0.01%)</title><rect x="861.5" y="405" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="864.50" y="415.5" ></text>
</g>
<g >
<title>initStringInfoInternal (3,218,182,279 samples, 0.03%)</title><rect x="1079.1" y="581" width="0.4" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1082.08" y="591.5" ></text>
</g>
<g >
<title>eval_const_expressions (886,882,643 samples, 0.01%)</title><rect x="124.5" y="501" width="0.1" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="127.48" y="511.5" ></text>
</g>
<g >
<title>list_free (987,557,694 samples, 0.01%)</title><rect x="1154.6" y="757" width="0.1" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="1157.58" y="767.5" ></text>
</g>
<g >
<title>tag_hash (2,012,049,509 samples, 0.02%)</title><rect x="948.6" y="325" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="951.56" y="335.5" ></text>
</g>
<g >
<title>SearchSysCache1 (6,221,940,475 samples, 0.07%)</title><rect x="848.2" y="389" width="0.8" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="851.22" y="399.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,597,747,131 samples, 0.03%)</title><rect x="508.0" y="421" width="0.4" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="511.05" y="431.5" ></text>
</g>
<g >
<title>ExecCloseResultRelations (18,276,068,040 samples, 0.19%)</title><rect x="235.7" y="485" width="2.3" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="238.67" y="495.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (5,356,501,168 samples, 0.06%)</title><rect x="395.2" y="357" width="0.7" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="398.21" y="367.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,184,143,175 samples, 0.01%)</title><rect x="1189.6" y="741" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="1192.57" y="751.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,292,418,849 samples, 0.01%)</title><rect x="493.3" y="453" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="496.31" y="463.5" ></text>
</g>
<g >
<title>x64_sys_call (852,578,176 samples, 0.01%)</title><rect x="503.0" y="389" width="0.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="506.03" y="399.5" ></text>
</g>
<g >
<title>pg_plan_queries (2,826,087,191 samples, 0.03%)</title><rect x="124.2" y="613" width="0.4" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="127.24" y="623.5" ></text>
</g>
<g >
<title>try_to_wake_up (19,108,959,219 samples, 0.20%)</title><rect x="486.3" y="309" width="2.4" height="15.0" fill="rgb(220,70,16)" rx="2" ry="2" />
<text  x="489.33" y="319.5" ></text>
</g>
<g >
<title>LWLockRelease (1,896,000,856 samples, 0.02%)</title><rect x="56.8" y="757" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="59.78" y="767.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (37,290,855,087 samples, 0.40%)</title><rect x="791.9" y="549" width="4.7" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="794.92" y="559.5" ></text>
</g>
<g >
<title>standard_planner (1,580,813,162,039 samples, 16.75%)</title><rect x="874.4" y="533" width="197.7" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="877.39" y="543.5" >standard_planner</text>
</g>
<g >
<title>hash_bytes (1,016,973,049 samples, 0.01%)</title><rect x="117.1" y="741" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="120.13" y="751.5" ></text>
</g>
<g >
<title>ExecutePlan (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="533" width="1.7" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="125.45" y="543.5" ></text>
</g>
<g >
<title>__memmove_avx_unaligned_erms (1,225,287,510 samples, 0.01%)</title><rect x="456.4" y="533" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="459.43" y="543.5" ></text>
</g>
<g >
<title>pfree (1,486,684,391 samples, 0.02%)</title><rect x="232.9" y="517" width="0.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="235.93" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,733,255,781 samples, 0.02%)</title><rect x="1116.3" y="677" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1119.29" y="687.5" ></text>
</g>
<g >
<title>_bt_getroot (3,010,407,211 samples, 0.03%)</title><rect x="123.5" y="277" width="0.4" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="126.54" y="287.5" ></text>
</g>
<g >
<title>TransactionIdGetStatus (6,856,492,637 samples, 0.07%)</title><rect x="1147.0" y="661" width="0.9" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="1150.01" y="671.5" ></text>
</g>
<g >
<title>hash_bytes (1,929,725,183 samples, 0.02%)</title><rect x="948.6" y="309" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="951.57" y="319.5" ></text>
</g>
<g >
<title>__schedule (1,166,527,053 samples, 0.01%)</title><rect x="174.2" y="405" width="0.1" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="177.15" y="415.5" ></text>
</g>
<g >
<title>makeVar (4,482,229,728 samples, 0.05%)</title><rect x="921.7" y="453" width="0.6" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="924.73" y="463.5" ></text>
</g>
<g >
<title>palloc (1,308,277,237 samples, 0.01%)</title><rect x="919.3" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="922.32" y="447.5" ></text>
</g>
<g >
<title>ReadBuffer (21,225,489,098 samples, 0.22%)</title><rect x="399.1" y="389" width="2.7" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="402.11" y="399.5" ></text>
</g>
<g >
<title>create_index_paths (288,205,670,814 samples, 3.05%)</title><rect x="986.5" y="405" width="36.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="989.53" y="415.5" >cre..</text>
</g>
<g >
<title>LWLockAttemptLock (960,572,293 samples, 0.01%)</title><rect x="56.4" y="757" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="59.35" y="767.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (832,493,872 samples, 0.01%)</title><rect x="1147.1" y="629" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="1150.08" y="639.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,016,396,383 samples, 0.01%)</title><rect x="815.5" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="818.47" y="415.5" ></text>
</g>
<g >
<title>planner (1,552,620,354 samples, 0.02%)</title><rect x="1171.9" y="757" width="0.2" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="1174.88" y="767.5" ></text>
</g>
<g >
<title>_bt_getroot (1,525,270,115 samples, 0.02%)</title><rect x="128.5" y="757" width="0.2" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="131.46" y="767.5" ></text>
</g>
<g >
<title>DatumGetInt32 (2,460,410,111 samples, 0.03%)</title><rect x="112.4" y="741" width="0.3" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="115.36" y="751.5" ></text>
</g>
<g >
<title>LWLockRelease (37,070,753,527 samples, 0.39%)</title><rect x="484.5" y="485" width="4.6" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="487.51" y="495.5" ></text>
</g>
<g >
<title>fix_indexqual_clause (21,532,823,427 samples, 0.23%)</title><rect x="887.8" y="373" width="2.7" height="15.0" fill="rgb(236,143,34)" rx="2" ry="2" />
<text  x="890.81" y="383.5" ></text>
</g>
<g >
<title>ComputeXidHorizons (2,423,459,785 samples, 0.03%)</title><rect x="303.5" y="181" width="0.3" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="306.52" y="191.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,594,664,986 samples, 0.02%)</title><rect x="46.5" y="757" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="49.46" y="767.5" ></text>
</g>
<g >
<title>hash_bytes (7,608,037,791 samples, 0.08%)</title><rect x="520.3" y="405" width="0.9" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="523.27" y="415.5" ></text>
</g>
<g >
<title>LWLockRelease (941,117,779 samples, 0.01%)</title><rect x="365.6" y="341" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="368.59" y="351.5" ></text>
</g>
<g >
<title>bms_is_valid_set (1,599,066,396 samples, 0.02%)</title><rect x="375.1" y="341" width="0.2" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="378.15" y="351.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (4,073,086,921 samples, 0.04%)</title><rect x="791.2" y="501" width="0.5" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="794.15" y="511.5" ></text>
</g>
<g >
<title>merge_collation_state (969,617,490 samples, 0.01%)</title><rect x="807.1" y="309" width="0.1" height="15.0" fill="rgb(220,73,17)" rx="2" ry="2" />
<text  x="810.11" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,426,551,371 samples, 0.02%)</title><rect x="405.3" y="453" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="408.29" y="463.5" ></text>
</g>
<g >
<title>table_close (1,729,066,803 samples, 0.02%)</title><rect x="235.5" y="469" width="0.2" height="15.0" fill="rgb(241,166,39)" rx="2" ry="2" />
<text  x="238.45" y="479.5" ></text>
</g>
<g >
<title>bms_del_member (2,863,017,622 samples, 0.03%)</title><rect x="1069.6" y="485" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1072.59" y="495.5" ></text>
</g>
<g >
<title>core_yyalloc (1,363,892,002 samples, 0.01%)</title><rect x="871.1" y="485" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="874.14" y="495.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (35,580,111,113 samples, 0.38%)</title><rect x="241.9" y="341" width="4.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="244.87" y="351.5" ></text>
</g>
<g >
<title>PushActiveSnapshotWithLevel (2,522,837,198 samples, 0.03%)</title><rect x="454.1" y="517" width="0.3" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="457.12" y="527.5" ></text>
</g>
<g >
<title>set_plan_references (67,767,249,031 samples, 0.72%)</title><rect x="895.3" y="517" width="8.5" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="898.35" y="527.5" ></text>
</g>
<g >
<title>palloc (918,747,572 samples, 0.01%)</title><rect x="833.4" y="421" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="836.42" y="431.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (3,932,715,692 samples, 0.04%)</title><rect x="220.6" y="533" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="223.57" y="543.5" ></text>
</g>
<g >
<title>get_relation_info (167,103,291,273 samples, 1.77%)</title><rect x="928.5" y="421" width="20.8" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="931.46" y="431.5" ></text>
</g>
<g >
<title>AllocSetCheck (4,653,290,802 samples, 0.05%)</title><rect x="460.0" y="453" width="0.6" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="463.00" y="463.5" ></text>
</g>
<g >
<title>make_oper_cache_key (10,761,344,909 samples, 0.11%)</title><rect x="851.4" y="389" width="1.3" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="854.39" y="399.5" ></text>
</g>
<g >
<title>ExecScanExtended (27,165,434,994 samples, 0.29%)</title><rect x="278.9" y="389" width="3.4" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="281.92" y="399.5" ></text>
</g>
<g >
<title>distribute_row_identity_vars (1,763,115,397 samples, 0.02%)</title><rect x="979.5" y="469" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="982.47" y="479.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (4,356,014,044 samples, 0.05%)</title><rect x="803.7" y="325" width="0.5" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="806.67" y="335.5" ></text>
</g>
<g >
<title>AcceptInvalidationMessages (3,598,246,854 samples, 0.04%)</title><rect x="1074.9" y="517" width="0.5" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1077.93" y="527.5" ></text>
</g>
<g >
<title>pgstat_count_heap_update (914,219,891 samples, 0.01%)</title><rect x="1170.7" y="757" width="0.2" height="15.0" fill="rgb(214,45,10)" rx="2" ry="2" />
<text  x="1173.74" y="767.5" ></text>
</g>
<g >
<title>pgstat_clear_backend_activity_snapshot (898,020,725 samples, 0.01%)</title><rect x="1170.5" y="757" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1173.49" y="767.5" ></text>
</g>
<g >
<title>eval_const_expressions (1,440,027,750 samples, 0.02%)</title><rect x="1173.0" y="741" width="0.2" height="15.0" fill="rgb(213,37,8)" rx="2" ry="2" />
<text  x="1176.03" y="751.5" ></text>
</g>
<g >
<title>sem_post@GLIBC_2.2.5 (4,447,432,053 samples, 0.05%)</title><rect x="351.8" y="245" width="0.6" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="354.83" y="255.5" ></text>
</g>
<g >
<title>transformAExprOp (52,675,067,824 samples, 0.56%)</title><rect x="834.2" y="405" width="6.6" height="15.0" fill="rgb(246,191,45)" rx="2" ry="2" />
<text  x="837.24" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerRelease (12,571,275,943 samples, 0.13%)</title><rect x="224.7" y="565" width="1.6" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="227.73" y="575.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,366,985,686 samples, 0.01%)</title><rect x="1024.2" y="357" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1027.18" y="367.5" ></text>
</g>
<g >
<title>BufTableLookup (5,087,299,096 samples, 0.05%)</title><rect x="95.4" y="181" width="0.7" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="98.45" y="191.5" ></text>
</g>
<g >
<title>avc_has_perm (1,397,105,881 samples, 0.01%)</title><rect x="177.7" y="373" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="180.71" y="383.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,585,398,524 samples, 0.04%)</title><rect x="360.9" y="309" width="0.5" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="363.94" y="319.5" ></text>
</g>
<g >
<title>_bt_check_compare (4,655,250,771 samples, 0.05%)</title><rect x="124.6" y="229" width="0.6" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="127.60" y="239.5" ></text>
</g>
<g >
<title>pfree (1,624,694,036 samples, 0.02%)</title><rect x="1001.5" y="277" width="0.3" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="1004.55" y="287.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (945,806,513 samples, 0.01%)</title><rect x="402.0" y="485" width="0.1" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="405.01" y="495.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,529,546,355 samples, 0.02%)</title><rect x="445.2" y="341" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="448.17" y="351.5" ></text>
</g>
<g >
<title>check_index_predicates (2,185,440,099 samples, 0.02%)</title><rect x="1027.8" y="405" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="1030.80" y="415.5" ></text>
</g>
<g >
<title>ExecOpenIndices (38,492,322,319 samples, 0.41%)</title><rect x="391.6" y="405" width="4.9" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="394.64" y="415.5" ></text>
</g>
<g >
<title>issue_xlog_fsync (15,648,182,657 samples, 0.17%)</title><rect x="501.8" y="469" width="2.0" height="15.0" fill="rgb(225,92,22)" rx="2" ry="2" />
<text  x="504.80" y="479.5" ></text>
</g>
<g >
<title>palloc (882,895,098 samples, 0.01%)</title><rect x="972.4" y="357" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="975.45" y="367.5" ></text>
</g>
<g >
<title>get_tablespace (3,577,662,818 samples, 0.04%)</title><rect x="1013.3" y="277" width="0.5" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1016.33" y="287.5" ></text>
</g>
<g >
<title>CheckForBufferLeaks (2,177,977,907 samples, 0.02%)</title><rect x="461.2" y="501" width="0.3" height="15.0" fill="rgb(227,101,24)" rx="2" ry="2" />
<text  x="464.23" y="511.5" ></text>
</g>
<g >
<title>__x64_sys_futex (996,759,821 samples, 0.01%)</title><rect x="296.8" y="149" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="299.84" y="159.5" ></text>
</g>
<g >
<title>new_list (3,422,330,904 samples, 0.04%)</title><rect x="1119.5" y="709" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1122.50" y="719.5" ></text>
</g>
<g >
<title>LWLockRelease (1,465,416,357 samples, 0.02%)</title><rect x="466.6" y="501" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="469.62" y="511.5" ></text>
</g>
<g >
<title>sysvec_apic_timer_interrupt (1,272,668,307 samples, 0.01%)</title><rect x="695.3" y="501" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="698.26" y="511.5" ></text>
</g>
<g >
<title>ExecInitUpdateProjection (77,560,381,898 samples, 0.82%)</title><rect x="268.8" y="437" width="9.7" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="271.84" y="447.5" ></text>
</g>
<g >
<title>pgstat_report_xact_timestamp (2,313,250,588 samples, 0.02%)</title><rect x="526.2" y="517" width="0.3" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="529.20" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,962,675,009 samples, 0.02%)</title><rect x="1120.1" y="677" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1123.13" y="687.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (1,498,575,697 samples, 0.02%)</title><rect x="822.9" y="309" width="0.2" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="825.88" y="319.5" ></text>
</g>
<g >
<title>bms_is_subset (942,313,701 samples, 0.01%)</title><rect x="959.2" y="405" width="0.1" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="962.22" y="415.5" ></text>
</g>
<g >
<title>ExprEvalPushStep (1,953,186,467 samples, 0.02%)</title><rect x="430.2" y="373" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="433.20" y="383.5" ></text>
</g>
<g >
<title>LWLockRelease (957,146,842 samples, 0.01%)</title><rect x="1147.1" y="645" width="0.1" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="1150.06" y="655.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (3,854,867,773 samples, 0.04%)</title><rect x="268.3" y="325" width="0.5" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="271.31" y="335.5" ></text>
</g>
<g >
<title>bms_make_singleton (2,184,664,765 samples, 0.02%)</title><rect x="927.6" y="421" width="0.2" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="930.58" y="431.5" ></text>
</g>
<g >
<title>detoast_attr (1,508,042,923 samples, 0.02%)</title><rect x="1131.3" y="757" width="0.2" height="15.0" fill="rgb(254,227,54)" rx="2" ry="2" />
<text  x="1134.33" y="767.5" ></text>
</g>
<g >
<title>_bt_checkkeys (2,568,695,668 samples, 0.03%)</title><rect x="1157.9" y="277" width="0.4" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="1160.94" y="287.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (2,031,936,756 samples, 0.02%)</title><rect x="861.4" y="421" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="864.35" y="431.5" ></text>
</g>
<g >
<title>hash_initial_lookup (847,596,591 samples, 0.01%)</title><rect x="299.2" y="101" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="302.22" y="111.5" ></text>
</g>
<g >
<title>AllocSetAlloc (902,411,703 samples, 0.01%)</title><rect x="985.7" y="357" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="988.70" y="367.5" ></text>
</g>
<g >
<title>__scm_recv_common.isra.0 (1,543,177,568 samples, 0.02%)</title><rect x="182.9" y="357" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="185.89" y="367.5" ></text>
</g>
<g >
<title>LockBuffer (1,626,615,858 samples, 0.02%)</title><rect x="57.7" y="757" width="0.2" height="15.0" fill="rgb(235,142,34)" rx="2" ry="2" />
<text  x="60.73" y="767.5" ></text>
</g>
<g >
<title>ExecGetUpdateNewTuple (22,655,575,050 samples, 0.24%)</title><rect x="266.0" y="437" width="2.8" height="15.0" fill="rgb(246,189,45)" rx="2" ry="2" />
<text  x="268.99" y="447.5" ></text>
</g>
<g >
<title>[unknown] (89,116,723,262 samples, 0.94%)</title><rect x="111.0" y="757" width="11.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="114.02" y="767.5" ></text>
</g>
<g >
<title>ConditionalCatalogCacheInitializeCache (1,102,693,012 samples, 0.01%)</title><rect x="826.0" y="309" width="0.1" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="828.99" y="319.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,803,676,597 samples, 0.04%)</title><rect x="917.3" y="405" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="920.27" y="415.5" ></text>
</g>
<g >
<title>pull_varnos_walker (9,417,438,642 samples, 0.10%)</title><rect x="974.1" y="373" width="1.1" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="977.06" y="383.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (943,094,944 samples, 0.01%)</title><rect x="261.2" y="405" width="0.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="264.19" y="415.5" ></text>
</g>
<g >
<title>list_nth (1,160,182,702 samples, 0.01%)</title><rect x="922.8" y="469" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="925.78" y="479.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (1,123,038,602 samples, 0.01%)</title><rect x="300.3" y="101" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="303.29" y="111.5" ></text>
</g>
<g >
<title>list_nth (1,033,962,007 samples, 0.01%)</title><rect x="1048.5" y="501" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="1051.47" y="511.5" ></text>
</g>
<g >
<title>dlist_init (1,032,987,377 samples, 0.01%)</title><rect x="1132.0" y="757" width="0.1" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="1134.97" y="767.5" ></text>
</g>
<g >
<title>ItemPointerGetBlockNumber (1,757,239,902 samples, 0.02%)</title><rect x="54.9" y="757" width="0.2" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="57.93" y="767.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,007,924,722 samples, 0.01%)</title><rect x="934.8" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="937.76" y="351.5" ></text>
</g>
<g >
<title>ExtendCommitTs (2,339,989,713 samples, 0.02%)</title><rect x="349.1" y="309" width="0.3" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="352.13" y="319.5" ></text>
</g>
<g >
<title>perf_ctx_disable (2,535,334,364 samples, 0.03%)</title><rect x="478.0" y="229" width="0.3" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="480.97" y="239.5" ></text>
</g>
<g >
<title>__schedule (43,458,626,869 samples, 0.46%)</title><rect x="476.4" y="293" width="5.4" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="479.41" y="303.5" ></text>
</g>
<g >
<title>RelationBuildPublicationDesc (3,950,832,897 samples, 0.04%)</title><rect x="411.8" y="421" width="0.5" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="414.85" y="431.5" ></text>
</g>
<g >
<title>table_open (28,478,317,501 samples, 0.30%)</title><rect x="865.6" y="517" width="3.5" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="868.58" y="527.5" ></text>
</g>
<g >
<title>pq_getmsgstring (15,828,403,274 samples, 0.17%)</title><rect x="1081.1" y="597" width="2.0" height="15.0" fill="rgb(233,133,31)" rx="2" ry="2" />
<text  x="1084.14" y="607.5" ></text>
</g>
<g >
<title>pg_atomic_sub_fetch_u32_impl (827,643,902 samples, 0.01%)</title><rect x="941.9" y="293" width="0.1" height="15.0" fill="rgb(225,94,22)" rx="2" ry="2" />
<text  x="944.87" y="303.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32_impl (969,354,770 samples, 0.01%)</title><rect x="354.0" y="229" width="0.2" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="357.04" y="239.5" ></text>
</g>
<g >
<title>SearchCatCache (24,815,996,488 samples, 0.26%)</title><rect x="823.3" y="341" width="3.1" height="15.0" fill="rgb(218,62,14)" rx="2" ry="2" />
<text  x="826.25" y="351.5" ></text>
</g>
<g >
<title>is_opclause (1,626,740,078 samples, 0.02%)</title><rect x="1152.1" y="757" width="0.2" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="1155.14" y="767.5" ></text>
</g>
<g >
<title>AtCommit_Memory (8,403,658,779 samples, 0.09%)</title><rect x="459.6" y="517" width="1.1" height="15.0" fill="rgb(205,3,0)" rx="2" ry="2" />
<text  x="462.62" y="527.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,011,414,068 samples, 0.01%)</title><rect x="828.3" y="325" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="831.30" y="335.5" ></text>
</g>
<g >
<title>dlist_is_empty (3,472,055,380 samples, 0.04%)</title><rect x="522.9" y="453" width="0.5" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="525.94" y="463.5" ></text>
</g>
<g >
<title>ReadCommand (859,573,036 samples, 0.01%)</title><rect x="87.6" y="757" width="0.1" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="90.57" y="767.5" ></text>
</g>
<g >
<title>heap_hot_search_buffer (63,837,808,028 samples, 0.68%)</title><rect x="300.9" y="261" width="7.9" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="303.85" y="271.5" ></text>
</g>
<g >
<title>ExecProcNode (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="501" width="1.9" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="127.59" y="511.5" ></text>
</g>
<g >
<title>pg_atomic_read_u64_impl (1,111,400,010 samples, 0.01%)</title><rect x="503.8" y="453" width="0.2" height="15.0" fill="rgb(249,203,48)" rx="2" ry="2" />
<text  x="506.83" y="463.5" ></text>
</g>
<g >
<title>rewriteTargetListIU (14,469,173,008 samples, 0.15%)</title><rect x="859.0" y="517" width="1.8" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="861.98" y="527.5" ></text>
</g>
<g >
<title>gup_fast_pgd_range (876,385,272 samples, 0.01%)</title><rect x="352.2" y="101" width="0.1" height="15.0" fill="rgb(223,86,20)" rx="2" ry="2" />
<text  x="355.18" y="111.5" ></text>
</g>
<g >
<title>do_syscall_64 (28,898,778,091 samples, 0.31%)</title><rect x="485.2" y="389" width="3.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="488.18" y="399.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,101,629,320 samples, 0.01%)</title><rect x="398.7" y="341" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="401.66" y="351.5" ></text>
</g>
<g >
<title>_bt_preprocess_keys (1,321,615,747 samples, 0.01%)</title><rect x="129.2" y="757" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="132.20" y="767.5" ></text>
</g>
<g >
<title>match_clause_to_index (26,446,532,382 samples, 0.28%)</title><rect x="1019.3" y="357" width="3.3" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="1022.25" y="367.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (2,314,758,137 samples, 0.02%)</title><rect x="1185.0" y="613" width="0.3" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1187.99" y="623.5" ></text>
</g>
<g >
<title>newNode (3,828,660,970 samples, 0.04%)</title><rect x="433.2" y="405" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="436.20" y="415.5" ></text>
</g>
<g >
<title>enable_statement_timeout (1,615,102,191 samples, 0.02%)</title><rect x="1078.8" y="565" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="1081.76" y="575.5" ></text>
</g>
<g >
<title>cost_index (149,622,298,944 samples, 1.59%)</title><rect x="998.6" y="341" width="18.7" height="15.0" fill="rgb(238,154,36)" rx="2" ry="2" />
<text  x="1001.56" y="351.5" ></text>
</g>
<g >
<title>lappend_int (3,515,063,061 samples, 0.04%)</title><rect x="449.5" y="453" width="0.5" height="15.0" fill="rgb(231,121,28)" rx="2" ry="2" />
<text  x="452.54" y="463.5" ></text>
</g>
<g >
<title>relation_close (1,495,450,986 samples, 0.02%)</title><rect x="922.9" y="453" width="0.2" height="15.0" fill="rgb(231,123,29)" rx="2" ry="2" />
<text  x="925.93" y="463.5" ></text>
</g>
<g >
<title>palloc0 (3,033,729,516 samples, 0.03%)</title><rect x="1116.7" y="693" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1119.71" y="703.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (1,647,958,216 samples, 0.02%)</title><rect x="354.2" y="229" width="0.2" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="357.16" y="239.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (1,338,761,308 samples, 0.01%)</title><rect x="123.9" y="229" width="0.2" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="126.91" y="239.5" ></text>
</g>
<g >
<title>clauselist_selectivity (76,615,185,122 samples, 0.81%)</title><rect x="1028.5" y="389" width="9.6" height="15.0" fill="rgb(232,126,30)" rx="2" ry="2" />
<text  x="1031.55" y="399.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (1,989,313,088 samples, 0.02%)</title><rect x="1015.0" y="245" width="0.2" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="1017.99" y="255.5" ></text>
</g>
<g >
<title>update_curr (15,720,843,091 samples, 0.17%)</title><rect x="167.4" y="277" width="2.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="170.45" y="287.5" ></text>
</g>
<g >
<title>IndexNext (53,015,643,311 samples, 0.56%)</title><rect x="94.7" y="421" width="6.6" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="97.67" y="431.5" ></text>
</g>
<g >
<title>lappend (3,177,205,634 samples, 0.03%)</title><rect x="859.9" y="501" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="862.85" y="511.5" ></text>
</g>
<g >
<title>AssertCouldGetRelation (2,217,726,630 samples, 0.02%)</title><rect x="30.2" y="757" width="0.3" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="33.22" y="767.5" ></text>
</g>
<g >
<title>int4eq (1,881,584,314 samples, 0.02%)</title><rect x="1158.0" y="229" width="0.2" height="15.0" fill="rgb(206,9,2)" rx="2" ry="2" />
<text  x="1160.97" y="239.5" ></text>
</g>
<g >
<title>all (9,437,180,925,478 samples, 100%)</title><rect x="10.0" y="789" width="1180.0" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="13.00" y="799.5" ></text>
</g>
<g >
<title>AllocSetDelete (900,641,585 samples, 0.01%)</title><rect x="28.7" y="757" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="31.67" y="767.5" ></text>
</g>
<g >
<title>RelationDecrementReferenceCount (1,370,651,162 samples, 0.01%)</title><rect x="239.6" y="405" width="0.2" height="15.0" fill="rgb(214,44,10)" rx="2" ry="2" />
<text  x="242.61" y="415.5" ></text>
</g>
<g >
<title>clause_selectivity_ext (803,901,079 samples, 0.01%)</title><rect x="126.6" y="373" width="0.1" height="15.0" fill="rgb(210,23,5)" rx="2" ry="2" />
<text  x="129.56" y="383.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (26,287,812,967 samples, 0.28%)</title><rect x="805.2" y="389" width="3.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="808.21" y="399.5" ></text>
</g>
<g >
<title>palloc (1,053,949,832 samples, 0.01%)</title><rect x="451.3" y="453" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="454.28" y="463.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (1,103,005,298 samples, 0.01%)</title><rect x="791.7" y="517" width="0.1" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="794.69" y="527.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,946,746,451 samples, 0.02%)</title><rect x="517.9" y="421" width="0.3" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="520.91" y="431.5" ></text>
</g>
<g >
<title>palloc0 (1,358,577,473 samples, 0.01%)</title><rect x="1035.3" y="197" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1038.32" y="207.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,243,490,808 samples, 0.01%)</title><rect x="354.4" y="245" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="357.38" y="255.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="437" width="1.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="127.59" y="447.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,321,623,581 samples, 0.01%)</title><rect x="1034.5" y="149" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1037.50" y="159.5" ></text>
</g>
<g >
<title>newNode (1,650,568,725 samples, 0.02%)</title><rect x="810.4" y="453" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="813.41" y="463.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,262,395,421 samples, 0.01%)</title><rect x="363.2" y="325" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="366.22" y="335.5" ></text>
</g>
<g >
<title>intel_thermal_interrupt (1,449,430,375 samples, 0.02%)</title><rect x="751.8" y="485" width="0.2" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="754.82" y="495.5" ></text>
</g>
<g >
<title>assign_query_collations (56,564,465,563 samples, 0.60%)</title><rect x="801.5" y="485" width="7.0" height="15.0" fill="rgb(236,145,34)" rx="2" ry="2" />
<text  x="804.46" y="495.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (17,085,190,311 samples, 0.18%)</title><rect x="802.5" y="405" width="2.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="805.48" y="415.5" ></text>
</g>
<g >
<title>LWLockReleaseInternal (3,701,218,692 samples, 0.04%)</title><rect x="382.1" y="261" width="0.4" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="385.06" y="271.5" ></text>
</g>
<g >
<title>ExecInterpExprStillValid (17,020,742,182 samples, 0.18%)</title><rect x="266.7" y="373" width="2.1" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="269.66" y="383.5" ></text>
</g>
<g >
<title>palloc (2,739,262,524 samples, 0.03%)</title><rect x="1165.3" y="757" width="0.4" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1168.32" y="767.5" ></text>
</g>
<g >
<title>palloc (1,518,647,351 samples, 0.02%)</title><rect x="944.3" y="373" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="947.27" y="383.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,183,461,259 samples, 0.01%)</title><rect x="835.8" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="838.83" y="351.5" ></text>
</g>
<g >
<title>populate_compact_attribute (2,471,392,037 samples, 0.03%)</title><rect x="437.2" y="357" width="0.3" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="440.17" y="367.5" ></text>
</g>
<g >
<title>lappend (2,972,207,528 samples, 0.03%)</title><rect x="104.2" y="437" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="107.23" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,017,943,440 samples, 0.02%)</title><rect x="879.6" y="421" width="0.3" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="882.61" y="431.5" ></text>
</g>
<g >
<title>enforce_generic_type_consistency (873,422,160 samples, 0.01%)</title><rect x="834.6" y="373" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="837.59" y="383.5" ></text>
</g>
<g >
<title>LWLockRelease (1,694,285,098 samples, 0.02%)</title><rect x="1078.4" y="517" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="1081.41" y="527.5" ></text>
</g>
<g >
<title>postmaster_child_launch (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="661" width="2.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="127.59" y="671.5" ></text>
</g>
<g >
<title>newNode (3,819,206,282 samples, 0.04%)</title><rect x="1023.9" y="389" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="1026.89" y="399.5" ></text>
</g>
<g >
<title>pg_comp_crc32c_sse42 (5,265,966,171 samples, 0.06%)</title><rect x="383.9" y="309" width="0.7" height="15.0" fill="rgb(244,183,43)" rx="2" ry="2" />
<text  x="386.91" y="319.5" ></text>
</g>
<g >
<title>assign_collations_walker (1,065,285,428 samples, 0.01%)</title><rect x="804.4" y="373" width="0.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="807.43" y="383.5" ></text>
</g>
<g >
<title>ResourceOwnerRemember (1,057,917,305 samples, 0.01%)</title><rect x="826.2" y="293" width="0.1" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="829.16" y="303.5" ></text>
</g>
<g >
<title>oper (36,784,736,664 samples, 0.39%)</title><rect x="848.1" y="405" width="4.6" height="15.0" fill="rgb(234,133,32)" rx="2" ry="2" />
<text  x="851.13" y="415.5" ></text>
</g>
<g >
<title>LWLockRelease (848,245,811 samples, 0.01%)</title><rect x="100.5" y="181" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="103.55" y="191.5" ></text>
</g>
<g >
<title>make_op (35,222,304,753 samples, 0.37%)</title><rect x="834.3" y="389" width="4.4" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="837.29" y="399.5" ></text>
</g>
<g >
<title>sentinel_ok (4,934,687,721 samples, 0.05%)</title><rect x="146.1" y="533" width="0.6" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="149.10" y="543.5" ></text>
</g>
<g >
<title>SyncRepWaitForLSN (2,197,019,029 samples, 0.02%)</title><rect x="469.3" y="501" width="0.3" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="472.31" y="511.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (5,982,816,431 samples, 0.06%)</title><rect x="350.7" y="277" width="0.8" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="353.71" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (985,898,650 samples, 0.01%)</title><rect x="869.3" y="485" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="872.32" y="495.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,135,739,602 samples, 0.01%)</title><rect x="363.0" y="325" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="366.01" y="335.5" ></text>
</g>
<g >
<title>ReleaseCatCache (922,291,850 samples, 0.01%)</title><rect x="916.5" y="357" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="919.48" y="367.5" ></text>
</g>
<g >
<title>core_yylex_init (6,486,342,646 samples, 0.07%)</title><rect x="871.5" y="533" width="0.8" height="15.0" fill="rgb(235,140,33)" rx="2" ry="2" />
<text  x="874.54" y="543.5" ></text>
</g>
<g >
<title>lappend_oid (2,272,393,056 samples, 0.02%)</title><rect x="897.6" y="469" width="0.3" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="900.59" y="479.5" ></text>
</g>
<g >
<title>CheckReadBuffersOperation (1,768,562,809 samples, 0.02%)</title><rect x="84.7" y="197" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="87.72" y="207.5" ></text>
</g>
<g >
<title>LWLockRelease (2,305,080,865 samples, 0.02%)</title><rect x="518.4" y="453" width="0.3" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="521.43" y="463.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,274,531,711 samples, 0.01%)</title><rect x="816.1" y="405" width="0.1" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="819.08" y="415.5" ></text>
</g>
<g >
<title>palloc0 (1,941,713,984 samples, 0.02%)</title><rect x="808.6" y="453" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="811.60" y="463.5" ></text>
</g>
<g >
<title>main (4,239,417,954 samples, 0.04%)</title><rect x="1157.9" y="757" width="0.6" height="15.0" fill="rgb(243,179,42)" rx="2" ry="2" />
<text  x="1160.94" y="767.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (840,027,331 samples, 0.01%)</title><rect x="279.9" y="277" width="0.1" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="282.86" y="287.5" ></text>
</g>
<g >
<title>LWLockAcquire (4,988,498,643 samples, 0.05%)</title><rect x="353.9" y="277" width="0.6" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="356.91" y="287.5" ></text>
</g>
<g >
<title>__rseq_handle_notify_resume (6,135,881,421 samples, 0.07%)</title><rect x="173.4" y="421" width="0.7" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="176.35" y="431.5" ></text>
</g>
<g >
<title>LockRelationOid (33,865,052,583 samples, 0.36%)</title><rect x="818.0" y="405" width="4.3" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="821.02" y="415.5" ></text>
</g>
<g >
<title>obj_cgroup_charge_account (1,010,375,605 samples, 0.01%)</title><rect x="197.3" y="325" width="0.2" height="15.0" fill="rgb(219,65,15)" rx="2" ry="2" />
<text  x="200.34" y="335.5" ></text>
</g>
<g >
<title>_bt_compare (4,164,144,568 samples, 0.04%)</title><rect x="337.2" y="229" width="0.5" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="340.19" y="239.5" ></text>
</g>
<g >
<title>pull_varnos_walker (5,033,029,222 samples, 0.05%)</title><rect x="974.6" y="325" width="0.6" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="977.61" y="335.5" ></text>
</g>
<g >
<title>bms_add_member (893,749,827 samples, 0.01%)</title><rect x="840.4" y="293" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="843.35" y="303.5" ></text>
</g>
<g >
<title>__strlen_avx2 (1,515,015,409 samples, 0.02%)</title><rect x="1073.7" y="565" width="0.2" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="1076.74" y="575.5" ></text>
</g>
<g >
<title>do_syscall_64 (1,873,122,891 samples, 0.02%)</title><rect x="382.2" y="181" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="385.18" y="191.5" ></text>
</g>
<g >
<title>max_parallel_hazard_checker (5,759,993,287 samples, 0.06%)</title><rect x="916.4" y="405" width="0.7" height="15.0" fill="rgb(228,110,26)" rx="2" ry="2" />
<text  x="919.35" y="415.5" ></text>
</g>
<g >
<title>att_isnull (1,832,970,078 samples, 0.02%)</title><rect x="1008.0" y="197" width="0.3" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="1011.03" y="207.5" ></text>
</g>
<g >
<title>ExecProcNode (1,101,721,034,957 samples, 11.67%)</title><rect x="264.3" y="485" width="137.7" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="267.25" y="495.5" >ExecProcNode</text>
</g>
<g >
<title>LWLockAttemptLock (1,335,873,097 samples, 0.01%)</title><rect x="339.6" y="181" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="342.56" y="191.5" ></text>
</g>
<g >
<title>all_rows_selectable (9,725,600,247 samples, 0.10%)</title><rect x="1034.1" y="213" width="1.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="1037.07" y="223.5" ></text>
</g>
<g >
<title>hash_bytes (12,024,925,687 samples, 0.13%)</title><rect x="1141.6" y="757" width="1.5" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="1144.64" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (800,370,399 samples, 0.01%)</title><rect x="435.5" y="293" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="438.50" y="303.5" ></text>
</g>
<g >
<title>CheckRelationLockedByMe (13,134,506,382 samples, 0.14%)</title><rect x="444.1" y="389" width="1.6" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="447.09" y="399.5" ></text>
</g>
<g >
<title>ExecModifyTable (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="469" width="1.9" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="127.59" y="479.5" ></text>
</g>
<g >
<title>convert_saop_to_hashed_saop (6,651,126,964 samples, 0.07%)</title><rect x="1052.3" y="485" width="0.8" height="15.0" fill="rgb(243,175,41)" rx="2" ry="2" />
<text  x="1055.28" y="495.5" ></text>
</g>
<g >
<title>RelationClose (1,498,828,738 samples, 0.02%)</title><rect x="939.9" y="389" width="0.2" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="942.91" y="399.5" ></text>
</g>
<g >
<title>futex_wait (925,058,534 samples, 0.01%)</title><rect x="296.8" y="117" width="0.2" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="299.85" y="127.5" ></text>
</g>
<g >
<title>relation_open (16,566,528,540 samples, 0.18%)</title><rect x="923.2" y="453" width="2.0" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="926.16" y="463.5" ></text>
</g>
<g >
<title>index_getattr (3,243,776,084 samples, 0.03%)</title><rect x="125.6" y="229" width="0.4" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="128.59" y="239.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,096,285,574 samples, 0.01%)</title><rect x="425.2" y="325" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="428.17" y="335.5" ></text>
</g>
<g >
<title>__x64_sys_futex (977,774,889 samples, 0.01%)</title><rect x="363.4" y="245" width="0.1" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="366.40" y="255.5" ></text>
</g>
<g >
<title>__futex_wait (969,987,414 samples, 0.01%)</title><rect x="1147.6" y="485" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="1150.60" y="495.5" ></text>
</g>
<g >
<title>newNode (4,336,646,713 samples, 0.05%)</title><rect x="424.1" y="357" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="427.08" y="367.5" ></text>
</g>
<g >
<title>pollwake (60,187,637,733 samples, 0.64%)</title><rect x="198.2" y="357" width="7.5" height="15.0" fill="rgb(238,154,37)" rx="2" ry="2" />
<text  x="201.16" y="367.5" ></text>
</g>
<g >
<title>do_syscall_64 (66,255,083,223 samples, 0.70%)</title><rect x="176.2" y="469" width="8.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="179.21" y="479.5" ></text>
</g>
<g >
<title>index_getnext_slot (379,355,462,146 samples, 4.02%)</title><rect x="293.4" y="325" width="47.4" height="15.0" fill="rgb(211,32,7)" rx="2" ry="2" />
<text  x="296.35" y="335.5" >inde..</text>
</g>
<g >
<title>hash_search (3,826,154,442 samples, 0.04%)</title><rect x="395.4" y="341" width="0.5" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="398.40" y="351.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (1,038,993,533 samples, 0.01%)</title><rect x="184.3" y="453" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="187.34" y="463.5" ></text>
</g>
<g >
<title>AllocSetCheck (3,298,438,532 samples, 0.03%)</title><rect x="133.0" y="485" width="0.4" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="136.03" y="495.5" ></text>
</g>
<g >
<title>palloc0 (3,682,504,019 samples, 0.04%)</title><rect x="275.0" y="389" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="278.00" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,471,158,299 samples, 0.02%)</title><rect x="388.1" y="277" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="391.11" y="287.5" ></text>
</g>
<g >
<title>SearchSysCache (6,758,039,845 samples, 0.07%)</title><rect x="1021.7" y="277" width="0.8" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="1024.66" y="287.5" ></text>
</g>
<g >
<title>expression_returns_set_walker (9,583,808,969 samples, 0.10%)</title><rect x="842.6" y="437" width="1.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="845.60" y="447.5" ></text>
</g>
<g >
<title>ReleaseSysCache (965,461,154 samples, 0.01%)</title><rect x="1009.4" y="277" width="0.2" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1012.44" y="287.5" ></text>
</g>
<g >
<title>ExecReadyExpr (2,968,485,550 samples, 0.03%)</title><rect x="432.4" y="405" width="0.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="435.42" y="415.5" ></text>
</g>
<g >
<title>create_plan (93,627,657,837 samples, 0.99%)</title><rect x="882.3" y="517" width="11.7" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="885.26" y="527.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,415,223,610 samples, 0.04%)</title><rect x="835.0" y="325" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="838.00" y="335.5" ></text>
</g>
<g >
<title>LWLockAcquire (3,244,056,214 samples, 0.03%)</title><rect x="1147.3" y="629" width="0.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="1150.34" y="639.5" ></text>
</g>
<g >
<title>ReadBuffer_common (18,229,004,061 samples, 0.19%)</title><rect x="366.4" y="325" width="2.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="369.44" y="335.5" ></text>
</g>
<g >
<title>CheckExprStillValid (20,791,200,822 samples, 0.22%)</title><rect x="283.4" y="293" width="2.6" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="286.37" y="303.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,506,470,732 samples, 0.02%)</title><rect x="423.0" y="325" width="0.1" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="425.96" y="335.5" ></text>
</g>
<g >
<title>bms_copy (1,267,344,313 samples, 0.01%)</title><rect x="995.3" y="341" width="0.2" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="998.35" y="351.5" ></text>
</g>
<g >
<title>new_list (1,493,585,974 samples, 0.02%)</title><rect x="104.4" y="421" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="107.38" y="431.5" ></text>
</g>
<g >
<title>attnumTypeId (911,076,858 samples, 0.01%)</title><rect x="841.3" y="437" width="0.1" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="844.31" y="447.5" ></text>
</g>
<g >
<title>PostgresMain (24,212,439,197 samples, 0.26%)</title><rect x="82.6" y="677" width="3.0" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="85.62" y="687.5" ></text>
</g>
<g >
<title>ExecModifyTable (804,184,285 samples, 0.01%)</title><rect x="44.6" y="757" width="0.1" height="15.0" fill="rgb(218,64,15)" rx="2" ry="2" />
<text  x="47.63" y="767.5" ></text>
</g>
<g >
<title>ep_send_events (23,952,390,286 samples, 0.25%)</title><rect x="155.2" y="389" width="3.0" height="15.0" fill="rgb(238,153,36)" rx="2" ry="2" />
<text  x="158.20" y="399.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (845,519,118 samples, 0.01%)</title><rect x="162.1" y="277" width="0.1" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="165.08" y="287.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (4,304,820,506 samples, 0.05%)</title><rect x="517.8" y="437" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="520.80" y="447.5" ></text>
</g>
<g >
<title>pg_client_to_server (13,536,346,552 samples, 0.14%)</title><rect x="1081.4" y="581" width="1.7" height="15.0" fill="rgb(206,6,1)" rx="2" ry="2" />
<text  x="1084.39" y="591.5" ></text>
</g>
<g >
<title>__strlen_avx2 (961,689,789 samples, 0.01%)</title><rect x="212.3" y="517" width="0.1" height="15.0" fill="rgb(246,188,45)" rx="2" ry="2" />
<text  x="215.27" y="527.5" ></text>
</g>
<g >
<title>tts_buffer_heap_clear (1,402,482,783 samples, 0.01%)</title><rect x="1188.0" y="757" width="0.2" height="15.0" fill="rgb(208,14,3)" rx="2" ry="2" />
<text  x="1191.02" y="767.5" ></text>
</g>
<g >
<title>fmgr_info (2,100,210,825 samples, 0.02%)</title><rect x="431.5" y="373" width="0.2" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="434.48" y="383.5" ></text>
</g>
<g >
<title>btrescan (5,473,261,993 samples, 0.06%)</title><rect x="341.0" y="309" width="0.7" height="15.0" fill="rgb(222,81,19)" rx="2" ry="2" />
<text  x="344.03" y="319.5" ></text>
</g>
<g >
<title>core_yy_switch_to_buffer (4,732,386,711 samples, 0.05%)</title><rect x="870.7" y="517" width="0.6" height="15.0" fill="rgb(237,149,35)" rx="2" ry="2" />
<text  x="873.73" y="527.5" ></text>
</g>
<g >
<title>preprocess_expression (22,615,945,539 samples, 0.24%)</title><rect x="1052.0" y="501" width="2.8" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1055.02" y="511.5" ></text>
</g>
<g >
<title>fetch_upper_rel (13,842,616,851 samples, 0.15%)</title><rect x="913.4" y="485" width="1.7" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="916.40" y="495.5" ></text>
</g>
<g >
<title>gup_fast (2,458,698,564 samples, 0.03%)</title><rect x="485.9" y="293" width="0.3" height="15.0" fill="rgb(227,103,24)" rx="2" ry="2" />
<text  x="488.89" y="303.5" ></text>
</g>
<g >
<title>UnregisterSnapshot (2,055,851,739 samples, 0.02%)</title><rect x="262.4" y="501" width="0.3" height="15.0" fill="rgb(212,33,7)" rx="2" ry="2" />
<text  x="265.42" y="511.5" ></text>
</g>
<g >
<title>MarkBufferDirty (980,575,177 samples, 0.01%)</title><rect x="58.7" y="757" width="0.2" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="61.74" y="767.5" ></text>
</g>
<g >
<title>newNode (3,909,308,646 samples, 0.04%)</title><rect x="970.8" y="357" width="0.5" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="973.83" y="367.5" ></text>
</g>
<g >
<title>palloc0 (4,461,571,650 samples, 0.05%)</title><rect x="885.2" y="389" width="0.6" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="888.24" y="399.5" ></text>
</g>
<g >
<title>BufTableLookup (2,364,759,180 samples, 0.03%)</title><rect x="400.1" y="277" width="0.3" height="15.0" fill="rgb(224,89,21)" rx="2" ry="2" />
<text  x="403.13" y="287.5" ></text>
</g>
<g >
<title>ExecutePlan (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="549" width="0.5" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1160.94" y="559.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (1,687,286,754 samples, 0.02%)</title><rect x="470.3" y="373" width="0.3" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="473.35" y="383.5" ></text>
</g>
<g >
<title>AllocSetAllocFromNewBlock (5,645,977,995 samples, 0.06%)</title><rect x="912.2" y="421" width="0.7" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="915.16" y="431.5" ></text>
</g>
<g >
<title>new_list (5,464,318,773 samples, 0.06%)</title><rect x="918.1" y="469" width="0.7" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="921.14" y="479.5" ></text>
</g>
<g >
<title>ParallelContextActive (896,798,061 samples, 0.01%)</title><rect x="81.4" y="757" width="0.1" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="84.43" y="767.5" ></text>
</g>
<g >
<title>arch_exit_to_user_mode_prepare.isra.0 (6,183,434,695 samples, 0.07%)</title><rect x="172.3" y="437" width="0.8" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="175.28" y="447.5" ></text>
</g>
<g >
<title>standard_planner (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="581" width="0.1" height="15.0" fill="rgb(207,11,2)" rx="2" ry="2" />
<text  x="1161.36" y="591.5" ></text>
</g>
<g >
<title>pg_rotate_left32 (11,500,004,180 samples, 0.12%)</title><rect x="1141.7" y="741" width="1.4" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="1144.70" y="751.5" ></text>
</g>
<g >
<title>CheckExprStillValid (801,493,887 samples, 0.01%)</title><rect x="38.2" y="757" width="0.1" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="41.20" y="767.5" ></text>
</g>
<g >
<title>BackendStartup (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="677" width="2.2" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="127.59" y="687.5" ></text>
</g>
<g >
<title>palloc0 (1,825,198,272 samples, 0.02%)</title><rect x="968.5" y="293" width="0.2" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="971.46" y="303.5" ></text>
</g>
<g >
<title>MemoryContextAllocZero (3,066,592,189 samples, 0.03%)</title><rect x="387.5" y="309" width="0.4" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="390.48" y="319.5" ></text>
</g>
<g >
<title>task_tick_fair (1,492,204,771 samples, 0.02%)</title><rect x="751.4" y="405" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="754.44" y="415.5" ></text>
</g>
<g >
<title>BackendMain (17,104,611,586 samples, 0.18%)</title><rect x="122.5" y="661" width="2.1" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="125.45" y="671.5" ></text>
</g>
<g >
<title>AllocSetAlloc (988,808,305 samples, 0.01%)</title><rect x="391.5" y="341" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="394.50" y="351.5" ></text>
</g>
<g >
<title>expression_tree_mutator_impl (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="357" width="0.1" height="15.0" fill="rgb(207,12,3)" rx="2" ry="2" />
<text  x="88.40" y="367.5" ></text>
</g>
<g >
<title>palloc0 (2,112,166,845 samples, 0.02%)</title><rect x="921.1" y="405" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="924.13" y="415.5" ></text>
</g>
<g >
<title>exprCollation (1,839,218,713 samples, 0.02%)</title><rect x="806.6" y="309" width="0.2" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="809.61" y="319.5" ></text>
</g>
<g >
<title>check_log_duration (1,212,663,747 samples, 0.01%)</title><rect x="1126.5" y="757" width="0.1" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="1129.46" y="767.5" ></text>
</g>
<g >
<title>StartReadBuffersImpl (11,970,693,185 samples, 0.13%)</title><rect x="83.2" y="213" width="1.5" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="86.22" y="223.5" ></text>
</g>
<g >
<title>palloc0 (2,484,149,880 samples, 0.03%)</title><rect x="922.0" y="421" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="924.97" y="431.5" ></text>
</g>
<g >
<title>palloc (1,454,168,223 samples, 0.02%)</title><rect x="1054.4" y="405" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1057.39" y="415.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (874,999,553 samples, 0.01%)</title><rect x="973.4" y="341" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="976.41" y="351.5" ></text>
</g>
<g >
<title>inline_function (1,700,520,170 samples, 0.02%)</title><rect x="104.6" y="453" width="0.2" height="15.0" fill="rgb(251,215,51)" rx="2" ry="2" />
<text  x="107.61" y="463.5" ></text>
</g>
<g >
<title>folio_mark_dirty (2,182,614,969 samples, 0.02%)</title><rect x="500.2" y="341" width="0.3" height="15.0" fill="rgb(217,55,13)" rx="2" ry="2" />
<text  x="503.22" y="351.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (949,362,941 samples, 0.01%)</title><rect x="961.3" y="293" width="0.1" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="964.30" y="303.5" ></text>
</g>
<g >
<title>exprTypmod (1,260,713,877 samples, 0.01%)</title><rect x="842.0" y="405" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="845.03" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (2,451,503,583 samples, 0.03%)</title><rect x="432.0" y="357" width="0.4" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="435.05" y="367.5" ></text>
</g>
<g >
<title>do_syscall_64 (4,443,211,167 samples, 0.05%)</title><rect x="932.6" y="293" width="0.5" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="935.55" y="303.5" ></text>
</g>
<g >
<title>coerce_to_target_type (1,162,104,662 samples, 0.01%)</title><rect x="1128.1" y="757" width="0.1" height="15.0" fill="rgb(232,124,29)" rx="2" ry="2" />
<text  x="1131.09" y="767.5" ></text>
</g>
<g >
<title>expr_setup_walker (8,383,253,194 samples, 0.09%)</title><rect x="417.7" y="309" width="1.0" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="420.70" y="319.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32_impl (2,634,789,378 samples, 0.03%)</title><rect x="96.4" y="133" width="0.3" height="15.0" fill="rgb(231,122,29)" rx="2" ry="2" />
<text  x="99.41" y="143.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64 (1,509,966,794 samples, 0.02%)</title><rect x="936.7" y="213" width="0.2" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="939.70" y="223.5" ></text>
</g>
<g >
<title>__x64_sys_pwrite64 (62,559,606,586 samples, 0.66%)</title><rect x="493.6" y="421" width="7.8" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="496.57" y="431.5" ></text>
</g>
<g >
<title>SearchSysCache1 (8,013,966,004 samples, 0.08%)</title><rect x="435.6" y="357" width="1.0" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="438.61" y="367.5" ></text>
</g>
<g >
<title>_raw_spin_lock (9,911,313,283 samples, 0.11%)</title><rect x="486.6" y="261" width="1.2" height="15.0" fill="rgb(239,160,38)" rx="2" ry="2" />
<text  x="489.57" y="271.5" ></text>
</g>
<g >
<title>table_tuple_fetch_row_version (38,377,672,695 samples, 0.41%)</title><rect x="397.0" y="437" width="4.8" height="15.0" fill="rgb(226,99,23)" rx="2" ry="2" />
<text  x="399.98" y="447.5" ></text>
</g>
<g >
<title>palloc (2,256,661,378 samples, 0.02%)</title><rect x="879.6" y="437" width="0.3" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="882.60" y="447.5" ></text>
</g>
<g >
<title>CatalogCacheComputeHashValue (945,077,666 samples, 0.01%)</title><rect x="103.3" y="405" width="0.1" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="106.27" y="415.5" ></text>
</g>
<g >
<title>ResourceOwnerRememberSnapshot (2,156,427,892 samples, 0.02%)</title><rect x="234.1" y="485" width="0.2" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="237.06" y="495.5" ></text>
</g>
<g >
<title>distribute_restrictinfo_to_rels (8,291,226,765 samples, 0.09%)</title><rect x="981.3" y="437" width="1.0" height="15.0" fill="rgb(205,1,0)" rx="2" ry="2" />
<text  x="984.29" y="447.5" ></text>
</g>
<g >
<title>entry_SYSCALL_64_after_hwframe (67,968,756,483 samples, 0.72%)</title><rect x="475.6" y="421" width="8.5" height="15.0" fill="rgb(218,63,15)" rx="2" ry="2" />
<text  x="478.56" y="431.5" ></text>
</g>
<g >
<title>_bt_readfirstpage (2,568,695,668 samples, 0.03%)</title><rect x="1157.9" y="309" width="0.4" height="15.0" fill="rgb(208,16,3)" rx="2" ry="2" />
<text  x="1160.94" y="319.5" ></text>
</g>
<g >
<title>hash_initial_lookup (1,507,719,097 samples, 0.02%)</title><rect x="830.6" y="357" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="833.62" y="367.5" ></text>
</g>
<g >
<title>ReadBuffer_common (27,553,359,179 samples, 0.29%)</title><rect x="297.4" y="213" width="3.4" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="300.36" y="223.5" ></text>
</g>
<g >
<title>filemap_get_entry (8,980,854,231 samples, 0.10%)</title><rect x="498.8" y="325" width="1.1" height="15.0" fill="rgb(246,193,46)" rx="2" ry="2" />
<text  x="501.82" y="335.5" ></text>
</g>
<g >
<title>ProcArrayEndTransaction (15,802,351,036 samples, 0.17%)</title><rect x="466.0" y="517" width="2.0" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="469.00" y="527.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,266,383,089 samples, 0.01%)</title><rect x="922.1" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="925.12" y="415.5" ></text>
</g>
<g >
<title>list_free (2,162,724,501 samples, 0.02%)</title><rect x="993.8" y="341" width="0.3" height="15.0" fill="rgb(231,121,29)" rx="2" ry="2" />
<text  x="996.85" y="351.5" ></text>
</g>
<g >
<title>FullTransactionIdNewer (1,113,284,104 samples, 0.01%)</title><rect x="219.9" y="549" width="0.1" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="222.85" y="559.5" ></text>
</g>
<g >
<title>pgstat_tracks_io_op (1,249,093,794 samples, 0.01%)</title><rect x="98.5" y="165" width="0.1" height="15.0" fill="rgb(243,177,42)" rx="2" ry="2" />
<text  x="101.46" y="175.5" ></text>
</g>
<g >
<title>ReleaseSysCache (1,013,532,107 samples, 0.01%)</title><rect x="973.4" y="373" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="976.40" y="383.5" ></text>
</g>
<g >
<title>LockRelease (871,229,770 samples, 0.01%)</title><rect x="58.3" y="757" width="0.1" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="61.28" y="767.5" ></text>
</g>
<g >
<title>HeapTupleNoNulls (1,240,391,429 samples, 0.01%)</title><rect x="51.8" y="757" width="0.1" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="54.79" y="767.5" ></text>
</g>
<g >
<title>bms_is_subset (2,368,670,661 samples, 0.03%)</title><rect x="951.9" y="437" width="0.3" height="15.0" fill="rgb(208,15,3)" rx="2" ry="2" />
<text  x="954.90" y="447.5" ></text>
</g>
<g >
<title>IsTransactionState (1,487,252,980 samples, 0.02%)</title><rect x="54.6" y="757" width="0.2" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="57.63" y="767.5" ></text>
</g>
<g >
<title>ExecProcNode (53,501,250,861 samples, 0.57%)</title><rect x="94.7" y="565" width="6.7" height="15.0" fill="rgb(221,75,18)" rx="2" ry="2" />
<text  x="97.67" y="575.5" ></text>
</g>
<g >
<title>SearchSysCache3 (5,978,254,728 samples, 0.06%)</title><rect x="1014.6" y="293" width="0.8" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="1017.60" y="303.5" ></text>
</g>
<g >
<title>newNode (5,369,154,466 samples, 0.06%)</title><rect x="816.4" y="453" width="0.7" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="819.40" y="463.5" ></text>
</g>
<g >
<title>ComputeXidHorizons (1,810,538,987 samples, 0.02%)</title><rect x="310.3" y="197" width="0.2" height="15.0" fill="rgb(230,115,27)" rx="2" ry="2" />
<text  x="313.28" y="207.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (6,321,887,487 samples, 0.07%)</title><rect x="943.3" y="373" width="0.8" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="946.29" y="383.5" ></text>
</g>
<g >
<title>bms_add_member (2,461,491,216 samples, 0.03%)</title><rect x="896.9" y="469" width="0.4" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="899.95" y="479.5" ></text>
</g>
<g >
<title>SearchCatCache1 (2,953,456,826 samples, 0.03%)</title><rect x="960.5" y="277" width="0.4" height="15.0" fill="rgb(252,216,51)" rx="2" ry="2" />
<text  x="963.51" y="287.5" ></text>
</g>
<g >
<title>AtStart_ResourceOwner (14,328,249,661 samples, 0.15%)</title><rect x="1075.6" y="533" width="1.8" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="1078.61" y="543.5" ></text>
</g>
<g >
<title>lappend (3,132,139,945 samples, 0.03%)</title><rect x="449.1" y="453" width="0.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="452.15" y="463.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturn (17,427,862,636 samples, 0.18%)</title><rect x="266.6" y="389" width="2.2" height="15.0" fill="rgb(232,125,30)" rx="2" ry="2" />
<text  x="269.61" y="399.5" ></text>
</g>
<g >
<title>slot_getsomeattrs_int (1,014,924,226 samples, 0.01%)</title><rect x="1183.0" y="757" width="0.1" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="1185.96" y="767.5" ></text>
</g>
<g >
<title>make_restrictinfo (26,146,899,974 samples, 0.28%)</title><rect x="965.6" y="405" width="3.3" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="968.58" y="415.5" ></text>
</g>
<g >
<title>parserOpenTable (111,444,993,224 samples, 1.18%)</title><rect x="817.1" y="469" width="14.0" height="15.0" fill="rgb(245,188,45)" rx="2" ry="2" />
<text  x="820.13" y="479.5" ></text>
</g>
<g >
<title>makeConst (3,380,901,017 samples, 0.04%)</title><rect x="853.4" y="389" width="0.5" height="15.0" fill="rgb(236,144,34)" rx="2" ry="2" />
<text  x="856.45" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,679,399,002 samples, 0.02%)</title><rect x="810.2" y="405" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="813.16" y="415.5" ></text>
</g>
<g >
<title>btgettuple (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="325" width="1.7" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="125.45" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,207,640,726 samples, 0.01%)</title><rect x="453.8" y="501" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="456.84" y="511.5" ></text>
</g>
<g >
<title>ProcArrayGroupClearXid (3,455,981,332 samples, 0.04%)</title><rect x="467.5" y="501" width="0.5" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="470.54" y="511.5" ></text>
</g>
<g >
<title>AtEOXact_MultiXact (1,172,569,125 samples, 0.01%)</title><rect x="31.8" y="757" width="0.2" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="34.83" y="767.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,033,276,146 samples, 0.01%)</title><rect x="467.7" y="453" width="0.1" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="470.66" y="463.5" ></text>
</g>
<g >
<title>GetPortalByName (9,022,148,914 samples, 0.10%)</title><rect x="211.5" y="565" width="1.1" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="214.51" y="575.5" ></text>
</g>
<g >
<title>__task_rq_lock (1,347,841,029 samples, 0.01%)</title><rect x="199.0" y="325" width="0.2" height="15.0" fill="rgb(236,146,35)" rx="2" ry="2" />
<text  x="202.03" y="335.5" ></text>
</g>
<g >
<title>AllocSetAlloc (909,456,124 samples, 0.01%)</title><rect x="896.5" y="405" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="899.47" y="415.5" ></text>
</g>
<g >
<title>unix_stream_read_actor (10,056,087,909 samples, 0.11%)</title><rect x="183.1" y="373" width="1.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="186.09" y="383.5" ></text>
</g>
<g >
<title>palloc0 (3,245,634,575 samples, 0.03%)</title><rect x="1017.4" y="325" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1020.37" y="335.5" ></text>
</g>
<g >
<title>__x64_sys_futex (4,693,256,224 samples, 0.05%)</title><rect x="350.8" y="213" width="0.6" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="353.79" y="223.5" ></text>
</g>
<g >
<title>MemoryContextAlloc (1,781,454,928 samples, 0.02%)</title><rect x="355.3" y="277" width="0.3" height="15.0" fill="rgb(215,49,11)" rx="2" ry="2" />
<text  x="358.33" y="287.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,775,079,742 samples, 0.02%)</title><rect x="1044.6" y="437" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1047.60" y="447.5" ></text>
</g>
<g >
<title>ConditionVariableBroadcast (4,555,106,594 samples, 0.05%)</title><rect x="491.7" y="453" width="0.5" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="494.66" y="463.5" ></text>
</g>
<g >
<title>exprTypmod (1,956,737,043 samples, 0.02%)</title><rect x="1035.7" y="229" width="0.2" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="1038.70" y="239.5" ></text>
</g>
<g >
<title>ReadBuffer_common (34,932,396,469 samples, 0.37%)</title><rect x="94.7" y="261" width="4.3" height="15.0" fill="rgb(213,40,9)" rx="2" ry="2" />
<text  x="97.67" y="271.5" ></text>
</g>
<g >
<title>FullTransactionIdAdvance (997,393,370 samples, 0.01%)</title><rect x="47.5" y="757" width="0.1" height="15.0" fill="rgb(254,228,54)" rx="2" ry="2" />
<text  x="50.49" y="767.5" ></text>
</g>
<g >
<title>ExecutorEnd (223,423,837,198 samples, 2.37%)</title><rect x="234.8" y="533" width="27.9" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="237.75" y="543.5" >E..</text>
</g>
<g >
<title>check_heap_object (4,496,595,107 samples, 0.05%)</title><rect x="193.1" y="373" width="0.5" height="15.0" fill="rgb(241,165,39)" rx="2" ry="2" />
<text  x="196.05" y="383.5" ></text>
</g>
<g >
<title>BufferIsLockedByMe (1,206,584,364 samples, 0.01%)</title><rect x="1146.7" y="661" width="0.2" height="15.0" fill="rgb(215,46,11)" rx="2" ry="2" />
<text  x="1149.71" y="671.5" ></text>
</g>
<g >
<title>palloc (1,288,305,130 samples, 0.01%)</title><rect x="1019.6" y="309" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="1022.58" y="319.5" ></text>
</g>
<g >
<title>update_se (6,385,522,766 samples, 0.07%)</title><rect x="168.6" y="261" width="0.8" height="15.0" fill="rgb(250,210,50)" rx="2" ry="2" />
<text  x="171.61" y="271.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,258,851,896 samples, 0.01%)</title><rect x="442.6" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="445.57" y="399.5" ></text>
</g>
<g >
<title>transformReturningClause (1,292,893,077 samples, 0.01%)</title><rect x="831.4" y="485" width="0.1" height="15.0" fill="rgb(234,135,32)" rx="2" ry="2" />
<text  x="834.36" y="495.5" ></text>
</g>
<g >
<title>bms_add_member (4,114,877,401 samples, 0.04%)</title><rect x="876.8" y="485" width="0.5" height="15.0" fill="rgb(254,229,54)" rx="2" ry="2" />
<text  x="879.80" y="495.5" ></text>
</g>
<g >
<title>XLogRegisterBuffer (12,203,573,911 samples, 0.13%)</title><rect x="385.3" y="341" width="1.5" height="15.0" fill="rgb(225,96,23)" rx="2" ry="2" />
<text  x="388.27" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,132,208,394 samples, 0.01%)</title><rect x="914.0" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="917.02" y="431.5" ></text>
</g>
<g >
<title>clamp_width_est (835,731,548 samples, 0.01%)</title><rect x="1040.9" y="373" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1043.85" y="383.5" ></text>
</g>
<g >
<title>update_curr (893,829,741 samples, 0.01%)</title><rect x="159.7" y="293" width="0.1" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="162.72" y="303.5" ></text>
</g>
<g >
<title>__sysvec_apic_timer_interrupt (5,366,169,648 samples, 0.06%)</title><rect x="751.1" y="501" width="0.6" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="754.07" y="511.5" ></text>
</g>
<g >
<title>_bt_first (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="293" width="1.9" height="15.0" fill="rgb(237,151,36)" rx="2" ry="2" />
<text  x="127.59" y="303.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (979,917,362 samples, 0.01%)</title><rect x="121.4" y="741" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="124.40" y="751.5" ></text>
</g>
<g >
<title>hash_search_with_hash_value (1,750,105,336 samples, 0.02%)</title><rect x="443.4" y="373" width="0.2" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="446.36" y="383.5" ></text>
</g>
<g >
<title>ReleaseSysCache (899,361,914 samples, 0.01%)</title><rect x="1021.6" y="277" width="0.1" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="1024.55" y="287.5" ></text>
</g>
<g >
<title>GlobalVisUpdate (2,488,025,902 samples, 0.03%)</title><rect x="303.5" y="197" width="0.3" height="15.0" fill="rgb(209,18,4)" rx="2" ry="2" />
<text  x="306.52" y="207.5" ></text>
</g>
<g >
<title>vector8_broadcast (2,603,493,092 samples, 0.03%)</title><rect x="1082.6" y="501" width="0.3" height="15.0" fill="rgb(213,39,9)" rx="2" ry="2" />
<text  x="1085.60" y="511.5" ></text>
</g>
<g >
<title>palloc0 (3,869,324,236 samples, 0.04%)</title><rect x="424.1" y="341" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="427.13" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,467,446,132 samples, 0.02%)</title><rect x="451.0" y="421" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="454.03" y="431.5" ></text>
</g>
<g >
<title>PGSemaphoreUnlock (2,297,024,922 samples, 0.02%)</title><rect x="382.1" y="229" width="0.3" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="385.14" y="239.5" ></text>
</g>
<g >
<title>list_last_cell (945,207,469 samples, 0.01%)</title><rect x="1153.7" y="741" width="0.1" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="1156.71" y="751.5" ></text>
</g>
<g >
<title>new_list (1,931,762,797 samples, 0.02%)</title><rect x="953.9" y="421" width="0.3" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="956.95" y="431.5" ></text>
</g>
<g >
<title>postmaster_child_launch (81,441,377,880 samples, 0.86%)</title><rect x="94.7" y="725" width="10.2" height="15.0" fill="rgb(206,5,1)" rx="2" ry="2" />
<text  x="97.67" y="735.5" ></text>
</g>
<g >
<title>add_path (3,761,264,430 samples, 0.04%)</title><rect x="986.0" y="405" width="0.5" height="15.0" fill="rgb(209,22,5)" rx="2" ry="2" />
<text  x="989.04" y="415.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (1,393,722,142 samples, 0.01%)</title><rect x="423.0" y="309" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="425.96" y="319.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,094,076,226 samples, 0.01%)</title><rect x="1119.0" y="693" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1121.97" y="703.5" ></text>
</g>
<g >
<title>ExecClearTuple (5,897,810,040 samples, 0.06%)</title><rect x="279.2" y="341" width="0.8" height="15.0" fill="rgb(232,127,30)" rx="2" ry="2" />
<text  x="282.24" y="351.5" ></text>
</g>
<g >
<title>ProcessQuery (14,278,524,395 samples, 0.15%)</title><rect x="122.5" y="581" width="1.7" height="15.0" fill="rgb(215,50,12)" rx="2" ry="2" />
<text  x="125.45" y="591.5" ></text>
</g>
<g >
<title>palloc0 (3,955,290,135 samples, 0.04%)</title><rect x="967.7" y="357" width="0.5" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="970.70" y="367.5" ></text>
</g>
<g >
<title>SearchSysCache1 (4,275,537,034 samples, 0.05%)</title><rect x="1034.7" y="149" width="0.5" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="1037.67" y="159.5" ></text>
</g>
<g >
<title>sem_wait@@GLIBC_2.34 (1,744,784,801 samples, 0.02%)</title><rect x="484.3" y="453" width="0.2" height="15.0" fill="rgb(221,73,17)" rx="2" ry="2" />
<text  x="487.26" y="463.5" ></text>
</g>
<g >
<title>RelationClose (1,197,020,135 samples, 0.01%)</title><rect x="860.9" y="485" width="0.1" height="15.0" fill="rgb(208,18,4)" rx="2" ry="2" />
<text  x="863.85" y="495.5" ></text>
</g>
<g >
<title>core_yyalloc (1,527,726,647 samples, 0.02%)</title><rect x="871.3" y="517" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="874.32" y="527.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (805,347,570 samples, 0.01%)</title><rect x="1000.1" y="261" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="1003.14" y="271.5" ></text>
</g>
<g >
<title>pgstat_report_stat (6,239,536,828 samples, 0.07%)</title><rect x="1080.3" y="597" width="0.8" height="15.0" fill="rgb(245,187,44)" rx="2" ry="2" />
<text  x="1083.32" y="607.5" ></text>
</g>
<g >
<title>palloc (1,197,965,078 samples, 0.01%)</title><rect x="910.5" y="421" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="913.54" y="431.5" ></text>
</g>
<g >
<title>preprocess_expression (1,863,826,840 samples, 0.02%)</title><rect x="1173.0" y="757" width="0.2" height="15.0" fill="rgb(251,211,50)" rx="2" ry="2" />
<text  x="1175.98" y="767.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (5,146,928,636 samples, 0.05%)</title><rect x="917.1" y="421" width="0.6" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="920.10" y="431.5" ></text>
</g>
<g >
<title>intel_thermal_interrupt (1,011,223,869 samples, 0.01%)</title><rect x="791.7" y="469" width="0.1" height="15.0" fill="rgb(217,56,13)" rx="2" ry="2" />
<text  x="794.69" y="479.5" ></text>
</g>
<g >
<title>ExecOpenScanRelation (1,855,529,946 samples, 0.02%)</title><rect x="439.6" y="421" width="0.3" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="442.64" y="431.5" ></text>
</g>
<g >
<title>match_clauses_to_index (27,378,663,107 samples, 0.29%)</title><rect x="1019.1" y="373" width="3.5" height="15.0" fill="rgb(244,180,43)" rx="2" ry="2" />
<text  x="1022.14" y="383.5" ></text>
</g>
<g >
<title>sched_balance_newidle (1,094,045,071 samples, 0.01%)</title><rect x="477.0" y="245" width="0.1" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="479.96" y="255.5" ></text>
</g>
<g >
<title>set_next_entity (898,333,598 samples, 0.01%)</title><rect x="160.4" y="309" width="0.1" height="15.0" fill="rgb(232,125,29)" rx="2" ry="2" />
<text  x="163.41" y="319.5" ></text>
</g>
<g >
<title>ExecEvalExprNoReturnSwitchContext (48,465,637,704 samples, 0.51%)</title><rect x="283.2" y="341" width="6.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="286.20" y="351.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,813,027,952 samples, 0.04%)</title><rect x="827.0" y="309" width="0.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="829.97" y="319.5" ></text>
</g>
<g >
<title>UnpinBufferNoOwner (1,141,143,325 samples, 0.01%)</title><rect x="282.0" y="245" width="0.2" height="15.0" fill="rgb(253,221,53)" rx="2" ry="2" />
<text  x="285.05" y="255.5" ></text>
</g>
<g >
<title>EvalPlanQualInit (2,865,371,477 samples, 0.03%)</title><rect x="412.5" y="453" width="0.3" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="415.48" y="463.5" ></text>
</g>
<g >
<title>SimpleLruGetBankLock (1,197,241,238 samples, 0.01%)</title><rect x="470.7" y="453" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="473.68" y="463.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (27,252,744,095 samples, 0.29%)</title><rect x="1055.1" y="501" width="3.4" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1058.05" y="511.5" ></text>
</g>
<g >
<title>LWLockRelease (3,588,447,938 samples, 0.04%)</title><rect x="470.2" y="453" width="0.5" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="473.22" y="463.5" ></text>
</g>
<g >
<title>assign_collations_walker (17,742,960,512 samples, 0.19%)</title><rect x="802.4" y="421" width="2.2" height="15.0" fill="rgb(219,67,16)" rx="2" ry="2" />
<text  x="805.39" y="431.5" ></text>
</g>
<g >
<title>bms_copy (7,226,624,671 samples, 0.08%)</title><rect x="369.3" y="341" width="0.9" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="372.31" y="351.5" ></text>
</g>
<g >
<title>tas (1,789,700,728 samples, 0.02%)</title><rect x="505.7" y="485" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="508.71" y="495.5" ></text>
</g>
<g >
<title>cost_qual_eval_walker (16,164,424,156 samples, 0.17%)</title><rect x="1038.3" y="373" width="2.0" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="1041.26" y="383.5" ></text>
</g>
<g >
<title>ExecCreateExprSetupSteps (17,921,453,776 samples, 0.19%)</title><rect x="416.5" y="357" width="2.2" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="419.51" y="367.5" ></text>
</g>
<g >
<title>query_planner (838,385,919 samples, 0.01%)</title><rect x="1174.7" y="757" width="0.1" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1177.73" y="767.5" ></text>
</g>
<g >
<title>finalize_primnode (10,575,982,151 samples, 0.11%)</title><rect x="880.5" y="437" width="1.3" height="15.0" fill="rgb(240,163,39)" rx="2" ry="2" />
<text  x="883.47" y="447.5" ></text>
</g>
<g >
<title>pg_atomic_read_u32 (1,681,637,442 samples, 0.02%)</title><rect x="354.2" y="245" width="0.2" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="357.16" y="255.5" ></text>
</g>
<g >
<title>create_plan (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="549" width="0.2" height="15.0" fill="rgb(234,136,32)" rx="2" ry="2" />
<text  x="127.24" y="559.5" ></text>
</g>
<g >
<title>GetCommandTagNameAndLen (1,003,030,299 samples, 0.01%)</title><rect x="217.1" y="581" width="0.2" height="15.0" fill="rgb(209,21,5)" rx="2" ry="2" />
<text  x="220.13" y="591.5" ></text>
</g>
<g >
<title>makeTargetEntry (6,093,629,299 samples, 0.06%)</title><rect x="885.0" y="421" width="0.8" height="15.0" fill="rgb(242,172,41)" rx="2" ry="2" />
<text  x="888.05" y="431.5" ></text>
</g>
<g >
<title>IsSharedRelation (1,045,831,990 samples, 0.01%)</title><rect x="441.2" y="357" width="0.1" height="15.0" fill="rgb(217,59,14)" rx="2" ry="2" />
<text  x="444.15" y="367.5" ></text>
</g>
<g >
<title>tick_nohz_handler (1,676,219,620 samples, 0.02%)</title><rect x="750.5" y="437" width="0.2" height="15.0" fill="rgb(248,200,48)" rx="2" ry="2" />
<text  x="753.46" y="447.5" ></text>
</g>
<g >
<title>__x64_sys_futex (1,845,955,282 samples, 0.02%)</title><rect x="382.2" y="165" width="0.2" height="15.0" fill="rgb(239,159,38)" rx="2" ry="2" />
<text  x="385.19" y="175.5" ></text>
</g>
<g >
<title>new_list (1,356,008,443 samples, 0.01%)</title><rect x="970.4" y="357" width="0.2" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="973.38" y="367.5" ></text>
</g>
<g >
<title>mutex_lock (1,784,049,210 samples, 0.02%)</title><rect x="182.4" y="373" width="0.2" height="15.0" fill="rgb(217,57,13)" rx="2" ry="2" />
<text  x="185.42" y="383.5" ></text>
</g>
<g >
<title>__strncpy_avx2 (2,772,283,637 samples, 0.03%)</title><rect x="436.7" y="341" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="439.73" y="351.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,096,694,893 samples, 0.01%)</title><rect x="927.7" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="930.71" y="399.5" ></text>
</g>
<g >
<title>palloc0 (3,390,734,870 samples, 0.04%)</title><rect x="970.9" y="341" width="0.4" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="973.89" y="351.5" ></text>
</g>
<g >
<title>LWLockAcquire (1,680,862,384 samples, 0.02%)</title><rect x="522.5" y="437" width="0.2" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="525.52" y="447.5" ></text>
</g>
<g >
<title>ReadBufferExtended (18,584,697,266 samples, 0.20%)</title><rect x="366.4" y="341" width="2.3" height="15.0" fill="rgb(242,171,40)" rx="2" ry="2" />
<text  x="369.41" y="351.5" ></text>
</g>
<g >
<title>do_futex (4,629,132,643 samples, 0.05%)</title><rect x="350.8" y="197" width="0.6" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="353.79" y="207.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (1,825,468,839 samples, 0.02%)</title><rect x="307.3" y="133" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="310.32" y="143.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,263,342,217 samples, 0.01%)</title><rect x="91.0" y="757" width="0.1" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="93.97" y="767.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,421,893,663 samples, 0.02%)</title><rect x="124.2" y="533" width="0.2" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="127.24" y="543.5" ></text>
</g>
<g >
<title>avg_vruntime (861,860,769 samples, 0.01%)</title><rect x="480.9" y="197" width="0.1" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="483.89" y="207.5" ></text>
</g>
<g >
<title>AtEOXact_Aio (3,112,717,159 samples, 0.03%)</title><rect x="460.7" y="517" width="0.4" height="15.0" fill="rgb(225,93,22)" rx="2" ry="2" />
<text  x="463.72" y="527.5" ></text>
</g>
<g >
<title>PushCopiedSnapshot (7,011,678,428 samples, 0.07%)</title><rect x="453.6" y="549" width="0.8" height="15.0" fill="rgb(248,198,47)" rx="2" ry="2" />
<text  x="456.57" y="559.5" ></text>
</g>
<g >
<title>ReleaseCatCache (1,014,691,778 samples, 0.01%)</title><rect x="102.7" y="437" width="0.2" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="105.75" y="447.5" ></text>
</g>
<g >
<title>__slab_free (2,536,019,359 samples, 0.03%)</title><rect x="179.7" y="325" width="0.4" height="15.0" fill="rgb(222,78,18)" rx="2" ry="2" />
<text  x="182.74" y="335.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,620,362,214 samples, 0.04%)</title><rect x="916.6" y="373" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="919.59" y="383.5" ></text>
</g>
<g >
<title>asm_sysvec_thermal (1,410,022,920 samples, 0.01%)</title><rect x="1085.9" y="757" width="0.2" height="15.0" fill="rgb(252,218,52)" rx="2" ry="2" />
<text  x="1088.91" y="767.5" ></text>
</g>
<g >
<title>XLogRecPtrToBytePos (953,623,668 samples, 0.01%)</title><rect x="507.7" y="437" width="0.1" height="15.0" fill="rgb(251,212,50)" rx="2" ry="2" />
<text  x="510.66" y="447.5" ></text>
</g>
<g >
<title>__pick_next_task (6,399,442,921 samples, 0.07%)</title><rect x="476.6" y="277" width="0.8" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="479.59" y="287.5" ></text>
</g>
<g >
<title>check_log_duration (1,412,542,935 samples, 0.01%)</title><rect x="457.2" y="581" width="0.2" height="15.0" fill="rgb(241,167,40)" rx="2" ry="2" />
<text  x="460.19" y="591.5" ></text>
</g>
<g >
<title>hash_initial_lookup (904,315,679 samples, 0.01%)</title><rect x="867.5" y="421" width="0.1" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="870.50" y="431.5" ></text>
</g>
<g >
<title>ExecIndexBuildScanKeys (29,800,632,650 samples, 0.32%)</title><rect x="425.7" y="421" width="3.7" height="15.0" fill="rgb(216,52,12)" rx="2" ry="2" />
<text  x="428.71" y="431.5" ></text>
</g>
<g >
<title>verify_compact_attribute (1,681,460,148 samples, 0.02%)</title><rect x="360.4" y="309" width="0.3" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="363.44" y="319.5" ></text>
</g>
<g >
<title>simplify_function (975,983,726 samples, 0.01%)</title><rect x="126.7" y="453" width="0.1" height="15.0" fill="rgb(214,43,10)" rx="2" ry="2" />
<text  x="129.71" y="463.5" ></text>
</g>
<g >
<title>lappend (4,035,352,989 samples, 0.04%)</title><rect x="953.7" y="437" width="0.5" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="956.74" y="447.5" ></text>
</g>
<g >
<title>_copy_to_iter (3,198,340,661 samples, 0.03%)</title><rect x="183.3" y="325" width="0.4" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="186.26" y="335.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,582,248,403 samples, 0.03%)</title><rect x="279.6" y="293" width="0.4" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="282.65" y="303.5" ></text>
</g>
<g >
<title>seg_alloc (4,183,699,105 samples, 0.04%)</title><rect x="1064.6" y="437" width="0.5" height="15.0" fill="rgb(210,25,6)" rx="2" ry="2" />
<text  x="1067.56" y="447.5" ></text>
</g>
<g >
<title>exec_simple_query (17,884,496,647 samples, 0.19%)</title><rect x="124.6" y="613" width="2.2" height="15.0" fill="rgb(211,29,6)" rx="2" ry="2" />
<text  x="127.59" y="623.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (2,036,118,200 samples, 0.02%)</title><rect x="431.5" y="357" width="0.2" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="434.49" y="367.5" ></text>
</g>
<g >
<title>palloc (873,872,571 samples, 0.01%)</title><rect x="977.3" y="389" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="980.26" y="399.5" ></text>
</g>
<g >
<title>ExecConstraints (8,703,852,778 samples, 0.09%)</title><rect x="343.2" y="405" width="1.1" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="346.24" y="415.5" ></text>
</g>
<g >
<title>do_futex (998,538,076 samples, 0.01%)</title><rect x="1147.6" y="517" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1150.60" y="527.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (886,882,643 samples, 0.01%)</title><rect x="124.5" y="437" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="127.48" y="447.5" ></text>
</g>
<g >
<title>AllocSetAlloc (875,296,791 samples, 0.01%)</title><rect x="975.0" y="261" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="977.95" y="271.5" ></text>
</g>
<g >
<title>fmgr_info_cxt_security (3,281,433,681 samples, 0.03%)</title><rect x="1037.0" y="293" width="0.4" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="1039.97" y="303.5" ></text>
</g>
<g >
<title>btgettuple (3,400,700,545 samples, 0.04%)</title><rect x="1157.9" y="341" width="0.5" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="1160.94" y="351.5" ></text>
</g>
<g >
<title>analyze_requires_snapshot (1,129,753,749 samples, 0.01%)</title><rect x="1085.0" y="757" width="0.1" height="15.0" fill="rgb(233,129,30)" rx="2" ry="2" />
<text  x="1087.99" y="767.5" ></text>
</g>
<g >
<title>newNode (2,975,165,573 samples, 0.03%)</title><rect x="102.0" y="469" width="0.4" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="105.00" y="479.5" ></text>
</g>
<g >
<title>ExecGetRangeTableRelation (27,944,311,130 samples, 0.30%)</title><rect x="443.0" y="437" width="3.5" height="15.0" fill="rgb(241,167,39)" rx="2" ry="2" />
<text  x="446.01" y="447.5" ></text>
</g>
<g >
<title>transformUpdateStmt (1,179,419,449 samples, 0.01%)</title><rect x="1187.7" y="757" width="0.1" height="15.0" fill="rgb(245,184,44)" rx="2" ry="2" />
<text  x="1190.66" y="767.5" ></text>
</g>
<g >
<title>next_pow2_int (1,477,529,419 samples, 0.02%)</title><rect x="1064.4" y="437" width="0.2" height="15.0" fill="rgb(228,106,25)" rx="2" ry="2" />
<text  x="1067.38" y="447.5" ></text>
</g>
<g >
<title>list_copy (5,347,986,480 samples, 0.06%)</title><rect x="992.0" y="357" width="0.7" height="15.0" fill="rgb(219,68,16)" rx="2" ry="2" />
<text  x="995.04" y="367.5" ></text>
</g>
<g >
<title>ReleaseBuffer (2,643,528,261 samples, 0.03%)</title><rect x="281.9" y="277" width="0.3" height="15.0" fill="rgb(220,71,17)" rx="2" ry="2" />
<text  x="284.87" y="287.5" ></text>
</g>
<g >
<title>ExecBuildUpdateProjection (40,870,069,542 samples, 0.43%)</title><rect x="270.4" y="421" width="5.1" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="273.36" y="431.5" ></text>
</g>
<g >
<title>list_delete_nth_cell (994,663,339 samples, 0.01%)</title><rect x="1154.4" y="757" width="0.1" height="15.0" fill="rgb(234,137,32)" rx="2" ry="2" />
<text  x="1157.40" y="767.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (10,760,583,705 samples, 0.11%)</title><rect x="144.7" y="533" width="1.3" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="147.65" y="543.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,084,760,504 samples, 0.01%)</title><rect x="956.7" y="261" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="959.71" y="271.5" ></text>
</g>
<g >
<title>BufferIsLockedByMeInMode (1,425,490,455 samples, 0.02%)</title><rect x="386.2" y="309" width="0.1" height="15.0" fill="rgb(220,69,16)" rx="2" ry="2" />
<text  x="389.15" y="319.5" ></text>
</g>
<g >
<title>heapam_index_fetch_tuple (132,594,050,746 samples, 1.41%)</title><rect x="294.5" y="277" width="16.6" height="15.0" fill="rgb(254,225,53)" rx="2" ry="2" />
<text  x="297.53" y="287.5" ></text>
</g>
<g >
<title>MemoryChunkIsExternal (2,805,513,118 samples, 0.03%)</title><rect x="146.7" y="549" width="0.4" height="15.0" fill="rgb(253,224,53)" rx="2" ry="2" />
<text  x="149.73" y="559.5" ></text>
</g>
<g >
<title>limit_needed (1,075,032,203 samples, 0.01%)</title><rect x="917.8" y="485" width="0.1" height="15.0" fill="rgb(232,125,29)" rx="2" ry="2" />
<text  x="920.76" y="495.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (3,583,384,334 samples, 0.04%)</title><rect x="288.6" y="197" width="0.5" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="291.62" y="207.5" ></text>
</g>
<g >
<title>heap_hot_search_buffer (1,490,924,305 samples, 0.02%)</title><rect x="1145.2" y="757" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="1148.20" y="767.5" ></text>
</g>
<g >
<title>secure_write (150,700,637,437 samples, 1.60%)</title><rect x="188.4" y="533" width="18.8" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="191.40" y="543.5" ></text>
</g>
<g >
<title>ReindexIsProcessingIndex (1,513,485,726 samples, 0.02%)</title><rect x="88.1" y="757" width="0.2" height="15.0" fill="rgb(246,190,45)" rx="2" ry="2" />
<text  x="91.13" y="767.5" ></text>
</g>
<g >
<title>FunctionCall2Coll (2,727,352,691 samples, 0.03%)</title><rect x="125.2" y="229" width="0.3" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="128.19" y="239.5" ></text>
</g>
<g >
<title>__rint_sse41 (2,516,486,766 samples, 0.03%)</title><rect x="938.2" y="341" width="0.3" height="15.0" fill="rgb(207,9,2)" rx="2" ry="2" />
<text  x="941.23" y="351.5" ></text>
</g>
<g >
<title>ProcessClientWriteInterrupt (3,721,441,461 samples, 0.04%)</title><rect x="188.6" y="517" width="0.5" height="15.0" fill="rgb(230,116,27)" rx="2" ry="2" />
<text  x="191.60" y="527.5" ></text>
</g>
<g >
<title>ReleaseCatCacheWithOwner (818,305,375 samples, 0.01%)</title><rect x="834.5" y="341" width="0.1" height="15.0" fill="rgb(237,148,35)" rx="2" ry="2" />
<text  x="837.49" y="351.5" ></text>
</g>
<g >
<title>__libc_recv (74,603,288,996 samples, 0.79%)</title><rect x="175.3" y="501" width="9.4" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="178.34" y="511.5" ></text>
</g>
<g >
<title>_bt_preprocess_array_keys (2,768,473,694 samples, 0.03%)</title><rect x="82.6" y="309" width="0.4" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="85.62" y="319.5" ></text>
</g>
<g >
<title>SearchSysCache (26,177,875,782 samples, 0.28%)</title><rect x="823.1" y="357" width="3.3" height="15.0" fill="rgb(216,53,12)" rx="2" ry="2" />
<text  x="826.08" y="367.5" ></text>
</g>
<g >
<title>list_make1_impl (2,691,686,909 samples, 0.03%)</title><rect x="986.2" y="373" width="0.3" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="989.16" y="383.5" ></text>
</g>
<g >
<title>tlist_matches_tupdesc (1,989,056,203 samples, 0.02%)</title><rect x="425.4" y="389" width="0.3" height="15.0" fill="rgb(222,79,19)" rx="2" ry="2" />
<text  x="428.45" y="399.5" ></text>
</g>
<g >
<title>pg_atomic_compare_exchange_u32 (1,120,694,284 samples, 0.01%)</title><rect x="474.1" y="453" width="0.1" height="15.0" fill="rgb(253,220,52)" rx="2" ry="2" />
<text  x="477.07" y="463.5" ></text>
</g>
<g >
<title>lappend (1,847,224,926 samples, 0.02%)</title><rect x="811.7" y="437" width="0.2" height="15.0" fill="rgb(233,129,31)" rx="2" ry="2" />
<text  x="814.67" y="447.5" ></text>
</g>
<g >
<title>SearchCatCacheInternal (3,607,439,355 samples, 0.04%)</title><rect x="93.4" y="757" width="0.4" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="96.37" y="767.5" ></text>
</g>
<g >
<title>RelationIdGetRelation (3,881,445,700 samples, 0.04%)</title><rect x="1068.7" y="453" width="0.5" height="15.0" fill="rgb(212,32,7)" rx="2" ry="2" />
<text  x="1071.67" y="463.5" ></text>
</g>
<g >
<title>bms_copy (1,870,167,645 samples, 0.02%)</title><rect x="969.9" y="357" width="0.3" height="15.0" fill="rgb(208,13,3)" rx="2" ry="2" />
<text  x="972.94" y="367.5" ></text>
</g>
<g >
<title>preprocess_qual_conditions (838,717,409 samples, 0.01%)</title><rect x="1158.4" y="549" width="0.1" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="1161.36" y="559.5" ></text>
</g>
<g >
<title>AllocSetFree (1,063,556,049 samples, 0.01%)</title><rect x="251.9" y="373" width="0.2" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="254.94" y="383.5" ></text>
</g>
<g >
<title>GetXLogBuffer (3,919,428,667 samples, 0.04%)</title><rect x="379.6" y="293" width="0.5" height="15.0" fill="rgb(222,80,19)" rx="2" ry="2" />
<text  x="382.62" y="303.5" ></text>
</g>
<g >
<title>recv@plt (1,238,807,054 samples, 0.01%)</title><rect x="184.7" y="501" width="0.1" height="15.0" fill="rgb(238,156,37)" rx="2" ry="2" />
<text  x="187.67" y="511.5" ></text>
</g>
<g >
<title>make_pathkeys_for_sortclauses (1,035,110,074 samples, 0.01%)</title><rect x="1045.4" y="453" width="0.1" height="15.0" fill="rgb(242,173,41)" rx="2" ry="2" />
<text  x="1048.39" y="463.5" ></text>
</g>
<g >
<title>palloc (977,226,607 samples, 0.01%)</title><rect x="813.1" y="437" width="0.2" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="816.14" y="447.5" ></text>
</g>
<g >
<title>check_stack_depth (7,681,438,356 samples, 0.08%)</title><rect x="1126.8" y="757" width="1.0" height="15.0" fill="rgb(248,202,48)" rx="2" ry="2" />
<text  x="1129.79" y="767.5" ></text>
</g>
<g >
<title>SearchSysCache1 (3,662,965,157 samples, 0.04%)</title><rect x="835.0" y="357" width="0.4" height="15.0" fill="rgb(250,207,49)" rx="2" ry="2" />
<text  x="837.97" y="367.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,651,445,115 samples, 0.02%)</title><rect x="361.2" y="277" width="0.2" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="364.18" y="287.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (14,053,202,670 samples, 0.15%)</title><rect x="1006.3" y="165" width="1.7" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="1009.27" y="175.5" ></text>
</g>
<g >
<title>__GI___strlcpy (3,453,719,878 samples, 0.04%)</title><rect x="852.0" y="373" width="0.5" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="855.05" y="383.5" ></text>
</g>
<g >
<title>ResourceOwnerForgetCatCacheRef (1,239,965,197 samples, 0.01%)</title><rect x="91.2" y="757" width="0.1" height="15.0" fill="rgb(212,36,8)" rx="2" ry="2" />
<text  x="94.16" y="767.5" ></text>
</g>
<g >
<title>palloc0 (2,586,633,375 samples, 0.03%)</title><rect x="1021.1" y="293" width="0.3" height="15.0" fill="rgb(251,213,51)" rx="2" ry="2" />
<text  x="1024.10" y="303.5" ></text>
</g>
<g >
<title>SearchCatCache3 (5,052,596,680 samples, 0.05%)</title><rect x="427.4" y="373" width="0.6" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="430.42" y="383.5" ></text>
</g>
<g >
<title>ServerLoop (81,441,377,880 samples, 0.86%)</title><rect x="94.7" y="757" width="10.2" height="15.0" fill="rgb(238,155,37)" rx="2" ry="2" />
<text  x="97.67" y="767.5" ></text>
</g>
<g >
<title>make_pathtarget_from_tlist (9,865,828,424 samples, 0.10%)</title><rect x="918.8" y="485" width="1.3" height="15.0" fill="rgb(213,37,9)" rx="2" ry="2" />
<text  x="921.84" y="495.5" ></text>
</g>
<g >
<title>llseek@GLIBC_2.2.5 (12,527,332,968 samples, 0.13%)</title><rect x="936.5" y="229" width="1.6" height="15.0" fill="rgb(239,156,37)" rx="2" ry="2" />
<text  x="939.50" y="239.5" ></text>
</g>
<g >
<title>hash_seq_search (5,973,746,165 samples, 0.06%)</title><rect x="465.1" y="501" width="0.7" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="468.10" y="511.5" ></text>
</g>
<g >
<title>contain_volatile_functions_walker (1,085,858,591 samples, 0.01%)</title><rect x="1020.5" y="293" width="0.1" height="15.0" fill="rgb(223,83,19)" rx="2" ry="2" />
<text  x="1023.45" y="303.5" ></text>
</g>
<g >
<title>stack_is_too_deep (3,326,729,053 samples, 0.04%)</title><rect x="1184.0" y="757" width="0.4" height="15.0" fill="rgb(243,178,42)" rx="2" ry="2" />
<text  x="1187.00" y="767.5" ></text>
</g>
<g >
<title>futex_wait (851,906,660 samples, 0.01%)</title><rect x="467.7" y="357" width="0.1" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="470.67" y="367.5" ></text>
</g>
<g >
<title>gup_fast_pte_range (1,684,560,666 samples, 0.02%)</title><rect x="486.0" y="261" width="0.2" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="488.98" y="271.5" ></text>
</g>
<g >
<title>hash_initial_lookup (873,126,526 samples, 0.01%)</title><rect x="947.8" y="293" width="0.2" height="15.0" fill="rgb(251,214,51)" rx="2" ry="2" />
<text  x="950.84" y="303.5" ></text>
</g>
<g >
<title>AllocSetAllocChunkFromBlock (829,117,037 samples, 0.01%)</title><rect x="370.0" y="293" width="0.1" height="15.0" fill="rgb(238,152,36)" rx="2" ry="2" />
<text  x="372.99" y="303.5" ></text>
</g>
<g >
<title>start_xact_command (39,254,287,260 samples, 0.42%)</title><rect x="1074.1" y="581" width="4.9" height="15.0" fill="rgb(242,174,41)" rx="2" ry="2" />
<text  x="1077.05" y="591.5" ></text>
</g>
<g >
<title>verify_compact_attribute (30,121,846,430 samples, 0.32%)</title><rect x="1004.3" y="181" width="3.7" height="15.0" fill="rgb(235,141,33)" rx="2" ry="2" />
<text  x="1007.26" y="191.5" ></text>
</g>
<g >
<title>perf_ctx_enable (1,197,496,193 samples, 0.01%)</title><rect x="477.5" y="245" width="0.2" height="15.0" fill="rgb(227,105,25)" rx="2" ry="2" />
<text  x="480.52" y="255.5" ></text>
</g>
<g >
<title>TupleDescCompactAttr (10,979,404,605 samples, 0.12%)</title><rect x="320.0" y="213" width="1.3" height="15.0" fill="rgb(219,66,15)" rx="2" ry="2" />
<text  x="322.97" y="223.5" ></text>
</g>
<g >
<title>gup_fast_pte_range (859,292,389 samples, 0.01%)</title><rect x="351.3" y="69" width="0.1" height="15.0" fill="rgb(233,130,31)" rx="2" ry="2" />
<text  x="354.27" y="79.5" ></text>
</g>
<g >
<title>uint32_hash (1,315,104,302 samples, 0.01%)</title><rect x="446.3" y="357" width="0.2" height="15.0" fill="rgb(240,162,38)" rx="2" ry="2" />
<text  x="449.29" y="367.5" ></text>
</g>
<g >
<title>ExecInterpExprStillValid (47,277,333,261 samples, 0.50%)</title><rect x="283.3" y="309" width="5.9" height="15.0" fill="rgb(212,35,8)" rx="2" ry="2" />
<text  x="286.31" y="319.5" ></text>
</g>
<g >
<title>get_hash_value (2,685,185,326 samples, 0.03%)</title><rect x="99.1" y="165" width="0.3" height="15.0" fill="rgb(211,27,6)" rx="2" ry="2" />
<text  x="102.10" y="175.5" ></text>
</g>
<g >
<title>do_syscall_64 (139,022,353,671 samples, 1.47%)</title><rect x="189.6" y="469" width="17.4" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="192.63" y="479.5" ></text>
</g>
<g >
<title>psi_account_irqtime (2,588,032,125 samples, 0.03%)</title><rect x="478.3" y="277" width="0.3" height="15.0" fill="rgb(224,88,21)" rx="2" ry="2" />
<text  x="481.28" y="287.5" ></text>
</g>
<g >
<title>newNode (1,699,310,026 samples, 0.02%)</title><rect x="839.0" y="341" width="0.2" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="842.03" y="351.5" ></text>
</g>
<g >
<title>exit_to_user_mode_loop (4,264,163,145 samples, 0.05%)</title><rect x="206.4" y="453" width="0.5" height="15.0" fill="rgb(224,90,21)" rx="2" ry="2" />
<text  x="209.36" y="463.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (10,655,414,065 samples, 0.11%)</title><rect x="955.6" y="389" width="1.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="958.64" y="399.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,036,629,267 samples, 0.01%)</title><rect x="814.0" y="389" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="817.05" y="399.5" ></text>
</g>
<g >
<title>cpus_share_cache (1,467,946,781 samples, 0.02%)</title><rect x="200.7" y="277" width="0.2" height="15.0" fill="rgb(214,42,10)" rx="2" ry="2" />
<text  x="203.69" y="287.5" ></text>
</g>
<g >
<title>AllocSetReset (37,840,898,373 samples, 0.40%)</title><rect x="257.3" y="421" width="4.8" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="260.34" y="431.5" ></text>
</g>
<g >
<title>create_modifytable_plan (87,772,593,339 samples, 0.93%)</title><rect x="883.0" y="485" width="11.0" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="885.98" y="495.5" ></text>
</g>
<g >
<title>newNode (2,581,329,950 samples, 0.03%)</title><rect x="839.9" y="309" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="842.90" y="319.5" ></text>
</g>
<g >
<title>TransactionIdSetStatusBit (4,604,964,868 samples, 0.05%)</title><rect x="472.5" y="437" width="0.6" height="15.0" fill="rgb(213,38,9)" rx="2" ry="2" />
<text  x="475.49" y="447.5" ></text>
</g>
<g >
<title>table_open (17,071,805,881 samples, 0.18%)</title><rect x="923.1" y="469" width="2.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="926.11" y="479.5" ></text>
</g>
<g >
<title>RegisterSnapshotOnOwner (1,496,377,687 samples, 0.02%)</title><rect x="452.3" y="485" width="0.2" height="15.0" fill="rgb(229,111,26)" rx="2" ry="2" />
<text  x="455.30" y="495.5" ></text>
</g>
<g >
<title>HeapTupleSatisfiesVacuumHorizon (2,008,856,635 samples, 0.02%)</title><rect x="1148.8" y="693" width="0.2" height="15.0" fill="rgb(207,13,3)" rx="2" ry="2" />
<text  x="1151.78" y="703.5" ></text>
</g>
<g >
<title>fireRIRrules (46,982,620,322 samples, 0.50%)</title><rect x="863.3" y="533" width="5.8" height="15.0" fill="rgb(221,74,17)" rx="2" ry="2" />
<text  x="866.27" y="543.5" ></text>
</g>
<g >
<title>__memset_avx2_unaligned_erms (7,307,034,692 samples, 0.08%)</title><rect x="148.0" y="533" width="0.9" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="150.96" y="543.5" ></text>
</g>
<g >
<title>ExecGetJunkAttribute (1,279,974,737 samples, 0.01%)</title><rect x="265.8" y="437" width="0.2" height="15.0" fill="rgb(230,117,28)" rx="2" ry="2" />
<text  x="268.83" y="447.5" ></text>
</g>
<g >
<title>hash_search (3,903,433,673 samples, 0.04%)</title><rect x="1143.7" y="757" width="0.4" height="15.0" fill="rgb(216,55,13)" rx="2" ry="2" />
<text  x="1146.65" y="767.5" ></text>
</g>
<g >
<title>create_plan_recurse (1,193,115,983 samples, 0.01%)</title><rect x="85.4" y="565" width="0.1" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="88.40" y="575.5" ></text>
</g>
<g >
<title>AtEOXact_LocalBuffers (840,664,057 samples, 0.01%)</title><rect x="31.7" y="757" width="0.1" height="15.0" fill="rgb(237,147,35)" rx="2" ry="2" />
<text  x="34.65" y="767.5" ></text>
</g>
<g >
<title>__new_sem_wait_slow64.constprop.0 (1,822,518,701 samples, 0.02%)</title><rect x="490.5" y="437" width="0.3" height="15.0" fill="rgb(215,48,11)" rx="2" ry="2" />
<text  x="493.54" y="447.5" ></text>
</g>
<g >
<title>GetPrivateRefCountEntry (1,406,404,582 samples, 0.01%)</title><rect x="97.5" y="165" width="0.2" height="15.0" fill="rgb(250,209,50)" rx="2" ry="2" />
<text  x="100.49" y="175.5" ></text>
</g>
<g >
<title>RelationPutHeapTuple (1,072,411,821 samples, 0.01%)</title><rect x="89.5" y="757" width="0.2" height="15.0" fill="rgb(240,161,38)" rx="2" ry="2" />
<text  x="92.53" y="767.5" ></text>
</g>
<g >
<title>hrtimer_interrupt (5,238,956,300 samples, 0.06%)</title><rect x="751.1" y="485" width="0.6" height="15.0" fill="rgb(228,109,26)" rx="2" ry="2" />
<text  x="754.09" y="495.5" ></text>
</g>
<g >
<title>create_plan_recurse (68,348,817,486 samples, 0.72%)</title><rect x="883.9" y="469" width="8.5" height="15.0" fill="rgb(205,0,0)" rx="2" ry="2" />
<text  x="886.89" y="479.5" ></text>
</g>
<g >
<title>PageGetItemId (1,346,372,748 samples, 0.01%)</title><rect x="79.8" y="741" width="0.1" height="15.0" fill="rgb(246,192,46)" rx="2" ry="2" />
<text  x="82.77" y="751.5" ></text>
</g>
<g >
<title>palloc (1,257,909,142 samples, 0.01%)</title><rect x="439.5" y="357" width="0.1" height="15.0" fill="rgb(211,29,7)" rx="2" ry="2" />
<text  x="442.46" y="367.5" ></text>
</g>
<g >
<title>ExecProcNodeFirst (15,388,897,108 samples, 0.16%)</title><rect x="124.6" y="485" width="1.9" height="15.0" fill="rgb(212,33,8)" rx="2" ry="2" />
<text  x="127.59" y="495.5" ></text>
</g>
<g >
<title>_int_free_create_chunk (1,450,973,390 samples, 0.02%)</title><rect x="147.7" y="517" width="0.2" height="15.0" fill="rgb(244,182,43)" rx="2" ry="2" />
<text  x="150.71" y="527.5" ></text>
</g>
<g >
<title>__wake_up_sync_key (3,121,104,146 samples, 0.03%)</title><rect x="180.9" y="293" width="0.4" height="15.0" fill="rgb(226,100,24)" rx="2" ry="2" />
<text  x="183.94" y="303.5" ></text>
</g>
<g >
<title>UnpinBuffer (2,954,035,915 samples, 0.03%)</title><rect x="248.8" y="421" width="0.3" height="15.0" fill="rgb(252,219,52)" rx="2" ry="2" />
<text  x="251.77" y="431.5" ></text>
</g>
<g >
<title>AfterTriggerFireDeferred (1,582,166,860 samples, 0.02%)</title><rect x="459.4" y="517" width="0.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="462.42" y="527.5" ></text>
</g>
<g >
<title>eval_const_expressions_mutator (1,149,259,739 samples, 0.01%)</title><rect x="1134.0" y="741" width="0.1" height="15.0" fill="rgb(210,26,6)" rx="2" ry="2" />
<text  x="1136.99" y="751.5" ></text>
</g>
<g >
<title>AllocSetFree (980,862,006 samples, 0.01%)</title><rect x="246.6" y="357" width="0.1" height="15.0" fill="rgb(210,24,5)" rx="2" ry="2" />
<text  x="249.63" y="367.5" ></text>
</g>
<g >
<title>update_ps_display_precheck (947,902,118 samples, 0.01%)</title><rect x="1083.8" y="565" width="0.1" height="15.0" fill="rgb(248,201,48)" rx="2" ry="2" />
<text  x="1086.80" y="575.5" ></text>
</g>
<g >
<title>LWLockAcquire (2,778,835,142 samples, 0.03%)</title><rect x="307.3" y="149" width="0.3" height="15.0" fill="rgb(209,20,4)" rx="2" ry="2" />
<text  x="310.28" y="159.5" ></text>
</g>
<g >
<title>populate_compact_attribute_internal (1,330,257,557 samples, 0.01%)</title><rect x="83.1" y="213" width="0.1" height="15.0" fill="rgb(234,134,32)" rx="2" ry="2" />
<text  x="86.06" y="223.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,555,633,039 samples, 0.02%)</title><rect x="968.0" y="341" width="0.2" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="970.99" y="351.5" ></text>
</g>
<g >
<title>LWLockRelease (1,599,406,687 samples, 0.02%)</title><rect x="297.0" y="245" width="0.2" height="15.0" fill="rgb(217,58,13)" rx="2" ry="2" />
<text  x="299.98" y="255.5" ></text>
</g>
<g >
<title>choose_nelem_alloc (819,756,112 samples, 0.01%)</title><rect x="1064.3" y="437" width="0.1" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="1067.27" y="447.5" ></text>
</g>
<g >
<title>LWLockAttemptLock (2,125,663,749 samples, 0.02%)</title><rect x="941.5" y="325" width="0.3" height="15.0" fill="rgb(235,138,33)" rx="2" ry="2" />
<text  x="944.52" y="335.5" ></text>
</g>
<g >
<title>sentinel_ok (26,743,743,288 samples, 0.28%)</title><rect x="25.3" y="741" width="3.4" height="15.0" fill="rgb(247,194,46)" rx="2" ry="2" />
<text  x="28.31" y="751.5" ></text>
</g>
<g >
<title>finish_xact_command (2,724,381,657,042 samples, 28.87%)</title><rect x="457.4" y="581" width="340.7" height="15.0" fill="rgb(206,7,1)" rx="2" ry="2" />
<text  x="460.44" y="591.5" >finish_xact_command</text>
</g>
<g >
<title>make_ands_implicit (4,025,683,741 samples, 0.04%)</title><rect x="1057.9" y="469" width="0.5" height="15.0" fill="rgb(236,145,34)" rx="2" ry="2" />
<text  x="1060.92" y="479.5" ></text>
</g>
<g >
<title>PreCommit_Portals (8,041,457,441 samples, 0.09%)</title><rect x="464.9" y="517" width="1.0" height="15.0" fill="rgb(221,76,18)" rx="2" ry="2" />
<text  x="467.85" y="527.5" ></text>
</g>
<g >
<title>IsToastClass (1,602,560,218 samples, 0.02%)</title><rect x="407.0" y="405" width="0.2" height="15.0" fill="rgb(227,104,24)" rx="2" ry="2" />
<text  x="410.02" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (975,063,013 samples, 0.01%)</title><rect x="1035.4" y="181" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="1038.37" y="191.5" ></text>
</g>
<g >
<title>hash_bytes (3,440,751,561 samples, 0.04%)</title><rect x="948.0" y="293" width="0.4" height="15.0" fill="rgb(227,102,24)" rx="2" ry="2" />
<text  x="950.97" y="303.5" ></text>
</g>
<g >
<title>_bt_readnextpage (5,713,748,173 samples, 0.06%)</title><rect x="281.0" y="261" width="0.7" height="15.0" fill="rgb(236,146,34)" rx="2" ry="2" />
<text  x="284.00" y="271.5" ></text>
</g>
<g >
<title>SetCurrentStatementStartTimestamp (846,522,998 samples, 0.01%)</title><rect x="104.9" y="757" width="0.1" height="15.0" fill="rgb(249,204,48)" rx="2" ry="2" />
<text  x="107.86" y="767.5" ></text>
</g>
<g >
<title>ResourceOwnerForget (1,175,172,151 samples, 0.01%)</title><rect x="865.4" y="437" width="0.2" height="15.0" fill="rgb(235,142,33)" rx="2" ry="2" />
<text  x="868.42" y="447.5" ></text>
</g>
<g >
<title>expression_tree_walker_impl (3,484,229,042 samples, 0.04%)</title><rect x="961.0" y="325" width="0.4" height="15.0" fill="rgb(223,84,20)" rx="2" ry="2" />
<text  x="964.00" y="335.5" ></text>
</g>
<g >
<title>set_rel_width (21,247,346,384 samples, 0.23%)</title><rect x="1040.3" y="389" width="2.6" height="15.0" fill="rgb(240,164,39)" rx="2" ry="2" />
<text  x="1043.28" y="399.5" ></text>
</g>
<g >
<title>expression_returns_set (9,923,230,912 samples, 0.11%)</title><rect x="842.6" y="453" width="1.2" height="15.0" fill="rgb(225,95,22)" rx="2" ry="2" />
<text  x="845.57" y="463.5" ></text>
</g>
<g >
<title>int4hashfast (1,622,199,897 samples, 0.02%)</title><rect x="825.1" y="293" width="0.2" height="15.0" fill="rgb(248,200,47)" rx="2" ry="2" />
<text  x="828.12" y="303.5" ></text>
</g>
<g >
<title>__get_user_8 (3,744,984,393 samples, 0.04%)</title><rect x="173.5" y="373" width="0.5" height="15.0" fill="rgb(242,171,41)" rx="2" ry="2" />
<text  x="176.51" y="383.5" ></text>
</g>
<g >
<title>LockRelationOid (8,789,550,590 samples, 0.09%)</title><rect x="394.1" y="357" width="1.1" height="15.0" fill="rgb(228,108,25)" rx="2" ry="2" />
<text  x="397.11" y="367.5" ></text>
</g>
<g >
<title>WaitXLogInsertionsToFinish (18,429,199,472 samples, 0.20%)</title><rect x="489.2" y="485" width="2.3" height="15.0" fill="rgb(249,205,49)" rx="2" ry="2" />
<text  x="492.18" y="495.5" ></text>
</g>
<g >
<title>PinBufferForBlock (14,803,975,244 samples, 0.16%)</title><rect x="366.9" y="277" width="1.8" height="15.0" fill="rgb(241,168,40)" rx="2" ry="2" />
<text  x="369.87" y="287.5" ></text>
</g>
<g >
<title>newNode (2,787,683,215 samples, 0.03%)</title><rect x="927.9" y="405" width="0.3" height="15.0" fill="rgb(212,34,8)" rx="2" ry="2" />
<text  x="930.89" y="415.5" ></text>
</g>
<g >
<title>AllocSetAlloc (1,102,182,370 samples, 0.01%)</title><rect x="983.8" y="389" width="0.1" height="15.0" fill="rgb(231,124,29)" rx="2" ry="2" />
<text  x="986.80" y="399.5" ></text>
</g>
<g >
<title>tag_hash (6,868,557,994 samples, 0.07%)</title><rect x="866.5" y="421" width="0.8" height="15.0" fill="rgb(245,185,44)" rx="2" ry="2" />
<text  x="869.45" y="431.5" ></text>
</g>
<g >
<title>internal_putbytes (3,687,699,741 samples, 0.04%)</title><rect x="216.7" y="549" width="0.4" height="15.0" fill="rgb(249,206,49)" rx="2" ry="2" />
<text  x="219.67" y="559.5" ></text>
</g>
<g >
<title>get_rightop (1,052,311,300 samples, 0.01%)</title><rect x="428.1" y="405" width="0.1" height="15.0" fill="rgb(240,165,39)" rx="2" ry="2" />
<text  x="431.05" y="415.5" ></text>
</g>
</g>
</svg>
cf-5556-a.txttext/plainDownload
master-a.txttext/plainDownload
run_bench_perf.shapplication/octet-streamDownload
#41Greg Burd
greg@burd.me
In reply to: Greg Burd (#40)
4 attachment(s)
Re: Expanding HOT updates for expression and partial indexes

Rebased to address conflicts.

best.

-greg

Attachments:

v27-0001-Prepare-heapam_tuple_update-and-simple_heap_upda.patchapplication/octet-streamDownload
From 82a76ba192e37b038af0adebfa2eca0aedd0e3d8 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 2 Nov 2025 11:36:20 -0500
Subject: [PATCH v27 1/4] Prepare heapam_tuple_update() and
 simple_heap_update() for divergence

This commit lays the foundation for larger changes to come by taking the
first portion of heap_update() through the HeapDeterminColumnsInfo() and
replicating that logic in both heapam_tuple_update() and
simple_heap_upate().  This is done so that these two paths might diverge
in implementation later on.  The simple_heap_update() path deals solely
with updates to catalog tuples which could record their modified
attributes rather than relearn them.  The remaining calls from the
executor into the table AM update API could include the set of updated
attributes.  This is foreshadowing... of course, as that's what the next
commit will start to do.

As part of this reorganization, the handling of replica identity key
attributes has been adjusted. Instead of fetching a second copy of
the bitmap during an update operation, the caller is now required to
provide it. This change applies to both heap_update() and
heap_delete().
---
 src/backend/access/heap/heapam.c         | 568 +++++++++++------------
 src/backend/access/heap/heapam_handler.c | 117 ++++-
 src/include/access/heapam.h              |  24 +-
 3 files changed, 410 insertions(+), 299 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ad9d6338ec2..2579f21e212 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -39,18 +39,24 @@
 #include "access/syncscan.h"
 #include "access/valid.h"
 #include "access/visibilitymap.h"
+#include "access/xact.h"
 #include "access/xloginsert.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
 #include "commands/vacuum.h"
+#include "nodes/bitmapset.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/relcache.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
@@ -62,16 +68,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
 								  bool all_visible_cleared, bool new_all_visible_cleared);
 #ifdef USE_ASSERT_CHECKING
-static void check_lock_if_inplace_updateable_rel(Relation relation,
-												 const ItemPointerData *otid,
-												 HeapTuple newtup);
 static void check_inplace_rel_lock(HeapTuple oldtup);
 #endif
-static Bitmapset *HeapDetermineColumnsInfo(Relation relation,
-										   Bitmapset *interesting_cols,
-										   Bitmapset *external_cols,
-										   HeapTuple oldtup, HeapTuple newtup,
-										   bool *has_external);
 static bool heap_acquire_tuplock(Relation relation, const ItemPointerData *tid,
 								 LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool *have_tuple_lock);
@@ -106,10 +104,10 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static void index_delete_sort(TM_IndexDeleteOp *delstate);
 static int	bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate);
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
-static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
+static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp,
+										Bitmapset *rid_attrs, bool key_required,
 										bool *copy);
 
-
 /*
  * Each tuple lock mode has a corresponding heavyweight lock, and one or two
  * corresponding MultiXactStatuses (one to merely lock tuples, another one to
@@ -2817,6 +2815,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
+	Bitmapset  *rid_attrs;
 	uint16		new_infomask,
 				new_infomask2;
 	bool		have_tuple_lock = false;
@@ -2829,6 +2828,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 
 	AssertHasSnapshotForToast(relation);
 
+	rid_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combo CID.
 	 * Other workers might need that combo CID for visibility checks, and we
@@ -3032,6 +3033,7 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		bms_free(rid_attrs);
 		return result;
 	}
 
@@ -3053,7 +3055,10 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, rid_attrs,
+										   true, &old_key_copied);
+	bms_free(rid_attrs);
+	rid_attrs = NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3265,7 +3270,10 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  *	heap_update - replace a tuple
  *
  * See table_tuple_update() for an explanation of the parameters, except that
- * this routine directly takes a tuple rather than a slot.
+ * this routine directly takes a heap tuple rather than a slot.
+ *
+ * It's required that the caller has acquired the pin and lock on the buffer.
+ * That lock and pin will be managed here, not in the caller.
  *
  * In the failure cases, the routine fills *tmfd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last
@@ -3273,30 +3281,21 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+heap_update(Relation relation, HeapTupleData *oldtup,
+			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+			Bitmapset *mix_attrs, Buffer *vmbuffer,
+			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
-	Bitmapset  *hot_attrs;
-	Bitmapset  *sum_attrs;
-	Bitmapset  *key_attrs;
-	Bitmapset  *id_attrs;
-	Bitmapset  *interesting_attrs;
-	Bitmapset  *modified_attrs;
-	ItemId		lp;
-	HeapTupleData oldtup;
 	HeapTuple	heaptup;
 	HeapTuple	old_key_tuple = NULL;
 	bool		old_key_copied = false;
-	Page		page;
-	BlockNumber block;
 	MultiXactStatus mxact_status;
-	Buffer		buffer,
-				newbuf,
-				vmbuffer = InvalidBuffer,
+	Buffer		newbuf,
 				vmbuffer_new = InvalidBuffer;
 	bool		need_toast;
 	Size		newtupsize,
@@ -3310,7 +3309,6 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		all_visible_cleared_new = false;
 	bool		checked_lockers;
 	bool		locker_remains;
-	bool		id_has_external = false;
 	TransactionId xmax_new_tuple,
 				xmax_old_tuple;
 	uint16		infomask_old_tuple,
@@ -3318,144 +3316,13 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 				infomask_new_tuple,
 				infomask2_new_tuple;
 
-	Assert(ItemPointerIsValid(otid));
-
-	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
-	Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
-		   RelationGetNumberOfAttributes(relation));
-
+	Assert(BufferIsLockedByMe(buffer));
+	Assert(ItemIdIsNormal(lp));
 	AssertHasSnapshotForToast(relation);
 
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combo CID.
-	 * Other workers might need that combo CID for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot update tuples during a parallel operation")));
-
-#ifdef USE_ASSERT_CHECKING
-	check_lock_if_inplace_updateable_rel(relation, otid, newtup);
-#endif
-
-	/*
-	 * Fetch the list of attributes to be checked for various operations.
-	 *
-	 * For HOT considerations, this is wasted effort if we fail to update or
-	 * have to put the new tuple on a different page.  But we must compute the
-	 * list before obtaining buffer lock --- in the worst case, if we are
-	 * doing an update on one of the relevant system catalogs, we could
-	 * deadlock if we try to fetch the list later.  In any case, the relcache
-	 * caches the data so this is usually pretty cheap.
-	 *
-	 * We also need columns used by the replica identity and columns that are
-	 * considered the "key" of rows in the table.
-	 *
-	 * Note that we get copies of each bitmap, so we need not worry about
-	 * relcache flush happening midway through.
-	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
-	sum_attrs = RelationGetIndexAttrBitmap(relation,
-										   INDEX_ATTR_BITMAP_SUMMARIZED);
-	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
-	id_attrs = RelationGetIndexAttrBitmap(relation,
-										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
-	interesting_attrs = NULL;
-	interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, sum_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
-	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
-	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin", NULL);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
-
-	/*
-	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
-	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
-	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
-	 * of which indicates concurrent pruning.
-	 *
-	 * Failing with TM_Updated would be most accurate.  However, unlike other
-	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
-	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
-	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
-	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
-	 * TM_Updated and TM_Deleted affects only the wording of error messages.
-	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
-	 * the specification of when tmfd->ctid is valid.  Second, it creates
-	 * error log evidence that we took this branch.
-	 *
-	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
-	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
-	 * unrelated row, we'll fail with "duplicate key value violates unique".
-	 * XXX if otid is the live, newer version of the newtup row, we'll discard
-	 * changes originating in versions of this catalog row after the version
-	 * the caller got from syscache.  See syscache-update-pruned.spec.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
-
-		UnlockReleaseBuffer(buffer);
-		Assert(!have_tuple_lock);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-		tmfd->ctid = *otid;
-		tmfd->xmax = InvalidTransactionId;
-		tmfd->cmax = InvalidCommandId;
-		*update_indexes = TU_None;
-
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		/* modified_attrs not yet initialized */
-		bms_free(interesting_attrs);
-		return TM_Deleted;
-	}
-
-	/*
-	 * Fill in enough data in oldtup for HeapDetermineColumnsInfo to work
-	 * properly.
-	 */
-	oldtup.t_tableOid = RelationGetRelid(relation);
-	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	oldtup.t_len = ItemIdGetLength(lp);
-	oldtup.t_self = *otid;
-
-	/* the new tuple is ready, except for this: */
+	/* The new tuple is ready, except for this */
 	newtup->t_tableOid = RelationGetRelid(relation);
 
-	/*
-	 * Determine columns modified by the update.  Additionally, identify
-	 * whether any of the unmodified replica identity key attributes in the
-	 * old tuple is externally stored or not.  This is required because for
-	 * such attributes the flattened value won't be WAL logged as part of the
-	 * new tuple so we must include it as part of the old_key_tuple.  See
-	 * ExtractReplicaIdentity.
-	 */
-	modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
-											  id_attrs, &oldtup,
-											  newtup, &id_has_external);
-
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3467,7 +3334,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitously arrive at the same key values.
 	 */
-	if (!bms_overlap(modified_attrs, key_attrs))
+	if (!bms_overlap(mix_attrs, pk_attrs))
 	{
 		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
@@ -3491,17 +3358,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 		key_intact = false;
 	}
 
-	/*
-	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
-	 * otid may very well point at newtup->t_self, which we will overwrite
-	 * with the new tuple's location, so there's great risk of confusion if we
-	 * use otid anymore.
-	 */
-
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3533,8 +3393,8 @@ l2:
 		 */
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
-		infomask = oldtup.t_data->t_infomask;
+		xwait = HeapTupleHeaderGetRawXmax(oldtup->t_data);
+		infomask = oldtup->t_data->t_infomask;
 
 		/*
 		 * Now we have to do something about the existing locker.  If it's a
@@ -3574,13 +3434,12 @@ l2:
 				 * requesting a lock and already have one; avoids deadlock).
 				 */
 				if (!current_is_member)
-					heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+					heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 										 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
 				MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
-								relation, &oldtup.t_self, XLTW_Update,
-								&remain);
+								relation, &oldtup->t_self, XLTW_Update, &remain);
 				checked_lockers = true;
 				locker_remains = remain != 0;
 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3590,9 +3449,9 @@ l2:
 				 * could update this tuple before we get to this point.  Check
 				 * for xmax change, and start over if so.
 				 */
-				if (xmax_infomask_changed(oldtup.t_data->t_infomask,
+				if (xmax_infomask_changed(oldtup->t_data->t_infomask,
 										  infomask) ||
-					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup.t_data),
+					!TransactionIdEquals(HeapTupleHeaderGetRawXmax(oldtup->t_data),
 										 xwait))
 					goto l2;
 			}
@@ -3617,8 +3476,8 @@ l2:
 			 * before this one, which are important to keep in case this
 			 * subxact aborts.
 			 */
-			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup.t_data->t_infomask))
-				update_xact = HeapTupleGetUpdateXid(oldtup.t_data);
+			if (!HEAP_XMAX_IS_LOCKED_ONLY(oldtup->t_data->t_infomask))
+				update_xact = HeapTupleGetUpdateXid(oldtup->t_data);
 			else
 				update_xact = InvalidTransactionId;
 
@@ -3659,9 +3518,9 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
+			heap_acquire_tuplock(relation, &oldtup->t_self, *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
-			XactLockTableWait(xwait, relation, &oldtup.t_self,
+			XactLockTableWait(xwait, relation, &oldtup->t_self,
 							  XLTW_Update);
 			checked_lockers = true;
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -3671,20 +3530,20 @@ l2:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(oldtup.t_data->t_infomask, infomask) ||
+			if (xmax_infomask_changed(oldtup->t_data->t_infomask, infomask) ||
 				!TransactionIdEquals(xwait,
-									 HeapTupleHeaderGetRawXmax(oldtup.t_data)))
+									 HeapTupleHeaderGetRawXmax(oldtup->t_data)))
 				goto l2;
 
 			/* Otherwise check if it committed or aborted */
-			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
-			if (oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)
+			UpdateXmaxHintBits(oldtup->t_data, buffer, xwait);
+			if (oldtup->t_data->t_infomask & HEAP_XMAX_INVALID)
 				can_continue = true;
 		}
 
 		if (can_continue)
 			result = TM_Ok;
-		else if (!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid))
+		else if (!ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid))
 			result = TM_Updated;
 		else
 			result = TM_Deleted;
@@ -3697,39 +3556,33 @@ l2:
 			   result == TM_Updated ||
 			   result == TM_Deleted ||
 			   result == TM_BeingModified);
-		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(oldtup->t_data->t_infomask & HEAP_XMAX_INVALID));
 		Assert(result != TM_Updated ||
-			   !ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
+			   !ItemPointerEquals(&oldtup->t_self, &oldtup->t_data->t_ctid));
 	}
 
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(oldtup, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
 	if (result != TM_Ok)
 	{
-		tmfd->ctid = oldtup.t_data->t_ctid;
-		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
+		tmfd->ctid = oldtup->t_data->t_ctid;
+		tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup->t_data);
 		if (result == TM_SelfModified)
-			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
+			tmfd->cmax = HeapTupleHeaderGetCmax(oldtup->t_data);
 		else
 			tmfd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+			UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
+		if (*vmbuffer != InvalidBuffer)
+			ReleaseBuffer(*vmbuffer);
 		*update_indexes = TU_None;
 
-		bms_free(hot_attrs);
-		bms_free(sum_attrs);
-		bms_free(key_attrs);
-		bms_free(id_attrs);
-		bms_free(modified_attrs);
-		bms_free(interesting_attrs);
 		return result;
 	}
 
@@ -3742,10 +3595,10 @@ l2:
 	 * tuple has been locked or updated under us, but hopefully it won't
 	 * happen very often.
 	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
 	{
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
+		visibilitymap_pin(relation, block, vmbuffer);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		goto l2;
 	}
@@ -3756,9 +3609,9 @@ l2:
 	 * If the tuple we're updating is locked, we need to preserve the locking
 	 * info in the old tuple's Xmax.  Prepare a new Xmax value for this.
 	 */
-	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-							  oldtup.t_data->t_infomask,
-							  oldtup.t_data->t_infomask2,
+	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+							  oldtup->t_data->t_infomask,
+							  oldtup->t_data->t_infomask2,
 							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
@@ -3770,12 +3623,12 @@ l2:
 	 * tuple.  (In rare cases that might also be InvalidTransactionId and yet
 	 * not have the HEAP_XMAX_INVALID bit set; that's fine.)
 	 */
-	if ((oldtup.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-		HEAP_LOCKED_UPGRADED(oldtup.t_data->t_infomask) ||
+	if ((oldtup->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+		HEAP_LOCKED_UPGRADED(oldtup->t_data->t_infomask) ||
 		(checked_lockers && !locker_remains))
 		xmax_new_tuple = InvalidTransactionId;
 	else
-		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup.t_data);
+		xmax_new_tuple = HeapTupleHeaderGetRawXmax(oldtup->t_data);
 
 	if (!TransactionIdIsValid(xmax_new_tuple))
 	{
@@ -3790,7 +3643,7 @@ l2:
 		 * Note that since we're doing an update, the only possibility is that
 		 * the lockers had FOR KEY SHARE lock.
 		 */
-		if (oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)
+		if (oldtup->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
 		{
 			GetMultiXactIdHintBits(xmax_new_tuple, &infomask_new_tuple,
 								   &infomask2_new_tuple);
@@ -3818,7 +3671,7 @@ l2:
 	 * Replace cid with a combo CID if necessary.  Note that we already put
 	 * the plain cid into the new tuple.
 	 */
-	HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
+	HeapTupleHeaderAdjustCmax(oldtup->t_data, &cid, &iscombo);
 
 	/*
 	 * If the toaster needs to be activated, OR if the new tuple will not fit
@@ -3835,12 +3688,12 @@ l2:
 		relation->rd_rel->relkind != RELKIND_MATVIEW)
 	{
 		/* toast table entries should never be recursively toasted */
-		Assert(!HeapTupleHasExternal(&oldtup));
+		Assert(!HeapTupleHasExternal(oldtup));
 		Assert(!HeapTupleHasExternal(newtup));
 		need_toast = false;
 	}
 	else
-		need_toast = (HeapTupleHasExternal(&oldtup) ||
+		need_toast = (HeapTupleHasExternal(oldtup) ||
 					  HeapTupleHasExternal(newtup) ||
 					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
 
@@ -3873,9 +3726,9 @@ l2:
 		 * updating, because the potentially created multixact would otherwise
 		 * be wrong.
 		 */
-		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
-								  oldtup.t_data->t_infomask,
-								  oldtup.t_data->t_infomask2,
+		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup->t_data),
+								  oldtup->t_data->t_infomask,
+								  oldtup->t_data->t_infomask2,
 								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
@@ -3885,18 +3738,18 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		HeapTupleClearHotUpdated(&oldtup);
+		oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		HeapTupleClearHotUpdated(oldtup);
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_lock_old_tuple));
-		HeapTupleHeaderSetXmax(oldtup.t_data, xmax_lock_old_tuple);
-		oldtup.t_data->t_infomask |= infomask_lock_old_tuple;
-		oldtup.t_data->t_infomask2 |= infomask2_lock_old_tuple;
-		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+		HeapTupleHeaderSetXmax(oldtup->t_data, xmax_lock_old_tuple);
+		oldtup->t_data->t_infomask |= infomask_lock_old_tuple;
+		oldtup->t_data->t_infomask2 |= infomask2_lock_old_tuple;
+		HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		oldtup->t_data->t_ctid = oldtup->t_self;
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -3905,7 +3758,7 @@ l2:
 		 * worthwhile.
 		 */
 		if (PageIsAllVisible(page) &&
-			visibilitymap_clear(relation, block, vmbuffer,
+			visibilitymap_clear(relation, block, *vmbuffer,
 								VISIBILITYMAP_ALL_FROZEN))
 			cleared_all_frozen = true;
 
@@ -3919,10 +3772,10 @@ l2:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&oldtup->t_self);
 			xlrec.xmax = xmax_lock_old_tuple;
-			xlrec.infobits_set = compute_infobits(oldtup.t_data->t_infomask,
-												  oldtup.t_data->t_infomask2);
+			xlrec.infobits_set = compute_infobits(oldtup->t_data->t_infomask,
+												  oldtup->t_data->t_infomask2);
 			xlrec.flags =
 				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 			XLogRegisterData(&xlrec, SizeOfHeapLock);
@@ -3944,7 +3797,7 @@ l2:
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = heap_toast_insert_or_update(relation, newtup, oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
 		else
@@ -3980,20 +3833,20 @@ l2:
 				/* It doesn't fit, must use RelationGetBufferForTuple. */
 				newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
 												   buffer, 0, NULL,
-												   &vmbuffer_new, &vmbuffer,
+												   &vmbuffer_new, vmbuffer,
 												   0);
 				/* We're all done. */
 				break;
 			}
 			/* Acquire VM page pin if needed and we don't have it. */
-			if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-				visibilitymap_pin(relation, block, &vmbuffer);
+			if (*vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+				visibilitymap_pin(relation, block, vmbuffer);
 			/* Re-acquire the lock on the old tuple's page. */
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 			/* Re-check using the up-to-date free space */
 			pagefree = PageGetHeapFreeSpace(page);
 			if (newtupsize > pagefree ||
-				(vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
+				(*vmbuffer == InvalidBuffer && PageIsAllVisible(page)))
 			{
 				/*
 				 * Rats, it doesn't fit anymore, or somebody just now set the
@@ -4031,7 +3884,7 @@ l2:
 	 * will include checking the relation level, there is no benefit to a
 	 * separate check for the new tuple.
 	 */
-	CheckForSerializableConflictIn(relation, &oldtup.t_self,
+	CheckForSerializableConflictIn(relation, &oldtup->t_self,
 								   BufferGetBlockNumber(buffer));
 
 	/*
@@ -4039,7 +3892,6 @@ l2:
 	 * has enough space for the new tuple.  If they are the same buffer, only
 	 * one pin is held.
 	 */
-
 	if (newbuf == buffer)
 	{
 		/*
@@ -4047,7 +3899,7 @@ l2:
 		 * to do a HOT update.  Check if any of the index columns have been
 		 * changed.
 		 */
-		if (!bms_overlap(modified_attrs, hot_attrs))
+		if (!bms_overlap(mix_attrs, hot_attrs))
 		{
 			use_hot_update = true;
 
@@ -4058,7 +3910,7 @@ l2:
 			 * indexes if the columns were updated, or we may fail to detect
 			 * e.g. value bound changes in BRIN minmax indexes.
 			 */
-			if (bms_overlap(modified_attrs, sum_attrs))
+			if (bms_overlap(mix_attrs, sum_attrs))
 				summarized_update = true;
 		}
 	}
@@ -4075,10 +3927,8 @@ l2:
 	 * logged.  Pass old key required as true only if the replica identity key
 	 * columns are modified or it has external data.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
-										   bms_overlap(modified_attrs, id_attrs) ||
-										   id_has_external,
-										   &old_key_copied);
+	old_key_tuple = ExtractReplicaIdentity(relation, oldtup, rid_attrs,
+										   rep_id_key_required, &old_key_copied);
 
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
@@ -4100,7 +3950,7 @@ l2:
 	if (use_hot_update)
 	{
 		/* Mark the old tuple as HOT-updated */
-		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHotUpdated(oldtup);
 		/* And mark the new tuple as heap-only */
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
@@ -4109,7 +3959,7 @@ l2:
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
-		HeapTupleClearHotUpdated(&oldtup);
+		HeapTupleClearHotUpdated(oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
 	}
@@ -4118,17 +3968,17 @@ l2:
 
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	oldtup->t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
-	HeapTupleHeaderSetXmax(oldtup.t_data, xmax_old_tuple);
-	oldtup.t_data->t_infomask |= infomask_old_tuple;
-	oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
-	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
+	HeapTupleHeaderSetXmax(oldtup->t_data, xmax_old_tuple);
+	oldtup->t_data->t_infomask |= infomask_old_tuple;
+	oldtup->t_data->t_infomask2 |= infomask2_old_tuple;
+	HeapTupleHeaderSetCmax(oldtup->t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	oldtup->t_data->t_ctid = heaptup->t_self;
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4136,7 +3986,7 @@ l2:
 		all_visible_cleared = true;
 		PageClearAllVisible(BufferGetPage(buffer));
 		visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
-							vmbuffer, VISIBILITYMAP_VALID_BITS);
+							*vmbuffer, VISIBILITYMAP_VALID_BITS);
 	}
 	if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
 	{
@@ -4161,12 +4011,12 @@ l2:
 		 */
 		if (RelationIsAccessibleInLogicalDecoding(relation))
 		{
-			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, oldtup);
 			log_heap_new_cid(relation, heaptup);
 		}
 
 		recptr = log_heap_update(relation, buffer,
-								 newbuf, &oldtup, heaptup,
+								 newbuf, oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4191,7 +4041,7 @@ l2:
 	 * both tuple versions in one call to inval.c so we can avoid redundant
 	 * sinval messages.)
 	 */
-	CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
+	CacheInvalidateHeapTuple(relation, oldtup, heaptup);
 
 	/* Now we can release the buffer(s) */
 	if (newbuf != buffer)
@@ -4199,14 +4049,14 @@ l2:
 	ReleaseBuffer(buffer);
 	if (BufferIsValid(vmbuffer_new))
 		ReleaseBuffer(vmbuffer_new);
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
+	if (BufferIsValid(*vmbuffer))
+		ReleaseBuffer(*vmbuffer);
 
 	/*
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
+		UnlockTupleTuplock(relation, &oldtup->t_self, *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update, newbuf != buffer);
 
@@ -4239,13 +4089,6 @@ l2:
 	if (old_key_tuple != NULL && old_key_copied)
 		heap_freetuple(old_key_tuple);
 
-	bms_free(hot_attrs);
-	bms_free(sum_attrs);
-	bms_free(key_attrs);
-	bms_free(id_attrs);
-	bms_free(modified_attrs);
-	bms_free(interesting_attrs);
-
 	return TM_Ok;
 }
 
@@ -4254,7 +4097,7 @@ l2:
  * Confirm adequate lock held during heap_update(), per rules from
  * README.tuplock section "Locking to write inplace-updated tables".
  */
-static void
+void
 check_lock_if_inplace_updateable_rel(Relation relation,
 									 const ItemPointerData *otid,
 									 HeapTuple newtup)
@@ -4426,7 +4269,7 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
  * listed as interesting) of the old tuple is a member of external_cols and is
  * stored externally.
  */
-static Bitmapset *
+Bitmapset *
 HeapDetermineColumnsInfo(Relation relation,
 						 Bitmapset *interesting_cols,
 						 Bitmapset *external_cols,
@@ -4509,25 +4352,175 @@ HeapDetermineColumnsInfo(Relation relation,
 }
 
 /*
- *	simple_heap_update - replace a tuple
- *
- * This routine may be used to update a tuple when concurrent updates of
- * the target tuple are not expected (for example, because we have a lock
- * on the relation associated with the tuple).  Any failure is reported
- * via ereport().
+ * This routine may be used to update a tuple when concurrent updates of the
+ * target tuple are not expected (for example, because we have a lock on the
+ * relation associated with the tuple).  Any failure is reported via ereport().
  */
 void
-simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup,
+simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
 	TM_FailureData tmfd;
 	LockTupleMode lockmode;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
+	ItemId		lp;
+	HeapTupleData oldtup;
+	bool		rep_id_key_required = false;
+
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	/*
+	 * Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
+	 * we see LP_NORMAL here.  When the otid origin is a syscache, we may have
+	 * neither a pin nor a snapshot.  Hence, we may see other LP_ states, each
+	 * of which indicates concurrent pruning.
+	 *
+	 * Failing with TM_Updated would be most accurate.  However, unlike other
+	 * TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
+	 * LP_DEAD cases.  While the distinction between TM_Updated and TM_Deleted
+	 * does matter to SQL statements UPDATE and MERGE, those SQL statements
+	 * hold a snapshot that ensures LP_NORMAL.  Hence, the choice between
+	 * TM_Updated and TM_Deleted affects only the wording of error messages.
+	 * Settle on TM_Deleted, for two reasons.  First, it avoids complicating
+	 * the specification of when tmfd->ctid is valid.  Second, it creates
+	 * error log evidence that we took this branch.
+	 *
+	 * Since it's possible to see LP_UNUSED at otid, it's also possible to see
+	 * LP_NORMAL for a tuple that replaced LP_UNUSED.  If it's a tuple for an
+	 * unrelated row, we'll fail with "duplicate key value violates unique".
+	 * XXX if otid is the live, newer version of the newtup row, we'll discard
+	 * changes originating in versions of this catalog row after the version
+	 * the caller got from syscache.  See syscache-update-pruned.spec.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+		*update_indexes = TU_None;
+
+		bms_free(hot_attrs);
+		bms_free(sum_attrs);
+		bms_free(pk_attrs);
+		bms_free(rid_attrs);
+		bms_free(idx_attrs);
+		/* mix_attrs not yet initialized */
+
+		elog(ERROR, "tuple concurrently deleted");
+
+		return;
+	}
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
+	result = heap_update(relation, &oldtup, tuple, GetCurrentCommandId(true),
+						 InvalidSnapshot, true /* wait for commit */ , &tmfd, &lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required,
+						 update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
 
-	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -9183,12 +9176,11 @@ log_heap_new_cid(Relation relation, HeapTuple tup)
  * the same tuple that was passed in.
  */
 static HeapTuple
-ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
-					   bool *copy)
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, Bitmapset *rid_attrs,
+					   bool key_required, bool *copy)
 {
 	TupleDesc	desc = RelationGetDescr(relation);
 	char		replident = relation->rd_rel->relreplident;
-	Bitmapset  *idattrs;
 	HeapTuple	key_tuple;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
@@ -9219,17 +9211,13 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	if (!key_required)
 		return NULL;
 
-	/* find out the replica identity columns */
-	idattrs = RelationGetIndexAttrBitmap(relation,
-										 INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
 	/*
 	 * If there's no defined replica identity columns, treat as !key_required.
 	 * (This case should not be reachable from heap_update, since that should
 	 * calculate key_required accurately.  But heap_delete just passes
 	 * constant true for key_required, so we can hit this case in deletes.)
 	 */
-	if (bms_is_empty(idattrs))
+	if (bms_is_empty(rid_attrs))
 		return NULL;
 
 	/*
@@ -9242,7 +9230,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	for (int i = 0; i < desc->natts; i++)
 	{
 		if (bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
-						  idattrs))
+						  rid_attrs))
 			Assert(!nulls[i]);
 		else
 			nulls[i] = true;
@@ -9251,8 +9239,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required,
 	key_tuple = heap_form_tuple(desc, values, nulls);
 	*copy = true;
 
-	bms_free(idattrs);
-
 	/*
 	 * If the tuple, which by here only contains indexed columns, still has
 	 * toasted columns, force them to be inlined. This is somewhat unlikely
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 09a456e9966..7d8c80d3ff7 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -312,23 +313,133 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
 }
 
-
 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, TU_UpdateIndexes *update_indexes)
 {
+	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+	HeapTupleData oldtup;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	Page		page;
+	BlockNumber block;
+	ItemId		lp;
+	Bitmapset  *hot_attrs,
+			   *sum_attrs,
+			   *pk_attrs,
+			   *rid_attrs,
+			   *mix_attrs,
+			   *idx_attrs;
 	TM_Result	result;
 
+	Assert(ItemPointerIsValid(otid));
+
+	/* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+	Assert(HeapTupleHeaderGetNatts(tuple->t_data) <=
+		   RelationGetNumberOfAttributes(relation));
+
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combo CID.
+	 * Other workers might need that combo CID for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot update tuples during a parallel operation")));
+
+#ifdef USE_ASSERT_CHECKING
+	check_lock_if_inplace_updateable_rel(relation, otid, tuple);
+#endif
+
+	/*
+	 * Fetch the list of attributes to be checked for various operations.
+	 *
+	 * For HOT considerations, this is wasted effort if we fail to update or
+	 * have to put the new tuple on a different page.  But we must compute the
+	 * list before obtaining buffer lock --- in the worst case, if we are
+	 * doing an update on one of the relevant system catalogs, we could
+	 * deadlock if we try to fetch the list later.  In any case, the relcache
+	 * caches the data so this is usually pretty cheap.
+	 *
+	 * We also need columns used by the replica identity and columns that are
+	 * considered the "key" of rows in the table.
+	 *
+	 * Note that we get copies of each bitmap, so we need not worry about
+	 * relcache flush happening midway through.
+	 */
+	hot_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_HOT_BLOCKING);
+	sum_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_SUMMARIZED);
+	pk_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	rid_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	idx_attrs = bms_copy(hot_attrs);
+	idx_attrs = bms_add_members(idx_attrs, sum_attrs);
+	idx_attrs = bms_add_members(idx_attrs, pk_attrs);
+	idx_attrs = bms_add_members(idx_attrs, rid_attrs);
+
+	block = ItemPointerGetBlockNumber(otid);
+	INJECTION_POINT("heap_update-before-pin", NULL);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
+
+	Assert(ItemIdIsNormal(lp));
+
+	/*
+	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
+	 * then pass that on to heap_update.
+	 */
+	oldtup.t_tableOid = RelationGetRelid(relation);
+	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	oldtup.t_len = ItemIdGetLength(lp);
+	oldtup.t_self = *otid;
+
+	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
+										 &oldtup, tuple, &rep_id_key_required);
+
+	/*
+	 * We'll need to WAL log the replica identity attributes if either they
+	 * overlap with the modified indexed attributes or, as we've checked for
+	 * just now in HeapDetermineColumnsInfo, they were unmodified external
+	 * indexed attributes.
+	 */
+	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+	result = heap_update(relation, &oldtup, tuple, cid, crosscheck, wait, tmfd, lockmode,
+						 buffer, page, block, lp, hot_attrs, sum_attrs, pk_attrs,
+						 rid_attrs, mix_attrs, &vmbuffer, rep_id_key_required, update_indexes);
+
+	bms_free(hot_attrs);
+	bms_free(sum_attrs);
+	bms_free(pk_attrs);
+	bms_free(rid_attrs);
+	bms_free(mix_attrs);
+	bms_free(idx_attrs);
+
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index ce48fac42ba..41193d5b3d2 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -364,11 +364,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 TM_FailureData *tmfd, bool changingPart);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
-extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
-							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
+							 HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
+							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
+							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
+							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
+							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
@@ -430,6 +432,18 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 									  OffsetNumber *dead, int ndead,
 									  OffsetNumber *unused, int nunused);
 
+/* in heap/heapam.c */
+extern Bitmapset *HeapDetermineColumnsInfo(Relation relation,
+										   Bitmapset *interesting_cols,
+										   Bitmapset *external_cols,
+										   HeapTuple oldtup, HeapTuple newtup,
+										   bool *has_external);
+#ifdef USE_ASSERT_CHECKING
+extern void check_lock_if_inplace_updateable_rel(Relation relation,
+												 const ItemPointerData *otid,
+												 HeapTuple newtup);
+#endif
+
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
 							const VacuumParams params, BufferAccessStrategy bstrategy);
-- 
2.51.2

v27-0004-Identify-if-partial-indexes-are-impacted-by-an-u.patchapplication/octet-streamDownload
From 99e6603d5e82b4e41c4fbb6923e49527f91ec376 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 5 Dec 2025 13:42:13 -0500
Subject: [PATCH v27 4/4] Identify if partial indexes are impacted by an
 update.

The executor now determines which, if any, attributes that are indexed
are both modified and force new index tuples to be inserted ahead of
calling into the table AM update function.  Prior to this commit the
test for partial indexes happened after table update, this changes that
to before so that in cases where the before and after tuples both lie
outside the predicate the attributes for the predicate are not included
in the "modified indexed attributes" bitmapset.
---
 src/backend/executor/nodeModifyTable.c | 53 ++++++++++++++++++++++++--
 1 file changed, 49 insertions(+), 4 deletions(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8fca5e09a26..5a2c95dc9a7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -226,9 +226,11 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 		Bitmapset  *m_attrs = NULL; /* (possibly) modified indexed attrs */
 		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attrs */
 		Bitmapset  *u_attrs = NULL; /* unmodified indexed attrs */
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
 		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
 		bool		supports_ios = (amroutine->amcanreturn != NULL);
 		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		TupleTableSlot *save_scantuple;
 		ExprContext *econtext = GetPerTupleExprContext(estate);
 		int			num_datums = supports_ios ?
 			indexInfo->ii_NumIndexAttrs : indexInfo->ii_NumIndexKeyAttrs;
@@ -237,9 +239,51 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
 			continue;
 
-		/* Add partial index attributes */
-		if (is_partial)
-			p_attrs = bms_add_members(p_attrs, indexInfo->ii_PredicateAttrs);
+		/* Checking partial at this point isn't viable when we're serializable */
+		if (is_partial && IsolationIsSerializable())
+		{
+			p_attrs = bms_add_members(p_attrs, pre_attrs);
+		}
+		/* Check partial index predicate */
+		else if (is_partial)
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+				continue;
+
+			/* A transition means we need to update the index */
+			if (old_qualifies != new_qualifies)
+				p_attrs = bms_copy(pre_attrs);
+
+			/*
+			 * When both are within the predicate we must update this index,
+			 * but only if one of the index key attributes changed.
+			 */
+		}
 
 		/* Compare the index datums for equality */
 		for (int j = 0; j < num_datums; j++)
@@ -275,11 +319,12 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 			 */
 			else if (rel_attrnum == 0)
 			{
-				TupleTableSlot *save_scantuple = econtext->ecxt_scantuple;
 				Oid			expr_type_oid;
 				Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
 				ExprState  *state;
 
+				save_scantuple = econtext->ecxt_scantuple;
+
 				if (indexInfo->ii_ExpressionsState == NIL)
 				{
 					/* First time through, set up expression evaluation state */
-- 
2.51.2

v27-0003-Replace-index_unchanged_by_update-with-ri_Change.patchapplication/octet-streamDownload
From 9423421a695b4d560cf7bfc7e6f2dcd1c630084d Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 31 Oct 2025 14:55:25 -0400
Subject: [PATCH v27 3/4] Replace index_unchanged_by_update() with
 ri_ChangedIndexedCols

In execIndexing on updates we'd like to pass a hint to the indexing code
when the indexed attributes are unchanged.  This commit replaces the now
redundant code in index_unchanged_by_update() with the same information
found earlier in ExecWhichIndexesRequireUpdates() and stashed in
ri_ChangedIndexedCols.
---
 src/backend/catalog/toasting.c      |   2 -
 src/backend/executor/execIndexing.c | 156 +---------------------------
 src/backend/nodes/makefuncs.c       |   2 -
 src/include/nodes/execnodes.h       |   4 -
 4 files changed, 1 insertion(+), 163 deletions(-)

diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index ff8da5be5f8..5675c6f8ea9 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -304,8 +304,6 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Unique = true;
 	indexInfo->ii_NullsNotDistinct = false;
 	indexInfo->ii_ReadyForInserts = true;
-	indexInfo->ii_CheckedUnchanged = false;
-	indexInfo->ii_IndexUnchanged = false;
 	indexInfo->ii_Concurrent = false;
 	indexInfo->ii_BrokenHotChain = false;
 	indexInfo->ii_ParallelWorkers = 0;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 1275feffae9..b75e76401d2 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -143,11 +143,6 @@ static bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
 static bool index_recheck_constraint(Relation index, const Oid *constr_procs,
 									 const Datum *existing_values, const bool *existing_isnull,
 									 const Datum *new_values);
-static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
-									  EState *estate, IndexInfo *indexInfo,
-									  Relation indexRelation);
-static bool index_expression_changed_walker(Node *node,
-											Bitmapset *allUpdatedCols);
 static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
 										char typtype, Oid atttypid);
 
@@ -451,10 +446,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * index.  If we're being called as part of an UPDATE statement,
 		 * consider if the 'indexUnchanged' = true hint should be passed.
 		 */
-		indexUnchanged = update && index_unchanged_by_update(resultRelInfo,
-															 estate,
-															 indexInfo,
-															 indexRelation);
+		indexUnchanged = update && bms_is_empty(resultRelInfo->ri_ChangedIndexedCols);
 
 		satisfiesConstraint =
 			index_insert(indexRelation, /* index relation */
@@ -1014,152 +1006,6 @@ index_recheck_constraint(Relation index, const Oid *constr_procs,
 	return true;
 }
 
-/*
- * Check if ExecInsertIndexTuples() should pass indexUnchanged hint.
- *
- * When the executor performs an UPDATE that requires a new round of index
- * tuples, determine if we should pass 'indexUnchanged' = true hint for one
- * single index.
- */
-static bool
-index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
-						  IndexInfo *indexInfo, Relation indexRelation)
-{
-	Bitmapset  *updatedCols;
-	Bitmapset  *extraUpdatedCols;
-	Bitmapset  *allUpdatedCols;
-	bool		hasexpression = false;
-	List	   *idxExprs;
-
-	/*
-	 * Check cache first
-	 */
-	if (indexInfo->ii_CheckedUnchanged)
-		return indexInfo->ii_IndexUnchanged;
-	indexInfo->ii_CheckedUnchanged = true;
-
-	/*
-	 * Check for indexed attribute overlap with updated columns.
-	 *
-	 * Only do this for key columns.  A change to a non-key column within an
-	 * INCLUDE index should not be counted here.  Non-key column values are
-	 * opaque payload state to the index AM, a little like an extra table TID.
-	 *
-	 * Note that row-level BEFORE triggers won't affect our behavior, since
-	 * they don't affect the updatedCols bitmaps generally.  It doesn't seem
-	 * worth the trouble of checking which attributes were changed directly.
-	 */
-	updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
-	extraUpdatedCols = ExecGetExtraUpdatedCols(resultRelInfo, estate);
-	for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
-	{
-		int			keycol = indexInfo->ii_IndexAttrNumbers[attr];
-
-		if (keycol <= 0)
-		{
-			/*
-			 * Skip expressions for now, but remember to deal with them later
-			 * on
-			 */
-			hasexpression = true;
-			continue;
-		}
-
-		if (bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  updatedCols) ||
-			bms_is_member(keycol - FirstLowInvalidHeapAttributeNumber,
-						  extraUpdatedCols))
-		{
-			/* Changed key column -- don't hint for this index */
-			indexInfo->ii_IndexUnchanged = false;
-			return false;
-		}
-	}
-
-	/*
-	 * When we get this far and index has no expressions, return true so that
-	 * index_insert() call will go on to pass 'indexUnchanged' = true hint.
-	 *
-	 * The _absence_ of an indexed key attribute that overlaps with updated
-	 * attributes (in addition to the total absence of indexed expressions)
-	 * shows that the index as a whole is logically unchanged by UPDATE.
-	 */
-	if (!hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = true;
-		return true;
-	}
-
-	/*
-	 * Need to pass only one bms to expression_tree_walker helper function.
-	 * Avoid allocating memory in common case where there are no extra cols.
-	 */
-	if (!extraUpdatedCols)
-		allUpdatedCols = updatedCols;
-	else
-		allUpdatedCols = bms_union(updatedCols, extraUpdatedCols);
-
-	/*
-	 * We have to work slightly harder in the event of indexed expressions,
-	 * but the principle is the same as before: try to find columns (Vars,
-	 * actually) that overlap with known-updated columns.
-	 *
-	 * If we find any matching Vars, don't pass hint for index.  Otherwise
-	 * pass hint.
-	 */
-	idxExprs = RelationGetIndexExpressions(indexRelation);
-	hasexpression = index_expression_changed_walker((Node *) idxExprs,
-													allUpdatedCols);
-	list_free(idxExprs);
-	if (extraUpdatedCols)
-		bms_free(allUpdatedCols);
-
-	if (hasexpression)
-	{
-		indexInfo->ii_IndexUnchanged = false;
-		return false;
-	}
-
-	/*
-	 * Deliberately don't consider index predicates.  We should even give the
-	 * hint when result rel's "updated tuple" has no corresponding index
-	 * tuple, which is possible with a partial index (provided the usual
-	 * conditions are met).
-	 */
-	indexInfo->ii_IndexUnchanged = true;
-	return true;
-}
-
-/*
- * Indexed expression helper for index_unchanged_by_update().
- *
- * Returns true when Var that appears within allUpdatedCols located.
- */
-static bool
-index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
-{
-	if (node == NULL)
-		return false;
-
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
-						  allUpdatedCols))
-		{
-			/* Var was updated -- indicates that we should not hint */
-			return true;
-		}
-
-		/* Still haven't found a reason to not pass the hint */
-		return false;
-	}
-
-	return expression_tree_walker(node, index_expression_changed_walker,
-								  allUpdatedCols);
-}
-
 /*
  * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
  * range or multirange in the given attribute.
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index dd092bacad9..9d3a5b79d27 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -845,8 +845,6 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	n->ii_Unique = unique;
 	n->ii_NullsNotDistinct = nulls_not_distinct;
 	n->ii_ReadyForInserts = isready;
-	n->ii_CheckedUnchanged = false;
-	n->ii_IndexUnchanged = false;
 	n->ii_Concurrent = concurrent;
 	n->ii_Summarizing = summarizing;
 	n->ii_WithoutOverlaps = withoutoverlaps;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d5af2f34d0f..8e583b1d9d3 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -216,10 +216,6 @@ typedef struct IndexInfo
 	bool		ii_NullsNotDistinct;
 	/* is it valid for inserts? */
 	bool		ii_ReadyForInserts;
-	/* IndexUnchanged status determined yet? */
-	bool		ii_CheckedUnchanged;
-	/* aminsert hint, cached for retail inserts */
-	bool		ii_IndexUnchanged;
 	/* are we doing a concurrent index build? */
 	bool		ii_Concurrent;
 	/* did we detect any broken HOT chains? */
-- 
2.51.2

v27-0002-Track-changed-indexed-columns-in-the-executor-du.patchapplication/octet-streamDownload
From 06c0a809cb636bb863a39c8db9dd11e036b27ed6 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Sun, 26 Oct 2025 10:49:25 -0400
Subject: [PATCH v27 2/4] Track changed indexed columns in the executor during
 UPDATEs

Refactor executor update logic to determine which indexed columns have
actually changed during an UPDATE operation rather than leaving this up
to HeapDetermineColumnsInfo() in heap_update().

ExecWhichIndexesRequireUpdates() replaces HeapDeterminesColumnsInfo()
when invoked from the table AM API via heapam_tuple_update(). The
test for equality remains datumIsEqual() as before.

This change necessitated some logic changes in execReplication() as it
performs updates now must provide the set of attributes that are both
changed and referenced by indexes.  Luckilly, this is available within
calls to slot_modify_data() where LogicalRepTupleData is processed and
has a record of updated attributes.  In this case rather than using
ExecWhichIndexesRequireUpdates() we can preseve what slot_modify_data()
identifies as the modified set and then intersect that with the set of
indexes on the relation and get the correct set of modified indexed
attributes required on heap_update().

This commit also extends the role index AMs play determining if they
require an update. A new optional index AM API, amcomparedatums(), is
added to allow index access methods to provide custom logic for
comparing datums. Hash and Gin indexes now implement this function. When
not implemented the executor will compare TupleTableSlot datum for
equality using datumIsEqual() as before.

Because heap_update() now requires the caller to provide the modified
indexed columns simple_heap_update() has become a tad more complex.  It
is only called from CatalogTupleUpdate() which either updates heap
tuples via their Form_XXX or by calling heap_modify_tuple().  In both
cases the caller does know the modified set of attributes, but sadly
those attributes are lost before being provided to simple_heap_update().
Due to that the "simple" path has to (for now) retain the
HeapDetermineColumnsInfo() logic in order for catalog updates to
potentially take the HOT path.
---
 src/backend/access/brin/brin.c                |   1 +
 src/backend/access/gin/ginutil.c              |  90 ++-
 src/backend/access/hash/hash.c                |  44 ++
 src/backend/access/heap/heapam.c              |  20 +-
 src/backend/access/heap/heapam_handler.c      |  76 +-
 src/backend/access/nbtree/nbtree.c            |   1 +
 src/backend/access/table/tableam.c            |   5 +-
 src/backend/bootstrap/bootstrap.c             |   8 +
 src/backend/catalog/index.c                   |  57 ++
 src/backend/catalog/indexing.c                |  16 +-
 src/backend/catalog/toasting.c                |   4 +
 src/backend/executor/execIndexing.c           |  41 +-
 src/backend/executor/execMain.c               |   1 +
 src/backend/executor/execReplication.c        |   7 +
 src/backend/executor/nodeModifyTable.c        | 287 +++++++-
 src/backend/nodes/bitmapset.c                 |   4 +
 src/backend/nodes/makefuncs.c                 |   4 +
 src/backend/replication/logical/worker.c      |  70 +-
 src/backend/utils/cache/relcache.c            |  15 +
 src/include/access/amapi.h                    |  28 +
 src/include/access/gin.h                      |   3 +
 src/include/access/heapam.h                   |   6 +-
 src/include/access/nbtree.h                   |   4 +
 src/include/access/tableam.h                  |   8 +-
 src/include/catalog/index.h                   |   1 +
 src/include/executor/executor.h               |   9 +
 src/include/nodes/execnodes.h                 |  20 +
 src/include/utils/rel.h                       |   1 +
 src/include/utils/relcache.h                  |   1 +
 .../expected/insert-conflict-specconflict.out |  20 +
 .../regress/expected/heap_hot_updates.out     | 650 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   6 +
 src/test/regress/sql/heap_hot_updates.sql     | 513 ++++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 34 files changed, 1948 insertions(+), 74 deletions(-)
 create mode 100644 src/test/regress/expected/heap_hot_updates.out
 create mode 100644 src/test/regress/sql/heap_hot_updates.sql

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 6887e421442..aa9fd110802 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -290,6 +290,7 @@ brinhandler(PG_FUNCTION_ARGS)
 		.amproperty = NULL,
 		.ambuildphasename = NULL,
 		.amvalidate = brinvalidate,
+		.amcomparedatums = NULL,
 		.amadjustmembers = NULL,
 		.ambeginscan = brinbeginscan,
 		.amrescan = brinrescan,
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index a546cac18d3..9f994feae5d 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -26,6 +26,7 @@
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS)
 		.amproperty = NULL,
 		.ambuildphasename = ginbuildphasename,
 		.amvalidate = ginvalidate,
+		.amcomparedatums = gincomparedatums,
 		.amadjustmembers = ginadjustmembers,
 		.ambeginscan = ginbeginscan,
 		.amrescan = ginrescan,
@@ -478,13 +480,6 @@ cmpEntries(const void *a, const void *b, void *arg)
 	return res;
 }
 
-
-/*
- * Extract the index key values from an indexable item
- *
- * The resulting key values are sorted, and any duplicates are removed.
- * This avoids generating redundant index entries.
- */
 Datum *
 ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
 				  Datum value, bool isNull,
@@ -730,3 +725,84 @@ ginbuildphasename(int64 phasenum)
 			return NULL;
 	}
 }
+
+/*
+ * gincomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * This function extracts keys from both old_datum and new_datum using the
+ * opclass's extractValue function, then compares the extracted key arrays.
+ * Returns true if the key sets are identical (same keys, same counts).
+ *
+ * This enables HOT updates for GIN indexes when the indexed portions of a
+ * value haven't changed, even if the value itself has changed.
+ *
+ * Example: JSONB column with GIN index. If an update changes a non-indexed
+ * key in the JSONB document, the extracted keys are identical and we can
+ * do a HOT update.
+ */
+bool
+gincomparedatums(Relation index, int attnum,
+				 Datum old_datum, bool old_isnull,
+				 Datum new_datum, bool new_isnull)
+{
+	GinState	ginstate;
+	Datum	   *old_keys;
+	Datum	   *new_keys;
+	GinNullCategory *old_categories;
+	GinNullCategory *new_categories;
+	int32		old_nkeys;
+	int32		new_nkeys;
+	MemoryContext tmpcontext;
+	MemoryContext oldcontext;
+	bool		result = true;
+
+	/* Handle NULL cases */
+	if (old_isnull != new_isnull)
+		return false;
+	if (old_isnull)
+		return true;
+
+	/* Create temporary context for extraction work */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "GIN datum comparison",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	initGinState(&ginstate, index);
+
+	/* Extract keys from both datums using existing GIN infrastructure */
+	old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull,
+								 &old_nkeys, &old_categories);
+	new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull,
+								 &new_nkeys, &new_categories);
+
+	/* Different number of keys, definitely different */
+	if (old_nkeys != new_nkeys)
+	{
+		result = false;
+		goto cleanup;
+	}
+
+	/*
+	 * Compare the sorted key arrays element-by-element. Since both arrays are
+	 * already sorted by ginExtractEntries, we can do a simple O(n)
+	 * comparison.
+	 */
+	for (int i = 0; i < old_nkeys; i++)
+	{
+		if (ginCompareEntries(&ginstate, attnum,
+							  old_keys[i], old_categories[i],
+							  new_keys[i], new_categories[i]) != 0)
+		{
+			result = false;
+			break;
+		}
+	}
+
+cleanup:
+	/* Clean up */
+	MemoryContextSwitchTo(oldcontext);
+	MemoryContextDelete(tmpcontext);
+
+	return result;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..49a99998083 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -50,6 +50,10 @@ static void hashbuildCallback(Relation index,
 							  void *state);
 
 
+static bool hashcomparedatums(Relation index, int attnum,
+							  Datum old_datum, bool old_isnull,
+							  Datum new_datum, bool new_isnull);
+
 /*
  * Hash handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -98,6 +102,7 @@ hashhandler(PG_FUNCTION_ARGS)
 		.amproperty = NULL,
 		.ambuildphasename = NULL,
 		.amvalidate = hashvalidate,
+		.amcomparedatums = hashcomparedatums,
 		.amadjustmembers = hashadjustmembers,
 		.ambeginscan = hashbeginscan,
 		.amrescan = hashrescan,
@@ -944,3 +949,42 @@ hashtranslatecmptype(CompareType cmptype, Oid opfamily)
 		return HTEqualStrategyNumber;
 	return InvalidStrategy;
 }
+
+/*
+ * hashcomparedatums - Compare datums to determine if they produce identical keys
+ *
+ * Returns true if the hash values are identical (index doesn't need update).
+ */
+bool
+hashcomparedatums(Relation index, int attnum,
+				  Datum old_datum, bool old_isnull,
+				  Datum new_datum, bool new_isnull)
+{
+	uint32		old_hashkey;
+	uint32		new_hashkey;
+
+	/* If both are NULL, they're equal */
+	if (old_isnull && new_isnull)
+		return true;
+
+	/* If NULL status differs, they're not equal */
+	if (old_isnull != new_isnull)
+		return false;
+
+	/*
+	 * _hash_datum2hashkey() is used because we know this can't be a cross
+	 * type comparison.
+	 */
+	old_hashkey = _hash_datum2hashkey(index, old_datum);
+	new_hashkey = _hash_datum2hashkey(index, new_datum);
+
+	/*
+	 * If hash keys are identical, the index entry would be the same. Return
+	 * true to indicate no index update needed.
+	 *
+	 * Note: Hash collisions are rare but possible. If hash(x) == hash(y) but
+	 * x != y, the hash index still treats them identically, so we correctly
+	 * return true.
+	 */
+	return (old_hashkey == new_hashkey);
+}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2579f21e212..023d0595349 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3281,12 +3281,12 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  * generated by another transaction).
  */
 TM_Result
-heap_update(Relation relation, HeapTupleData *oldtup,
-			HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
-			Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
-			Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-			Bitmapset *mix_attrs, Buffer *vmbuffer,
+heap_update(Relation relation, HeapTupleData *oldtup, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			TM_FailureData *tmfd, LockTupleMode *lockmode,
+			Buffer buffer, Page page, BlockNumber block, ItemId lp,
+			Bitmapset *hot_attrs, Bitmapset *sum_attrs, Bitmapset *pk_attrs,
+			Bitmapset *rid_attrs, const Bitmapset *mix_attrs, Buffer *vmbuffer,
 			bool rep_id_key_required, TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -4355,8 +4355,9 @@ HeapDetermineColumnsInfo(Relation relation,
  * This routine may be used to update a tuple when concurrent updates of the
  * target tuple are not expected (for example, because we have a lock on the
  * relation associated with the tuple).  Any failure is reported via ereport().
+ * Returns the set of modified indexed attributes.
  */
-void
+Bitmapset *
 simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tuple,
 				   TU_UpdateIndexes *update_indexes)
 {
@@ -4485,7 +4486,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 		elog(ERROR, "tuple concurrently deleted");
 
-		return;
+		return NULL;
 	}
 
 	/*
@@ -4518,7 +4519,6 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	switch (result)
@@ -4544,6 +4544,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
 	}
+
+	return mix_attrs;
 }
 
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 7d8c80d3ff7..d171247145c 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -315,9 +315,12 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 
 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, TU_UpdateIndexes *update_indexes)
+					CommandId cid, Snapshot snapshot,
+					Snapshot crosscheck, bool wait,
+					TM_FailureData *tmfd,
+					LockTupleMode *lockmode,
+					const Bitmapset *mix_attrs,
+					TU_UpdateIndexes *update_indexes)
 {
 	bool		rep_id_key_required = false;
 	bool		shouldFree = true;
@@ -332,7 +335,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 			   *sum_attrs,
 			   *pk_attrs,
 			   *rid_attrs,
-			   *mix_attrs,
 			   *idx_attrs;
 	TM_Result	result;
 
@@ -405,25 +407,66 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 
 	Assert(ItemIdIsNormal(lp));
 
-	/*
-	 * Partially construct the oldtup for HeapDetermineColumnsInfo to work and
-	 * then pass that on to heap_update.
-	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
 	oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	oldtup.t_len = ItemIdGetLength(lp);
 	oldtup.t_self = *otid;
 
-	mix_attrs = HeapDetermineColumnsInfo(relation, idx_attrs, rid_attrs,
-										 &oldtup, tuple, &rep_id_key_required);
-
 	/*
-	 * We'll need to WAL log the replica identity attributes if either they
-	 * overlap with the modified indexed attributes or, as we've checked for
-	 * just now in HeapDetermineColumnsInfo, they were unmodified external
-	 * indexed attributes.
+	 * We'll need to include the replica identity key when either the identity
+	 * key attributes overlap with the modified index attributes or when the
+	 * replica identity attributes are stored externally.  This is required
+	 * because for such attributes the flattened value won't be WAL logged as
+	 * part of the new tuple so we must determine if we need to extract and
+	 * include them as part of the old_key_tuple (see ExtractReplicaIdentity).
 	 */
-	rep_id_key_required = rep_id_key_required || bms_overlap(mix_attrs, rid_attrs);
+	rep_id_key_required = bms_overlap(mix_attrs, rid_attrs);
+	if (!rep_id_key_required)
+	{
+		Bitmapset  *attrs;
+		TupleDesc	tupdesc = RelationGetDescr(relation);
+		int			attidx = -1;
+
+		/*
+		 * We don't own idx_attrs so we'll copy it and remove the modified set
+		 * to reduce the attributes we need to test in the while loop and
+		 * avoid a two branches in the loop.
+		 */
+		attrs = bms_difference(idx_attrs, mix_attrs);
+		attrs = bms_int_members(attrs, rid_attrs);
+
+		while ((attidx = bms_next_member(attrs, attidx)) >= 0)
+		{
+			/*
+			 * attidx is zero-based, attrnum is the normal attribute number
+			 */
+			AttrNumber	attrnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			Datum		value;
+			bool		isnull;
+
+			/*
+			 * System attributes are not added into interesting_attrs in
+			 * relcache
+			 */
+			Assert(attrnum > 0);
+
+			value = heap_getattr(&oldtup, attrnum, tupdesc, &isnull);
+
+			/* No need to check attributes that can't be stored externally */
+			if (isnull ||
+				TupleDescCompactAttr(tupdesc, attrnum - 1)->attlen != -1)
+				continue;
+
+			/* Check if the old tuple's attribute is stored externally */
+			if (VARATT_IS_EXTERNAL((struct varlena *) DatumGetPointer(value)))
+			{
+				rep_id_key_required = true;
+				break;
+			}
+		}
+
+		bms_free(attrs);
+	}
 
 	/* Update the tuple with table oid */
 	slot->tts_tableOid = RelationGetRelid(relation);
@@ -437,7 +480,6 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	bms_free(sum_attrs);
 	bms_free(pk_attrs);
 	bms_free(rid_attrs);
-	bms_free(mix_attrs);
 	bms_free(idx_attrs);
 
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 3dec1ee657d..b975612bbdd 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -156,6 +156,7 @@ bthandler(PG_FUNCTION_ARGS)
 		.amproperty = btproperty,
 		.ambuildphasename = btbuildphasename,
 		.amvalidate = btvalidate,
+		.amcomparedatums = NULL,
 		.amadjustmembers = btadjustmembers,
 		.ambeginscan = btbeginscan,
 		.amrescan = btrescan,
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 87491796523..458d48ca79e 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -367,6 +367,7 @@ void
 simple_table_tuple_update(Relation rel, ItemPointer otid,
 						  TupleTableSlot *slot,
 						  Snapshot snapshot,
+						  const Bitmapset *mix_attrs,
 						  TU_UpdateIndexes *update_indexes)
 {
 	TM_Result	result;
@@ -377,7 +378,9 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, &lockmode, update_indexes);
+								&tmfd, &lockmode,
+								mix_attrs,
+								update_indexes);
 
 	switch (result)
 	{
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index dd57624b4f9..81347c7b47e 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -963,10 +963,18 @@ index_register(Oid heap,
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
 	newind->il_info->ii_ExpressionsState = NIL;
+	/* expression attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_ExpressionsAttrs =
+		copyObject(indexInfo->ii_ExpressionsAttrs);
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
 	newind->il_info->ii_PredicateState = NULL;
+	/* predicate attrs will likely be null, but may as well copy it */
+	newind->il_info->ii_PredicateAttrs =
+		copyObject(indexInfo->ii_PredicateAttrs);
+	newind->il_info->ii_CheckedPredicate = false;
+	newind->il_info->ii_PredicateSatisfied = false;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 43de42ce39e..fe536c9740f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/transam.h"
@@ -58,6 +59,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -2412,6 +2414,61 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ * BuildUpdateIndexInfo
+ *
+ * For expression indexes updates may not change the indexed value allowing
+ * for a HOT update.  Add information to the IndexInfo to allow for checking
+ * if the indexed value has changed.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to not incur the
+ * overhead in the common non-expression cases.
+ * ----------------
+ */
+void
+BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo)
+{
+	for (int j = 0; j < resultRelInfo->ri_NumIndices; j++)
+	{
+		int			i;
+		int			indnatts;
+		Bitmapset  *attrs = NULL;
+		IndexInfo  *ii = resultRelInfo->ri_IndexRelationInfo[j];
+
+		indnatts = ii->ii_NumIndexAttrs;
+
+		/* Collect key attributes used by the index, key and including */
+		for (i = 0; i < indnatts; i++)
+		{
+			AttrNumber	attnum = ii->ii_IndexAttrNumbers[i];
+
+			if (attnum != 0)
+				attrs = bms_add_member(attrs, attnum - FirstLowInvalidHeapAttributeNumber);
+		}
+
+		/* Collect attributes used in the expression */
+		if (ii->ii_Expressions)
+			pull_varattnos((Node *) ii->ii_Expressions,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_ExpressionsAttrs);
+
+		/* Collect attributes used in the predicate */
+		if (ii->ii_Predicate)
+			pull_varattnos((Node *) ii->ii_Predicate,
+						   resultRelInfo->ri_RangeTableIndex,
+						   &ii->ii_PredicateAttrs);
+
+		/*
+		 * Combine key, including, and expression, but not partial index
+		 * predicate attributes.
+		 */
+		ii->ii_IndexedAttrs = bms_union(attrs, ii->ii_ExpressionsAttrs);
+
+		/* All indexes should index *something*! */
+		Assert(!bms_is_empty(ii->ii_IndexedAttrs));
+	}
+}
+
 /* ----------------
  *		BuildIndexInfo
  *			Construct an IndexInfo record for an open index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 0a1a68e0644..690a2511023 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -102,7 +102,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
-	if (numIndexes == 0)
+	if (numIndexes == 0 || updateIndexes == TU_None)
 		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
@@ -314,15 +314,18 @@ CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+
 	CatalogCloseIndexes(indstate);
+	bms_free(updatedAttrs);
 }
 
 /*
@@ -338,12 +341,15 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
 						   CatalogIndexState indstate)
 {
 	TU_UpdateIndexes updateIndexes = TU_All;
+	Bitmapset  *updatedAttrs;
 
 	CatalogTupleCheckConstraints(heapRel, tup);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
+	updatedAttrs = simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = updatedAttrs;
 	CatalogIndexInsert(indstate, tup, updateIndexes);
+	((ResultRelInfo *) indstate)->ri_ChangedIndexedCols = NULL;
+	bms_free(updatedAttrs);
 }
 
 /*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index c78dcea98c1..ff8da5be5f8 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -292,8 +292,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsAttrs = NULL;
 	indexInfo->ii_Predicate = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateAttrs = NULL;
+	indexInfo->ii_CheckedPredicate = false;
+	indexInfo->ii_PredicateSatisfied = false;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 6ae0f959592..1275feffae9 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -109,11 +109,15 @@
 #include "access/genam.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/executor.h"
+#include "nodes/bitmapset.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
+#include "utils/datum.h"
 #include "utils/injection_point.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
@@ -324,8 +328,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	Relation	heapRelation;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
-	Datum		values[INDEX_MAX_KEYS];
-	bool		isnull[INDEX_MAX_KEYS];
+	Datum		loc_values[INDEX_MAX_KEYS];
+	bool		loc_isnull[INDEX_MAX_KEYS];
 
 	Assert(ItemPointerIsValid(tupleid));
 
@@ -349,13 +353,13 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
 
-	/*
-	 * for each index, form and insert the index tuple
-	 */
+	/* Insert into each index that needs updating */
 	for (i = 0; i < numIndices; i++)
 	{
 		Relation	indexRelation = relationDescs[i];
 		IndexInfo  *indexInfo;
+		Datum	   *values;
+		bool	   *isnull;
 		bool		applyNoDupErr;
 		IndexUniqueCheck checkUnique;
 		bool		indexUnchanged;
@@ -372,7 +376,7 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * Skip processing of non-summarizing indexes if we only update
-		 * summarizing indexes
+		 * summarizing indexes or if this index is unchanged.
 		 */
 		if (onlySummarizing && !indexInfo->ii_Summarizing)
 			continue;
@@ -393,8 +397,15 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 				indexInfo->ii_PredicateState = predicate;
 			}
 
+			/* Check the index predicate if we haven't done so earlier on */
+			if (!indexInfo->ii_CheckedPredicate)
+			{
+				indexInfo->ii_PredicateSatisfied = ExecQual(predicate, econtext);
+				indexInfo->ii_CheckedPredicate = true;
+			}
+
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!indexInfo->ii_PredicateSatisfied)
 				continue;
 		}
 
@@ -402,11 +413,10 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		 * FormIndexDatum fills in its values and isnull parameters with the
 		 * appropriate values for the column(s) of the index.
 		 */
-		FormIndexDatum(indexInfo,
-					   slot,
-					   estate,
-					   values,
-					   isnull);
+		FormIndexDatum(indexInfo, slot, estate, loc_values, loc_isnull);
+
+		values = loc_values;
+		isnull = loc_isnull;
 
 		/* Check whether to apply noDupErr to this index */
 		applyNoDupErr = noDupErr &&
@@ -613,7 +623,12 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		checkedIndex = true;
 
 		/* Check for partial index */
-		if (indexInfo->ii_Predicate != NIL)
+		if (indexInfo->ii_CheckedPredicate && !indexInfo->ii_PredicateSatisfied)
+		{
+			/* We've already checked and the predicate wasn't satisfied. */
+			continue;
+		}
+		else if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ca14cdabdd0..fc6f7aa8fad 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1282,6 +1282,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	/* The following fields are set later if needed */
 	resultRelInfo->ri_RowIdAttNo = 0;
 	resultRelInfo->ri_extraUpdatedCols = NULL;
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
 	resultRelInfo->ri_projectNew = NULL;
 	resultRelInfo->ri_newTupleSlot = NULL;
 	resultRelInfo->ri_oldTupleSlot = NULL;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 173d2fe548d..0146e6f61ef 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -32,6 +32,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -936,7 +937,13 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
+		/*
+		 * We're not going to call ExecCheckIndexedAttrsForChanges here
+		 * because we've already identified the changes earlier on thanks to
+		 * slot_modify_data.
+		 */
 		simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
+								  resultRelInfo->ri_ChangedIndexedCols,
 								  &update_indexes);
 
 		conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 46ff6da8289..8fca5e09a26 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -17,6 +17,7 @@
  *		ExecModifyTable		- retrieve the next tuple from the node
  *		ExecEndModifyTable	- shut down the ModifyTable node
  *		ExecReScanModifyTable - rescan the ModifyTable node
+ *		ExecCheckIndexedAttrsForChanges - find set of updated indexed columns
  *
  *	 NOTES
  *		The ModifyTable node receives input from its outerPlan, which is
@@ -53,12 +54,18 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/attnum.h"
+#include "access/sysattr.h"
 #include "access/tableam.h"
+#include "access/tupconvert.h"
+#include "access/tupdesc.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -68,8 +75,11 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/float.h"
 #include "utils/injection_point.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/snapmgr.h"
 
 
@@ -176,6 +186,224 @@ static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
 										   ResultRelInfo *resultRelInfo,
 										   bool canSetTag);
 
+/*
+ * ExecCheckIndexedAttrsForChanges
+ *
+ * Determine which indexes need updating by finding the set of modified indexed
+ * attributes.
+ *
+ * For which implement the amcomparedatums() index AM API we'll need to form
+ * index datum and compare each attribute to see if anything actually changed.
+ *
+ * The goal is for the executor to know, ahead of calling into the table AM to
+ * process the update and before calling into the index AM for inserting new
+ * index tuples, which attributes in the new TupleTableSlot, if any, truely
+ * necessitate a new index tuple.
+ *
+ * Returns a Bitmapset of attributes that intersects with indexes which require
+ * a new index tuple.
+ */
+Bitmapset *
+ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+								EState *estate,
+								TupleTableSlot *old_tts,
+								TupleTableSlot *new_tts)
+{
+	Relation	relation = relinfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(relation);
+	Bitmapset  *mix_attrs = NULL;	/* modified indexed attributes */
+
+	/* If no indexes, we're done */
+	if (relinfo->ri_NumIndices == 0)
+		return NULL;
+
+	/* Find the indexes that reference this attribute */
+	for (int i = 0; i < relinfo->ri_NumIndices; i++)
+	{
+		Relation	index = relinfo->ri_IndexRelationDescs[i];
+		const IndexAmRoutine *amroutine = index->rd_indam;
+		IndexInfo  *indexInfo = relinfo->ri_IndexRelationInfo[i];
+		Bitmapset  *m_attrs = NULL; /* (possibly) modified indexed attrs */
+		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attrs */
+		Bitmapset  *u_attrs = NULL; /* unmodified indexed attrs */
+		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
+		bool		supports_ios = (amroutine->amcanreturn != NULL);
+		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		int			num_datums = supports_ios ?
+			indexInfo->ii_NumIndexAttrs : indexInfo->ii_NumIndexKeyAttrs;
+
+		/* If we've reviewed all the attributes on this index, move on */
+		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
+			continue;
+
+		/* Add partial index attributes */
+		if (is_partial)
+			p_attrs = bms_add_members(p_attrs, indexInfo->ii_PredicateAttrs);
+
+		/* Compare the index datums for equality */
+		for (int j = 0; j < num_datums; j++)
+		{
+			AttrNumber	rel_attrnum = indexInfo->ii_IndexAttrNumbers[j];
+			int			rel_attridx = rel_attrnum - FirstLowInvalidHeapAttributeNumber;
+			int			nth_expr = 0;
+			int16		typlen;
+			bool		typbyval;
+			Datum		old_value;
+			Datum		new_value;
+			bool		old_null;
+			bool		new_null;
+			bool		values_equal = false;
+
+			/* System attributes */
+			if (rel_attrnum < 0)
+			{
+				/* Extract system values from both slots for this attribute */
+				old_value = slot_getsysattr(old_tts, rel_attrnum, &old_null);
+				new_value = slot_getsysattr(new_tts, rel_attrnum, &new_null);
+
+				/* The only allowed system columns are OIDs, so do this */
+				values_equal = (DatumGetObjectId(old_value) == DatumGetObjectId(new_value));
+				goto equality_determined;
+			}
+
+			/*
+			 * This is an expression attribute, but in an effort to avoid the
+			 * expense of IndexFormDatum we're now faced with testing for
+			 * equality so we'll have to exec the expressions and test for
+			 * binary equality of the results.
+			 */
+			else if (rel_attrnum == 0)
+			{
+				TupleTableSlot *save_scantuple = econtext->ecxt_scantuple;
+				Oid			expr_type_oid;
+				Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+				ExprState  *state;
+
+				if (indexInfo->ii_ExpressionsState == NIL)
+				{
+					/* First time through, set up expression evaluation state */
+					indexInfo->ii_ExpressionsState =
+						ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+				}
+
+				state = (ExprState *) list_nth(indexInfo->ii_ExpressionsState, nth_expr);
+
+				econtext->ecxt_scantuple = old_tts;
+				old_value = ExecEvalExprSwitchContext(state,
+													  GetPerTupleExprContext(estate),
+													  &old_null);
+
+				econtext->ecxt_scantuple = new_tts;
+				new_value = ExecEvalExprSwitchContext(state,
+													  GetPerTupleExprContext(estate),
+													  &new_null);
+
+				econtext->ecxt_scantuple = save_scantuple;
+
+				/*
+				 * NOTE: test for NULL cases here to potentially avoid looking
+				 * up the type information.  It's a tad redundant, but worth
+				 * it.
+				 */
+
+				/* A change to/from NULL, so not equal */
+				if (old_null != new_null)
+				{
+					values_equal = false;
+					goto equality_determined;
+				}
+
+				/* Both NULL, no change record as unmodified */
+				if (old_null)
+				{
+					values_equal = true;
+					goto equality_determined;
+				}
+
+				/* Get type OID from the expression */
+				expr_type_oid = exprType((Node *) expr);
+
+				/* Get type information from the OID */
+				get_typlenbyval(expr_type_oid, &typlen, &typbyval);
+			}
+			/* Not a system or expression attribute */
+			else
+			{
+				CompactAttribute *att = TupleDescCompactAttr(tupdesc, rel_attrnum - 1);
+
+				/* Extract values from both slots for this attribute */
+				old_value = slot_getattr(old_tts, rel_attrnum, &old_null);
+				new_value = slot_getattr(new_tts, rel_attrnum, &new_null);
+
+				typlen = att->attlen;
+				typbyval = att->attbyval;
+			}
+
+			/* A change to/from NULL, so not equal */
+			if (old_null != new_null)
+			{
+				values_equal = false;
+				goto equality_determined;
+			}
+
+			/* Both NULL, no change record as unmodified */
+			if (old_null)
+			{
+				values_equal = true;
+				goto equality_determined;
+			}
+
+			if (has_am_compare)
+			{
+				/*
+				 * NOTE: For AM comparison, pass the 1-based index attribute
+				 * number. The AM's compare function expects the same
+				 * numbering as used internally by the AM.
+				 */
+				values_equal = amroutine->amcomparedatums(index, j + 1,
+														  old_value, old_null,
+														  new_value, new_null);
+			}
+			else
+			{
+				values_equal = datumIsEqual(old_value, new_value, typbyval, typlen);
+			}
+
+	equality_determined:;
+			if (!values_equal)
+				if (rel_attrnum == 0)
+				{
+					Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
+
+					pull_varattnos((Node *) expr, relinfo->ri_RangeTableIndex, &m_attrs);
+				}
+				else
+					m_attrs = bms_add_member(m_attrs, rel_attridx);
+			else
+				u_attrs = bms_add_member(u_attrs, rel_attridx);
+
+			if (rel_attrnum == 0)
+				nth_expr++;
+		}
+
+		/*
+		 * Here we know all the attributes that might be modified and all
+		 * those we know haven't been across all indexes.  Take the difference
+		 * and add it to the modified indexed attributes set.
+		 */
+		m_attrs = bms_del_members(m_attrs, u_attrs);
+		p_attrs = bms_del_members(p_attrs, u_attrs);
+		mix_attrs = bms_add_members(mix_attrs, m_attrs);
+		mix_attrs = bms_add_members(mix_attrs, p_attrs);
+
+		bms_free(m_attrs);
+		bms_free(u_attrs);
+		bms_free(p_attrs);
+	}
+
+	return mix_attrs;
+}
 
 /*
  * Verify that the tuples to be produced by INSERT match the
@@ -2168,14 +2396,17 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
  */
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+			  TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	bool		partition_constraint_failed;
 	TM_Result	result;
 
+	/* The set of modified indexed attributes that trigger new index entries */
+	Bitmapset  *mix_attrs = NULL;
+
 	updateCxt->crossPartUpdate = false;
 
 	/*
@@ -2292,9 +2523,38 @@ lreplace:
 		ExecConstraints(resultRelInfo, slot, estate);
 
 	/*
-	 * replace the heap tuple
+	 * Identify which, if any, indexed attributes were modified here so that
+	 * we might reuse it in a few places.
+	 */
+	bms_free(resultRelInfo->ri_ChangedIndexedCols);
+	resultRelInfo->ri_ChangedIndexedCols = NULL;
+
+	/*
+	 * During updates we'll need a bit more information in IndexInfo but we've
+	 * delayed adding it until here.  We check to ensure that there are
+	 * indexes, that something has changed that is indexed, and that the first
+	 * index doesn't yet have ii_IndexedAttrs set as a way to ensure we only
+	 * build this when needed and only once.  We don't build this in
+	 * ExecOpenIndicies() as it is unnecessary overhead when not performing an
+	 * update.
+	 */
+	if (resultRelInfo->ri_NumIndices > 0 &&
+		bms_is_empty(resultRelInfo->ri_IndexRelationInfo[0]->ii_IndexedAttrs))
+		BuildUpdateIndexInfo(resultRelInfo);
+
+	/*
+	 * Next up we need to find out the set of indexed attributes that have
+	 * changed in value and should trigger a new index tuple.  We could start
+	 * with the set of updated columns via ExecGetUpdatedCols(), but if we do
+	 * we will overlook attributes directly modified by heap_modify_tuple()
+	 * which are not known to ExecGetUpdatedCols().
+	 */
+	mix_attrs = ExecCheckIndexedAttrsForChanges(resultRelInfo, estate, oldSlot, slot);
+
+	/*
+	 * Call into the table AM to update the heap tuple.
 	 *
-	 * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+	 * NOTE: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
 	 * the row to be updated is visible to that snapshot, and throw a
 	 * can't-serialize error if not. This is a special-case behavior needed
 	 * for referential integrity updates in transaction-snapshot mode
@@ -2306,8 +2566,12 @@ lreplace:
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&context->tmfd, &updateCxt->lockmode,
+								mix_attrs,
 								&updateCxt->updateIndexes);
 
+	Assert(bms_is_empty(resultRelInfo->ri_ChangedIndexedCols));
+	resultRelInfo->ri_ChangedIndexedCols = mix_attrs;
+
 	return result;
 }
 
@@ -2325,7 +2589,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
 
-	/* insert index entries for tuple if necessary */
+	/* Insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
 		recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
 											   slot, context->estate,
@@ -2524,8 +2788,9 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		lockedtid = *tupleid;
-		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+
+		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, oldSlot,
+							   slot, canSetTag, &updateCxt);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -3222,8 +3487,8 @@ lmerge_matched:
 					Assert(oldtuple == NULL);
 
 					result = ExecUpdateAct(context, resultRelInfo, tupleid,
-										   NULL, newslot, canSetTag,
-										   &updateCxt);
+										   NULL, resultRelInfo->ri_oldTupleSlot,
+										   newslot, canSetTag, &updateCxt);
 
 					/*
 					 * As in ExecUpdate(), if ExecUpdateAct() reports that a
@@ -3248,6 +3513,7 @@ lmerge_matched:
 									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
+
 				break;
 
 			case CMD_DELETE:
@@ -4354,7 +4620,7 @@ ExecModifyTable(PlanState *pstate)
 		 * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
 		 * to be updated/deleted/merged.  For a heap relation, that's a TID;
 		 * otherwise we may have a wholerow junk attr that carries the old
-		 * tuple in toto.  Keep this in step with the part of
+		 * tuple in total.  Keep this in step with the part of
 		 * ExecInitModifyTable that sets up ri_RowIdAttNo.
 		 */
 		if (operation == CMD_UPDATE || operation == CMD_DELETE ||
@@ -4530,6 +4796,7 @@ ExecModifyTable(PlanState *pstate)
 				/* Now apply the update. */
 				slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
 								  oldSlot, slot, node->canSetTag);
+
 				if (tuplock)
 					UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
 								InplaceUpdateTupleLock);
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index a4765876c31..17f5f66b25f 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -238,6 +238,10 @@ bms_make_singleton(int x)
 void
 bms_free(Bitmapset *a)
 {
+#if USE_ASSERT_CHECKING
+	Assert(bms_is_valid_set(a));
+#endif
+
 	if (a)
 		pfree(a);
 }
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 2caec621d73..dd092bacad9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -857,10 +857,14 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 	/* expressions */
 	n->ii_Expressions = expressions;
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsAttrs = NULL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateAttrs = NULL;
+	n->ii_CheckedPredicate = false;
+	n->ii_PredicateSatisfied = false;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index ad281e7069b..90b0c2c40e9 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -275,7 +275,6 @@
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
-#include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
@@ -285,12 +284,14 @@
 #include "storage/procarray.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
+#include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
+#include "utils/relcache.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1110,15 +1111,18 @@ slot_store_data(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
  * "slot" is filled with a copy of the tuple in "srcslot", replacing
  * columns provided in "tupleData" and leaving others as-is.
  *
+ * Returns a bitmap of the modified columns.
+ *
  * Caution: unreplaced pass-by-ref columns in "slot" will point into the
  * storage for "srcslot".  This is OK for current usage, but someday we may
  * need to materialize "slot" at the end to make it independent of "srcslot".
  */
-static void
+static Bitmapset *
 slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				 LogicalRepRelMapEntry *rel,
 				 LogicalRepTupleData *tupleData)
 {
+	Bitmapset  *modified = NULL;
 	int			natts = slot->tts_tupleDescriptor->natts;
 	int			i;
 
@@ -1195,6 +1199,27 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 				slot->tts_isnull[i] = true;
 			}
 
+			/*
+			 * Determine if the replicated value changed the local value by
+			 * comparing slots.  This is a subset of
+			 * ExecCheckIndexedAttrsForChanges.
+			 */
+			if (srcslot->tts_isnull[i] != slot->tts_isnull[i])
+			{
+				/* One is NULL, the other is not so the value changed */
+				modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+			else if (!srcslot->tts_isnull[i])
+			{
+				/* Both are not NULL, compare their values */
+
+				if (!datumIsEqual(srcslot->tts_values[i],
+								  slot->tts_values[i],
+								  att->attbyval,
+								  att->attlen))
+					modified = bms_add_member(modified, i + 1 - FirstLowInvalidHeapAttributeNumber);
+			}
+
 			/* Reset attnum for error callback */
 			apply_error_callback_arg.remote_attnum = -1;
 		}
@@ -1202,6 +1227,8 @@ slot_modify_data(TupleTableSlot *slot, TupleTableSlot *srcslot,
 
 	/* And finally, declare that "slot" contains a valid virtual tuple */
 	ExecStoreVirtualTuple(slot);
+
+	return modified;
 }
 
 /*
@@ -2918,6 +2945,7 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	ConflictTupleInfo conflicttuple = {0};
 	bool		found;
 	MemoryContext oldctx;
+	Bitmapset  *indexed = NULL;
 
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
 	ExecOpenIndices(relinfo, false);
@@ -2934,6 +2962,8 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 	 */
 	if (found)
 	{
+		Bitmapset  *modified = NULL;
+
 		/*
 		 * Report the conflict if the tuple was modified by a different
 		 * origin.
@@ -2957,15 +2987,29 @@ apply_handle_update_internal(ApplyExecutionData *edata,
 
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		slot_modify_data(remoteslot, localslot, relmapentry, newtup);
+		modified = slot_modify_data(remoteslot, localslot, relmapentry, newtup);
 		MemoryContextSwitchTo(oldctx);
 
+		/*
+		 * Normally we'd call ExecCheckIndexedAttrForChanges but here we have
+		 * the record of changed columns in the replication state, so let's
+		 * use that instead.
+		 */
+		indexed = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+											 INDEX_ATTR_BITMAP_INDEXED);
+
+		bms_free(relinfo->ri_ChangedIndexedCols);
+		relinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+		bms_free(indexed);
+
 		EvalPlanQualSetSlot(&epqstate, remoteslot);
 
 		InitConflictIndexes(relinfo);
 
-		/* Do the actual update. */
+		/* First check privileges */
 		TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_UPDATE);
+
+		/* Then do the actual update. */
 		ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot,
 								 remoteslot);
 	}
@@ -3455,6 +3499,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				bool		found;
 				EPQState	epqstate;
 				ConflictTupleInfo conflicttuple = {0};
+				Bitmapset  *modified = NULL;
+				Bitmapset  *indexed;
 
 				/* Get the matching local tuple from the partition. */
 				found = FindReplTupleInLocalRel(edata, partrel,
@@ -3523,8 +3569,8 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 				 * remoteslot_part.
 				 */
 				oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-				slot_modify_data(remoteslot_part, localslot, part_entry,
-								 newtup);
+				modified = slot_modify_data(remoteslot_part, localslot, part_entry,
+											newtup);
 				MemoryContextSwitchTo(oldctx);
 
 				EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL);
@@ -3549,6 +3595,18 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					EvalPlanQualSetSlot(&epqstate, remoteslot_part);
 					TargetPrivilegesCheck(partrelinfo->ri_RelationDesc,
 										  ACL_UPDATE);
+
+					/*
+					 * Normally we'd call ExecCheckIndexedAttrForChanges but
+					 * here we have the record of changed columns in the
+					 * replication state, so let's use that instead.
+					 */
+					indexed = RelationGetIndexAttrBitmap(partrelinfo->ri_RelationDesc,
+														 INDEX_ATTR_BITMAP_INDEXED);
+					bms_free(partrelinfo->ri_ChangedIndexedCols);
+					partrelinfo->ri_ChangedIndexedCols = bms_int_members(modified, indexed);
+					bms_free(indexed);
+
 					ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate,
 											 localslot, remoteslot_part);
 				}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 6b634c9fff1..8cc97e4fbca 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2477,6 +2477,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_idattr);
 	bms_free(relation->rd_hotblockingattr);
 	bms_free(relation->rd_summarizedattr);
+	bms_free(relation->rd_indexedattr);
 	if (relation->rd_pubdesc)
 		pfree(relation->rd_pubdesc);
 	if (relation->rd_options)
@@ -5278,6 +5279,7 @@ RelationGetIndexPredicate(Relation relation)
  *									index (empty if FULL)
  *	INDEX_ATTR_BITMAP_HOT_BLOCKING	Columns that block updates from being HOT
  *	INDEX_ATTR_BITMAP_SUMMARIZED	Columns included in summarizing indexes
+ *	INDEX_ATTR_BITMAP_INDEXED		Columns referenced by indexes
  *
  * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
  * we can include system attributes (e.g., OID) in the bitmap representation.
@@ -5302,6 +5304,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
 	Bitmapset  *hotblockingattrs;	/* columns with HOT blocking indexes */
 	Bitmapset  *summarizedattrs;	/* columns with summarizing indexes */
+	Bitmapset  *indexedattrs;	/* columns referenced by indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
 	Oid			relpkindex;
@@ -5324,6 +5327,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_hotblockingattr);
 			case INDEX_ATTR_BITMAP_SUMMARIZED:
 				return bms_copy(relation->rd_summarizedattr);
+			case INDEX_ATTR_BITMAP_INDEXED:
+				return bms_copy(relation->rd_indexedattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -5368,6 +5373,7 @@ restart:
 	idindexattrs = NULL;
 	hotblockingattrs = NULL;
 	summarizedattrs = NULL;
+	indexedattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -5500,10 +5506,14 @@ restart:
 		bms_free(idindexattrs);
 		bms_free(hotblockingattrs);
 		bms_free(summarizedattrs);
+		bms_free(indexedattrs);
 
 		goto restart;
 	}
 
+	/* Combine all index attributes */
+	indexedattrs = bms_union(hotblockingattrs, summarizedattrs);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	relation->rd_attrsvalid = false;
 	bms_free(relation->rd_keyattr);
@@ -5516,6 +5526,8 @@ restart:
 	relation->rd_hotblockingattr = NULL;
 	bms_free(relation->rd_summarizedattr);
 	relation->rd_summarizedattr = NULL;
+	bms_free(relation->rd_indexedattr);
+	relation->rd_indexedattr = NULL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5530,6 +5542,7 @@ restart:
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
 	relation->rd_summarizedattr = bms_copy(summarizedattrs);
+	relation->rd_indexedattr = bms_copy(indexedattrs);
 	relation->rd_attrsvalid = true;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5546,6 +5559,8 @@ restart:
 			return hotblockingattrs;
 		case INDEX_ATTR_BITMAP_SUMMARIZED:
 			return summarizedattrs;
+		case INDEX_ATTR_BITMAP_INDEXED:
+			return indexedattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index ecfbd017d66..2a36b7e4a18 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -211,6 +211,33 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/*
+ * amcomparedatums - Compare datums to determine if index update is needed
+ *
+ * This function compares old_datum and new_datum to determine if they would
+ * produce different index entries. For extraction-based indexes (GIN, RUM),
+ * this should:
+ *  1. Extract keys from old_datum using the opclass's extractValue function
+ *  2. Extract keys from new_datum using the opclass's extractValue function
+ *  3. Compare the two sets of keys using appropriate equality operators
+ *  4. Return true if the sets are equal (no index update needed)
+ *
+ * The comparison should account for:
+ *  - Different numbers of extracted keys
+ *  - NULL values
+ *  - Type-specific equality (not just binary equality)
+ *  - Opclass parameters (e.g., path in bson_rum_single_path_ops)
+ *
+ * For the DocumentDB example with path='a', this would extract values at
+ * path 'a' from both old and new BSON documents and compare them using
+ * BSON's equality operator.
+ */
+/* identify if updated datums would produce one or more index entries */
+typedef bool (*amcomparedatums_function) (Relation indexRelation,
+										  int attno,
+										  Datum old_datum, bool old_isnull,
+										  Datum new_datum, bool new_isnull);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -313,6 +340,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	amcomparedatums_function amcomparedatums;	/* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index fa1a3b20e09..69771fe947b 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -100,6 +100,9 @@ extern PGDLLIMPORT int gin_pending_list_limit;
 extern void ginGetStats(Relation index, GinStatsData *stats);
 extern void ginUpdateStats(Relation index, const GinStatsData *stats,
 						   bool is_build);
+extern bool gincomparedatums(Relation index, int attnum,
+							 Datum old_datum, bool old_isnull,
+							 Datum new_datum, bool new_isnull);
 
 extern void _gin_parallel_build_main(dsm_segment *seg, shm_toc *toc);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 41193d5b3d2..029f8e84e8a 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -369,7 +369,7 @@ extern TM_Result heap_update(Relation relation, HeapTupleData *oldtup,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode, Buffer buffer,
 							 Page page, BlockNumber block, ItemId lp, Bitmapset *hot_attrs,
 							 Bitmapset *sum_attrs, Bitmapset *pk_attrs, Bitmapset *rid_attrs,
-							 Bitmapset *mix_attrs, Buffer *vmbuffer,
+							 const Bitmapset *mix_attrs, Buffer *vmbuffer,
 							 bool rep_id_key_required, TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
@@ -404,8 +404,8 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 
 extern void simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, const ItemPointerData *tid);
-extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
-							   HeapTuple tup, TU_UpdateIndexes *update_indexes);
+extern Bitmapset *simple_heap_update(Relation relation, const ItemPointerData *otid,
+									 HeapTuple tup, TU_UpdateIndexes *update_indexes);
 
 extern TransactionId heap_index_delete_tuples(Relation rel,
 											  TM_IndexDeleteOp *delstate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 77224859685..532656a487f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1179,6 +1179,10 @@ extern int	btgettreeheight(Relation rel);
 
 extern CompareType bttranslatestrategy(StrategyNumber strategy, Oid opfamily);
 extern StrategyNumber bttranslatecmptype(CompareType cmptype, Oid opfamily);
+extern bool btcomparedatums(Relation index, int attnum,
+							Datum old_datum, bool old_isnull,
+							Datum new_datum, bool new_isnull);
+
 
 /*
  * prototypes for internal functions in nbtree.c
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index e2ec5289d4d..4bed0f8e56e 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -549,6 +549,7 @@ typedef struct TableAmRoutine
 								 bool wait,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
+								 const Bitmapset *updated_cols,
 								 TU_UpdateIndexes *update_indexes);
 
 	/* see table_tuple_lock() for reference about parameters */
@@ -1512,12 +1513,12 @@ 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,
-				   TU_UpdateIndexes *update_indexes)
+				   const Bitmapset *mix_cols, TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
-										 lockmode, update_indexes);
+										 wait, tmfd, lockmode,
+										 mix_cols, update_indexes);
 }
 
 /*
@@ -2020,6 +2021,7 @@ extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
 									  Snapshot snapshot);
 extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
 									  TupleTableSlot *slot, Snapshot snapshot,
+									  const Bitmapset *mix_attrs,
 									  TU_UpdateIndexes *update_indexes);
 
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index b259c4141ed..14a39beab6e 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -132,6 +132,7 @@ extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
 							 const AttrMap *attmap);
 
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
+extern void BuildUpdateIndexInfo(ResultRelInfo *resultRelInfo);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 						   TupleTableSlot *slot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 5929aabc353..b4c757af618 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -739,6 +739,11 @@ extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+extern Bitmapset *ExecWhichIndexesRequireUpdates(ResultRelInfo *relinfo,
+												 Bitmapset *mix_attrs,
+												 EState *estate,
+												 TupleTableSlot *old_tts,
+												 TupleTableSlot *new_tts);
 extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 								   TupleTableSlot *slot, EState *estate,
 								   bool update,
@@ -800,5 +805,9 @@ extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node,
 											   Oid resultoid,
 											   bool missing_ok,
 											   bool update_cache);
+extern Bitmapset *ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
+												  EState *estate,
+												  TupleTableSlot *old_tts,
+												  TupleTableSlot *new_tts);
 
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 02265456978..d5af2f34d0f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,15 +174,29 @@ typedef struct IndexInfo
 	 */
 	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
 
+	/*
+	 * All key, expression, sumarizing, and partition attributes referenced by
+	 * this index
+	 */
+	Bitmapset  *ii_IndexedAttrs;
+
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	/* attributes exclusively referenced by expression indexes */
+	Bitmapset  *ii_ExpressionsAttrs;
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	/* attributes referenced by the predicate */
+	Bitmapset  *ii_PredicateAttrs;
+	/* partial index predicate determined yet? */
+	bool		ii_CheckedPredicate;
+	/* amupdate hint used to avoid rechecking predicate */
+	bool		ii_PredicateSatisfied;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
@@ -499,6 +513,12 @@ typedef struct ResultRelInfo
 	/* true if the above has been computed */
 	bool		ri_extraUpdatedCols_valid;
 
+	/*
+	 * For UPDATE a Bitmapset of the attributes that are both indexed and have
+	 * changed in value.
+	 */
+	Bitmapset  *ri_ChangedIndexedCols;
+
 	/* Projection to generate new tuple in an INSERT/UPDATE */
 	ProjectionInfo *ri_projectNew;
 	/* Slot to hold that tuple */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..95b38abfd89 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -164,6 +164,7 @@ typedef struct RelationData
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Bitmapset  *rd_hotblockingattr; /* cols blocking HOT update */
 	Bitmapset  *rd_summarizedattr;	/* cols indexed by summarizing indexes */
+	Bitmapset  *rd_indexedattr; /* all cols referenced by indexes */
 
 	PublicationDesc *rd_pubdesc;	/* publication descriptor, or NULL */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 2700224939a..5834ab7b903 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,7 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_IDENTITY_KEY,
 	INDEX_ATTR_BITMAP_HOT_BLOCKING,
 	INDEX_ATTR_BITMAP_SUMMARIZED,
+	INDEX_ATTR_BITMAP_INDEXED,
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/isolation/expected/insert-conflict-specconflict.out b/src/test/isolation/expected/insert-conflict-specconflict.out
index e34a821c403..54b3981918c 100644
--- a/src/test/isolation/expected/insert-conflict-specconflict.out
+++ b/src/test/isolation/expected/insert-conflict-specconflict.out
@@ -80,6 +80,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
@@ -172,6 +176,10 @@ pg_advisory_unlock
 t                 
 (1 row)
 
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
@@ -369,6 +377,10 @@ key|data
 step s1_commit: COMMIT;
 s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
 s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
+s2: NOTICE:  blurt_and_lock_123() called for k1 in session 2
+s2: NOTICE:  acquiring advisory lock on 2
 step s2_upsert: <... completed>
 step controller_show: SELECT * FROM upserttest;
 key|data       
@@ -530,6 +542,14 @@ isolation/insert-conflict-specconflict/s2|transactionid|ExclusiveLock|t
 step s2_commit: COMMIT;
 s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
 s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_123() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 2
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
+s1: NOTICE:  blurt_and_lock_4() called for k1 in session 1
+s1: NOTICE:  acquiring advisory lock on 4
 step s1_upsert: <... completed>
 step s1_noop: 
 step controller_show: SELECT * FROM upserttest;
diff --git a/src/test/regress/expected/heap_hot_updates.out b/src/test/regress/expected/heap_hot_updates.out
new file mode 100644
index 00000000000..14276e3cbca
--- /dev/null
+++ b/src/test/regress/expected/heap_hot_updates.out
@@ -0,0 +1,650 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+DROP TABLE t;
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+CREATE INDEX t_gin ON t USING gin(search_vec);
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (index keys changed)
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+ count 
+-------
+     1
+(1 row)
+
+-- Expected: 1 row
+DROP TABLE t CASCADE;
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (keys actually changed)
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Expected: 1 HOT (GIN keys semantically identical)
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: Still 1 HOT (not this one)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+INSERT INTO t VALUES (1, 50, 'below range');
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     150
+(1 row)
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           3 |                100.00 | t
+(1 row)
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+ cnt | max_val 
+-----+---------
+   1 |     160
+(1 row)
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           4 |                100.00 | t
+(1 row)
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+ cnt 
+-----
+   0
+(1 row)
+
+SELECT id, value, description FROM t;
+ id | value |  description  
+----+-------+---------------
+  1 |    50 | updated again
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           1 |                 50.00 | t
+(1 row)
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           1 |                 25.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_brin     |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT (BRIN allows it for single row)
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t_hash     |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT (HASH blocks it)
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           0 |                  0.00 | t
+(1 row)
+
+-- Expected: 0 HOT
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           1 |                 33.33 | t
+(1 row)
+
+-- Expected: 1 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT (BRIN permits single-row updates)
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           1 |                100.00 | t
+(1 row)
+
+-- Expected: 1 HOT
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             2 |           2 |                100.00 | t
+(1 row)
+
+-- Expected: 2 HOT
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             3 |           2 |                 66.67 | t
+(1 row)
+
+-- Expected: 2 HOT (HASH blocks it)
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             4 |           3 |                 75.00 | t
+(1 row)
+
+-- Expected: 3 HOT
+DROP TABLE t CASCADE;
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+ table_name | total_updates | hot_updates | hot_update_percentage | matches_expected 
+------------+---------------+-------------+-----------------------+------------------
+ t          |             1 |           0 |                  0.00 | t
+(1 row)
+
+DROP TABLE t CASCADE;
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 021d57f66bb..2d6641992e9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,6 +125,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 test: partition_merge partition_split partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
 
+
+# ----------
+# Another group of parallel tests, these focused on heap HOT updates
+# ----------
+test: heap_hot_updates
+
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/sql/heap_hot_updates.sql b/src/test/regress/sql/heap_hot_updates.sql
new file mode 100644
index 00000000000..e047bcddf5c
--- /dev/null
+++ b/src/test/regress/sql/heap_hot_updates.sql
@@ -0,0 +1,513 @@
+-- ================================================================
+-- Test Suite for Heap-only (HOT) Updates
+-- ================================================================
+
+-- Setup: Create function to measure HOT updates
+CREATE OR REPLACE FUNCTION check_hot_updates(
+    expected INT,
+    p_table_name TEXT DEFAULT 't',
+    p_schema_name TEXT DEFAULT current_schema()
+)
+RETURNS TABLE (
+    table_name TEXT,
+    total_updates BIGINT,
+    hot_updates BIGINT,
+    hot_update_percentage NUMERIC,
+    matches_expected BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    v_relid oid;
+    v_qualified_name TEXT;
+    v_hot_updates BIGINT;
+    v_updates BIGINT;
+    v_xact_hot_updates BIGINT;
+    v_xact_updates BIGINT;
+BEGIN
+    -- Force statistics update
+    PERFORM pg_stat_force_next_flush();
+
+    -- Get table OID
+    v_qualified_name := quote_ident(p_schema_name) || '.' || quote_ident(p_table_name);
+    v_relid := v_qualified_name::regclass;
+
+    IF v_relid IS NULL THEN
+	RAISE EXCEPTION 'Table %.% not found', p_schema_name, p_table_name;
+    END IF;
+
+    -- Get cumulative + transaction stats
+    v_hot_updates := COALESCE(pg_stat_get_tuples_hot_updated(v_relid), 0);
+    v_updates := COALESCE(pg_stat_get_tuples_updated(v_relid), 0);
+    v_xact_hot_updates := COALESCE(pg_stat_get_xact_tuples_hot_updated(v_relid), 0);
+    v_xact_updates := COALESCE(pg_stat_get_xact_tuples_updated(v_relid), 0);
+
+    v_hot_updates := v_hot_updates + v_xact_hot_updates;
+    v_updates := v_updates + v_xact_updates;
+
+    RETURN QUERY
+    SELECT
+	p_table_name::TEXT,
+	v_updates::BIGINT,
+	v_hot_updates::BIGINT,
+	CASE WHEN v_updates > 0
+	     THEN ROUND((v_hot_updates::numeric / v_updates::numeric * 100)::numeric, 2)
+	     ELSE 0
+	END,
+	(v_hot_updates = expected)::BOOLEAN;
+END;
+$$;
+
+CREATE COLLATION case_insensitive (
+    provider = libc,
+    locale = 'C'
+);
+
+
+-- ================================================================
+-- GIN Index on JSONB
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data);
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "database"]}');
+
+-- Change tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Change tags again - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+-- Add field without changing existing keys - GIN keys changed (added "note"), NOT HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "note": "test"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN Index with Unchanged Keys
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- Create GIN index on specific path
+CREATE INDEX t_gin_idx ON t USING gin((data->'tags'));
+INSERT INTO t VALUES (1, '{"tags": ["postgres", "sql"], "status": "active"}');
+
+-- Change non-indexed field - GIN keys on 'tags' unchanged, should be HOT
+UPDATE t SET data = '{"tags": ["postgres", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Change indexed tags - GIN keys changed, should NOT be HOT
+UPDATE t SET data = '{"tags": ["mysql", "sql"], "status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN with jsonb_path_ops
+-- ================================================================
+CREATE TABLE t(id INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin_idx ON t USING gin(data jsonb_path_ops);
+INSERT INTO t VALUES (1, '{"user": {"name": "alice"}, "tags": ["a", "b"]}');
+
+-- Change value at different path - keys changed, NOT HOT
+UPDATE t SET data = '{"user": {"name": "bob"}, "tags": ["a", "b"]}' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- Mixed Index Types (BRIN + Expression)
+-- ================================================================
+CREATE TABLE t(id INT, value INT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_idx ON t USING brin(value);
+CREATE INDEX t_expr_idx ON t((data->'status'));
+INSERT INTO t VALUES (1, 100, '{"status": "active"}');
+
+-- Update only BRIN column - should be HOT
+UPDATE t SET value = 200 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update only expression column - should NOT be HOT
+UPDATE t SET data = '{"status": "inactive"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - should NOT be HOT
+UPDATE t SET value = 300, data = '{"status": "pending"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- GIN Array Index - Order Insensitive Extraction
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    data JSONB
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- GIN index on JSONB array (extracts all elements)
+CREATE INDEX t_items_gin ON t USING GIN ((data->'items'));
+
+INSERT INTO t VALUES (1, '{"items": [1, 2, 3], "status": "active"}');
+
+-- Update: Reorder array elements
+-- JSONB equality: NOT equal (different arrays)
+-- GIN extraction: Same elements extracted (might allow HOT if not careful)
+UPDATE t SET data = '{"items": [3, 2, 1], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update: Add/remove element
+UPDATE t SET data = '{"items": [1, 2, 3, 4], "status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t;
+
+
+-- ================================================================
+-- TEST: GIN with TOASTed TEXT (tsvector)
+-- ================================================================
+CREATE TABLE t(id INT, content TEXT, search_vec tsvector)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+-- Create trigger to maintain tsvector
+CREATE TRIGGER tsvectorupdate_toast
+    BEFORE INSERT OR UPDATE ON t
+    FOR EACH ROW EXECUTE FUNCTION
+    tsvector_update_trigger(search_vec, 'pg_catalog.english', content);
+
+CREATE INDEX t_gin ON t USING gin(search_vec);
+
+-- Insert with large content (will be TOASTed)
+INSERT INTO t (id, content) VALUES
+    (1, repeat('important keyword ', 1000) || repeat('filler text ', 10000));
+
+-- Verify initial state
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('important');
+-- Expected: 1 row
+
+-- IMPORTANT: The BEFORE UPDATE trigger modifies search_vec, so by the time
+-- ExecWhichIndexesRequireUpdates() runs, search_vec has already changed.
+-- This means the comparison sees old tsvector vs. trigger-modified tsvector,
+-- not the natural progression. HOT won't happen because the trigger changed
+-- the indexed column.
+
+-- Update: Even though content keywords unchanged, trigger still fires
+UPDATE t
+SET content = repeat('important keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (trigger modifies search_vec, blocking HOT)
+-- This is actually correct behavior - the trigger updated an indexed column
+
+-- Update: Change indexed keywords
+UPDATE t
+SET content = repeat('critical keyword ', 1000) || repeat('different filler ', 10000)
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (index keys changed)
+
+-- Verify query correctness
+SELECT count(*) FROM t WHERE search_vec @@ to_tsquery('critical');
+-- Expected: 1 row
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- TEST: GIN with Array of Large Strings
+-- ================================================================
+CREATE TABLE t(id INT, tags TEXT[])
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_gin ON t USING gin(tags);
+
+-- Insert with large array elements (might be TOASTed)
+INSERT INTO t (id, tags) VALUES
+    (1, ARRAY[repeat('tag1', 1000), repeat('tag2', 1000)]);
+
+-- Update: Change to different large values - NOT HOT
+UPDATE t
+SET tags = ARRAY[repeat('tag3', 1000), repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT (keys actually changed)
+
+-- Update: Keep same tag values, just reorder - SHOULD BE HOT
+-- (GIN is order-insensitive: both [tag3,tag4] and [tag4,tag3]
+-- extract to the same sorted key set ['tag3','tag4'])
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000), repeat('tag3', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT (GIN keys semantically identical)
+
+-- Update: Remove an element - NOT HOT (keys changed)
+UPDATE t
+SET tags = ARRAY[repeat('tag4', 1000)]
+WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: Still 1 HOT (not this one)
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN Index with Partial Predicate
+-- ================================================================
+CREATE TABLE t(
+    id INT PRIMARY KEY,
+    value INT,
+    description TEXT
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_partial_idx ON t USING brin(value) WHERE value > 100;
+
+INSERT INTO t VALUES (1, 50, 'below range');
+
+-- Test 1: Outside predicate
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Validate: Predicate query returns 0 rows
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+-- Test 2: Transition into predicate
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Validate: Predicate query returns 1 row with correct value
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 3: Inside predicate, value changes
+UPDATE t SET value = 160, description = 'updated again' WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+
+-- Validate: Updated value (160) is returned
+SELECT COUNT(*) as cnt, MAX(value) as max_val FROM t WHERE value > 100;
+
+-- Test 4: Transition out of predicate
+UPDATE t SET value = 50 WHERE id = 1;
+SELECT * FROM check_hot_updates(4);
+
+SELECT COUNT(*) as cnt FROM t WHERE value > 100;
+
+SELECT id, value, description FROM t;
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- HASH Index (Simple Column)
+-- ================================================================
+CREATE TABLE t(id INT, code VARCHAR(20), description TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_idx ON t USING hash(code);
+INSERT INTO t VALUES (1, 'CODE001', 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET description = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update indexed column - HASH index requires update, NOT HOT
+UPDATE t SET code = 'CODE002' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both - NOT HOT
+UPDATE t SET code = 'CODE003', description = 'changed' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Back to original code - NOT HOT (different hash bucket location)
+UPDATE t SET code = 'CODE001' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- HASH Index on Expression
+-- ================================================================
+CREATE TABLE t(id INT, email TEXT, data JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_lower_email_idx ON t USING HASH(lower(email));
+INSERT INTO t VALUES (1, 'Alice@Example.com', '{"status": "new"}');
+
+-- Update non-indexed field - should be HOT
+UPDATE t SET data = '{"status": "active"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update email with case change only (same lowercase) - should be HOT
+UPDATE t SET email = 'alice@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+-- Update email to different lowercase - NOT HOT
+UPDATE t SET email = 'bob@example.com' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Multiple HASH Indexes
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, status VARCHAR, value INT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+CREATE INDEX t_hash_status_idx ON t USING hash(status);
+INSERT INTO t VALUES (1, 'electronics', 'active', 100);
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update one indexed column - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update other indexed column - NOT HOT
+UPDATE t SET status = 'inactive' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+-- Update both indexed columns - NOT HOT
+UPDATE t SET category = 'videos', status = 'pending' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN vs HASH Comparison
+-- ================================================================
+CREATE TABLE t_brin(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE TABLE t_hash(id INT, value INT, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+
+CREATE INDEX t_brin_value_idx ON t_brin USING brin(value);
+CREATE INDEX t_hash_value_idx ON t_hash USING hash(value);
+
+INSERT INTO t_brin VALUES (1, 100, 'initial');
+INSERT INTO t_hash VALUES (1, 100, 'initial');
+
+-- Same update on both - different HOT behavior expected
+-- BRIN: might allow HOT (range summary unchanged)
+-- HASH: blocks HOT (hash bucket changed)
+UPDATE t_brin SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(1, 't_brin');
+-- Expected: 1 HOT (BRIN allows it for single row)
+
+UPDATE t_hash SET value = 150 WHERE id = 1;
+SELECT * FROM check_hot_updates(0, 't_hash');
+-- Expected: 0 HOT (HASH blocks it)
+
+DROP TABLE t_brin CASCADE;
+DROP TABLE t_hash CASCADE;
+
+
+-- ================================================================
+-- HASH Index with NULL Values
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'electronics', 'initial');
+
+-- Update indexed column to NULL - NOT HOT (hash value changed)
+UPDATE t SET category = NULL WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update indexed column from NULL to value - NOT HOT
+UPDATE t SET category = 'books' WHERE id = 1;
+SELECT * FROM check_hot_updates(0);
+-- Expected: 0 HOT
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- BRIN on JSONB Field
+-- ================================================================
+CREATE TABLE t(id INT, metrics JSONB)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+-- BRIN doesn't directly support JSONB, but we can test on expression
+CREATE INDEX t_brin_count_idx ON t USING brin(
+    CAST(metrics->>'count' AS INTEGER)
+);
+INSERT INTO t VALUES (1, '{"count": "100", "timestamp": "2024-01-01"}');
+
+-- Update non-indexed JSONB field - should be HOT
+UPDATE t SET metrics = '{"count": "100", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update indexed field - BRIN allows HOT for single row
+UPDATE t SET metrics = '{"count": "150", "timestamp": "2024-01-02"}' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (BRIN permits single-row updates)
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Mixed BRIN + HASH on Same Table
+-- ================================================================
+CREATE TABLE t(id INT, category VARCHAR, timestamp TIMESTAMP, price NUMERIC, data TEXT)
+    WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_brin_timestamp_idx ON t USING brin(timestamp);
+CREATE INDEX t_hash_category_idx ON t USING hash(category);
+INSERT INTO t VALUES (1, 'books', '2024-01-01 10:00:00', 29.99, 'initial');
+
+-- Update non-indexed column - should be HOT
+UPDATE t SET data = 'updated' WHERE id = 1;
+SELECT * FROM check_hot_updates(1);
+-- Expected: 1 HOT
+
+-- Update BRIN indexed column - allows HOT
+UPDATE t SET timestamp = '2024-01-02 10:00:00' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT
+
+-- Update HASH indexed column - blocks HOT
+UPDATE t SET category = 'videos' WHERE id = 1;
+SELECT * FROM check_hot_updates(2);
+-- Expected: 2 HOT (HASH blocks it)
+
+-- Update price (non-indexed) - should be HOT
+UPDATE t SET price = 39.99 WHERE id = 1;
+SELECT * FROM check_hot_updates(3);
+-- Expected: 3 HOT
+
+DROP TABLE t CASCADE;
+
+
+-- ================================================================
+-- Index both on a field in a JSONB document, and the document
+-- ================================================================
+CREATE TABLE t(id INT PRIMARY KEY, docs JSONB) WITH (autovacuum_enabled = off, fillfactor = 70);
+CREATE INDEX t_docs_idx ON t((docs->'name'));
+CREATE INDEX t_docs_col_idx ON t(docs);
+INSERT INTO t VALUES (1, '{"name": "john", "data": "some data"}');
+
+-- Update impacts index on whole docment attribute, can't go HOT
+UPDATE t SET docs='{"name": "john", "data": "some other data"}' WHERE id=1;
+SELECT * FROM check_hot_updates(0);
+
+DROP TABLE t CASCADE;
+
+
+-- Cleanup
+DROP FUNCTION check_hot_updates(int, text, text);
+DROP COLLATION case_insensitive;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 09e7f1d420e..637990ac252 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -399,6 +399,7 @@ CachedFunctionCompileCallback
 CachedFunctionDeleteCallback
 CachedFunctionHashEntry
 CachedFunctionHashKey
+CachedIndexDatum
 CachedPlan
 CachedPlanSource
 CallContext
-- 
2.51.2