diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index 3547cdba56e..39c801a0a52 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -20,6 +20,8 @@
 #include "common/int.h"
 #include "common/pg_lzcompress.h"
 #include "utils/expandeddatum.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 
 static struct varlena *toast_fetch_datum(struct varlena *attr);
@@ -29,6 +31,25 @@ static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
+bool enable_toast_cache = false;
+
+HTAB *toastCache = NULL;
+MemoryContext ToastCacheContext = NULL;
+
+typedef struct toast_cache_key {
+	Oid toastrelid;
+	Oid valueid;
+} toast_cache_key;
+
+typedef struct toast_cache_entry {
+	toast_cache_key key;
+	int32	size;
+	void   *data;
+} toast_cache_entry;
+
+static toast_cache_entry *toast_cache_lookup(Oid toastrelid, Oid valueid);
+static void toast_cache_add_entry(Oid toastrelid, Oid valueid, struct varlena *attr);
+
 /* ----------
  * detoast_external_attr -
  *
@@ -346,6 +367,7 @@ toast_fetch_datum(struct varlena *attr)
 	struct varlena *result;
 	struct varatt_external toast_pointer;
 	int32		attrsize;
+	toast_cache_entry *entry;
 
 	if (!VARATT_IS_EXTERNAL_ONDISK(attr))
 		elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums");
@@ -366,6 +388,16 @@ toast_fetch_datum(struct varlena *attr)
 		return result;			/* Probably shouldn't happen, but just in
 								 * case. */
 
+	/* try lookup in the toast cache */
+	entry = toast_cache_lookup(toast_pointer.va_toastrelid,
+							   toast_pointer.va_valueid);
+
+	if (entry)
+	{
+		memcpy(VARDATA(result), entry->data, attrsize);
+		return result;
+	}
+
 	/*
 	 * Open the toast relation and its indexes
 	 */
@@ -378,6 +410,10 @@ toast_fetch_datum(struct varlena *attr)
 	/* Close toast table */
 	table_close(toastrel, AccessShareLock);
 
+	toast_cache_add_entry(toast_pointer.va_toastrelid,
+						  toast_pointer.va_valueid,
+						  result);
+
 	return result;
 }
 
@@ -644,3 +680,93 @@ toast_datum_size(Datum value)
 	}
 	return result;
 }
+
+static bool
+toast_cache_init(void)
+{
+	HASHCTL		ctl;
+
+	if (!enable_toast_cache)
+		return false;
+
+	/* already initialized */
+	if (toastCache)
+		return true;
+
+	/* FIXME should really be per query */
+	ToastCacheContext = AllocSetContextCreate(TopMemoryContext,
+											  "TOAST cache context",
+											  ALLOCSET_DEFAULT_SIZES);
+
+	/* Make a new hash table for the cache */
+	ctl.keysize = sizeof(toast_cache_key);
+	ctl.entrysize = sizeof(toast_cache_entry);
+	ctl.hcxt = ToastCacheContext;
+
+	toastCache = hash_create("TOAST cache",
+							 128, &ctl,
+							 HASH_ELEM | HASH_CONTEXT | HASH_BLOBS);
+
+	return true;
+}
+
+static toast_cache_entry *
+toast_cache_lookup(Oid toastrelid, Oid valueid)
+{
+	toast_cache_key		key;
+	toast_cache_entry  *entry;
+
+	/* make sure cache initialized */
+	if (!toast_cache_init())
+		return NULL;
+
+	key.toastrelid = toastrelid;
+	key.valueid = valueid;
+
+	entry = (toast_cache_entry *) hash_search(toastCache, &key,
+											  HASH_FIND, NULL);
+
+	return entry;
+}
+
+static void
+toast_cache_add_entry(Oid toastrelid, Oid valueid, struct varlena *attr)
+{
+	bool				found = false;
+	toast_cache_key		key;
+	toast_cache_entry  *entry;
+
+	/* make sure cache initialized */
+	if (!toast_cache_init())
+		return;
+
+	key.toastrelid = toastrelid;
+	key.valueid = valueid;
+
+	entry = (toast_cache_entry *) hash_search(toastCache, &key,
+											  HASH_ENTER, &found);
+
+	Assert(!found);
+
+	entry->size = VARSIZE_ANY_EXHDR(attr);
+	entry->data = MemoryContextAlloc(ToastCacheContext, entry->size);
+	memcpy(entry->data, VARDATA_ANY(attr), entry->size);
+}
+
+static void
+toast_cache_destroy(void)
+{
+	if (!toastCache)
+		return;
+
+	MemoryContextDelete(ToastCacheContext);
+
+	toastCache = NULL;
+	ToastCacheContext = NULL;
+}
+
+void
+AtEOXact_ToastCache(void)
+{
+	toast_cache_destroy();
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 70ab6e27a13..2cae3582026 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -21,6 +21,7 @@
 #include <unistd.h>
 
 #include "access/commit_ts.h"
+#include "access/detoast.h"
 #include "access/multixact.h"
 #include "access/parallel.h"
 #include "access/subtrans.h"
@@ -2381,6 +2382,7 @@ CommitTransaction(void)
 	AtEOXact_Snapshot(true, false);
 	AtEOXact_ApplyLauncher(true);
 	AtEOXact_LogicalRepWorkers(true);
+	AtEOXact_ToastCache();
 	pgstat_report_xact_timestamp(0);
 
 	ResourceOwnerDelete(TopTransactionResourceOwner);
@@ -2671,6 +2673,7 @@ PrepareTransaction(void)
 	/* we treat PREPARE as ROLLBACK so far as waking workers goes */
 	AtEOXact_ApplyLauncher(false);
 	AtEOXact_LogicalRepWorkers(false);
+	AtEOXact_ToastCache();
 	pgstat_report_xact_timestamp(0);
 
 	CurrentResourceOwner = NULL;
@@ -2888,6 +2891,7 @@ AbortTransaction(void)
 		AtEOXact_PgStat(false, is_parallel_worker);
 		AtEOXact_ApplyLauncher(false);
 		AtEOXact_LogicalRepWorkers(false);
+		AtEOXact_ToastCache();
 		pgstat_report_xact_timestamp(0);
 	}
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 93ded31ed92..ceb3d3b953c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -27,6 +27,7 @@
 #endif
 
 #include "access/commit_ts.h"
+#include "access/detoast.h"
 #include "access/gin.h"
 #include "access/slru.h"
 #include "access/toast_compression.h"
@@ -1015,6 +1016,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_toast_cache", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables caching of detoasted values."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_toast_cache,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
index 12d8cdb356a..1a662c81388 100644
--- a/src/include/access/detoast.h
+++ b/src/include/access/detoast.h
@@ -79,4 +79,7 @@ extern Size toast_raw_datum_size(Datum value);
  */
 extern Size toast_datum_size(Datum value);
 
+extern PGDLLIMPORT bool enable_toast_cache;
+extern void AtEOXact_ToastCache(void);
+
 #endif							/* DETOAST_H */
