diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 606c920b06..610f98aab1 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -176,7 +176,7 @@ static void FormPartitionKeyDatum(PartitionDispatch pd,
 								  Datum *values,
 								  bool *isnull);
 static int	get_partition_for_tuple(PartitionDispatch pd, Datum *values,
-									bool *isnull);
+									bool *isnull, bool check_cached);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 												  Datum *values,
 												  bool *isnull,
@@ -271,6 +271,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 	TupleTableSlot *myslot = NULL;
 	MemoryContext oldcxt;
 	ResultRelInfo *rri = NULL;
+	bool		check_cached = true;
 
 	/* use per-tuple context here to avoid leaking memory */
 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
@@ -310,7 +311,8 @@ ExecFindPartition(ModifyTableState *mtstate,
 		 * these values, error out.
 		 */
 		if (partdesc->nparts == 0 ||
-			(partidx = get_partition_for_tuple(dispatch, values, isnull)) < 0)
+			(partidx = get_partition_for_tuple(dispatch, values, isnull,
+											   check_cached)) < 0)
 		{
 			char	   *val_desc;
 
@@ -1236,11 +1238,16 @@ FormPartitionKeyDatum(PartitionDispatch pd,
  *		Finds partition of relation which accepts the partition key specified
  *		in values and isnull
  *
+ * If check_cached is true, this short-circuits a full-blown search across all
+ * the bounds, instead checking the tuple against the bound whose offset is
+ * cached in pd->partdesc.
+ *
  * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
  * found or -1 if none found.
  */
 static int
-get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
+get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull,
+						bool check_cached)
 {
 	int			bound_offset;
 	int			part_index = -1;
@@ -1248,10 +1255,18 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
 	PartitionDesc partdesc = pd->partdesc;
 	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 
+	/*
+	 * If the cached bound offset is valid, we check below if the that bound
+	 * is satisfied by the new tuple.  If it is, there's no need to perform a
+	 * search across all bounds.
+	 */
+	int			cached_off = partdesc->cached_bound_offset;
+
 	/* Route as appropriate based on partitioning strategy. */
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_HASH:
+			Assert(cached_off < 0);
 			{
 				uint64		rowHash;
 
@@ -1272,14 +1287,33 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
 			}
 			else
 			{
-				bool		equal = false;
-
-				bound_offset = partition_list_bsearch(key->partsupfunc,
-													  key->partcollation,
-													  boundinfo,
-													  values[0], &equal);
-				if (bound_offset >= 0 && equal)
-					part_index = boundinfo->indexes[bound_offset];
+				if (cached_off >= 0 && check_cached)
+				{
+					Datum	bound_datum = boundinfo->datums[cached_off][0];
+					int32	cmpval;
+
+					cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+															 key->partcollation[0],
+															 bound_datum,
+															 values[0]));
+					if (cmpval == 0)
+						part_index = boundinfo->indexes[cached_off];
+				}
+
+				if (part_index < 0)
+				{
+					bool		equal = false;
+
+					bound_offset = partition_list_bsearch(key->partsupfunc,
+														  key->partcollation,
+														  boundinfo,
+														  values[0], &equal);
+					if (bound_offset >= 0 && equal)
+					{
+						part_index = boundinfo->indexes[bound_offset];
+						partdesc->cached_bound_offset = bound_offset;
+					}
+				}
 			}
 			break;
 
@@ -1304,20 +1338,56 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
 
 				if (!range_partkey_has_null)
 				{
-					bound_offset = partition_range_datum_bsearch(key->partsupfunc,
-																 key->partcollation,
-																 boundinfo,
-																 key->partnatts,
-																 values,
-																 &equal);
+					if (cached_off >= 0 && check_cached)
+					{
+						Datum   *bound_datums = boundinfo->datums[cached_off];
+						PartitionRangeDatumKind *bound_kind = boundinfo->kind[cached_off];
+						int32	cmpval;
+
+						/* Check if the value is above the low bound */
+						cmpval = partition_rbound_datum_cmp(key->partsupfunc,
+															key->partcollation,
+															bound_datums,
+															bound_kind,
+															values,
+															key->partnatts);
+						if (cmpval == 0)
+							part_index = boundinfo->indexes[cached_off + 1];
+						else if (cmpval < 0 && cached_off + 1 < boundinfo->ndatums)
+						{
+							/* Check if the value is below the high bound */
+							bound_datums = boundinfo->datums[cached_off + 1];
+							bound_kind = boundinfo->kind[cached_off + 1];
+							cmpval = partition_rbound_datum_cmp(key->partsupfunc,
+																key->partcollation,
+																bound_datums,
+																bound_kind,
+																values,
+																key->partnatts);
+
+							if (cmpval > 0)
+								part_index = boundinfo->indexes[cached_off + 1];
+						}
+					}
 
-					/*
-					 * The bound at bound_offset is less than or equal to the
-					 * tuple value, so the bound at offset+1 is the upper
-					 * bound of the partition we're looking for, if there
-					 * actually exists one.
-					 */
-					part_index = boundinfo->indexes[bound_offset + 1];
+					if (part_index < 0)
+					{
+						bound_offset = partition_range_datum_bsearch(key->partsupfunc,
+																	 key->partcollation,
+																	 boundinfo,
+																	 key->partnatts,
+																	 values,
+																	 &equal);
+
+						/*
+						 * The bound at bound_offset is less than or equal to the
+						 * tuple value, so the bound at offset+1 is the upper
+						 * bound of the partition we're looking for, if there
+						 * actually exists one.
+						 */
+						part_index = boundinfo->indexes[bound_offset + 1];
+						partdesc->cached_bound_offset = bound_offset;
+					}
 				}
 			}
 			break;
diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c
index 9a9d6a9643..e6ec01de71 100644
--- a/src/backend/partitioning/partdesc.c
+++ b/src/backend/partitioning/partdesc.c
@@ -285,6 +285,8 @@ RelationBuildPartitionDesc(Relation rel, bool omit_detached)
 		MemoryContextAllocZero(new_pdcxt, sizeof(PartitionDescData));
 	partdesc->nparts = nparts;
 	partdesc->detached_exist = detached_exist;
+	partdesc->cached_bound_offset = -1;
+
 	/* If there are no partitions, the rest of the partdesc can stay zero */
 	if (nparts > 0)
 	{
diff --git a/src/include/partitioning/partdesc.h b/src/include/partitioning/partdesc.h
index 0792f48507..9df28ae14f 100644
--- a/src/include/partitioning/partdesc.h
+++ b/src/include/partitioning/partdesc.h
@@ -36,6 +36,9 @@ typedef struct PartitionDescData
 								 * the corresponding 'oids' element belongs to
 								 * a leaf partition or not */
 	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
+	int			cached_bound_offset; /* offset of the bound datum most
+									  * recently chosen by
+									  * get_partition_for_tuple() */
 } PartitionDescData;
 
 
