diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ad9c911542d18a41b15c59f0e281b3db9593921e..d6d20fde9af6813c4cc8ec1465eee02ad8670d99 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -227,7 +227,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 		FdwRoutine *fdwroutine;
 		bool		ok = false;
 
-		fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(onerel));
+		fdwroutine = GetFdwRoutineForRelation(onerel, false);
 
 		if (fdwroutine->AnalyzeForeignTable != NULL)
 			ok = fdwroutine->AnalyzeForeignTable(onerel,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 6ebffadef198f07feb68db18cdbaacfae03058a9..63478cd12ad311f692b94cab86c0093ec7c2a348 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -160,7 +160,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * Acquire function pointers from the FDW's handler, and init fdw_state.
 	 */
-	fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(currentRelation));
+	fdwroutine = GetFdwRoutineForRelation(currentRelation, true);
 	scanstate->fdwroutine = fdwroutine;
 	scanstate->fdw_state = NULL;
 
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index bfcc323924aaebe0e48a1a258929085f81fe0bca..2b75f73e08fb42b6793ed8e61cbac6c814e92b69 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -23,6 +23,8 @@
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 
@@ -352,6 +354,50 @@ GetFdwRoutineByRelId(Oid relid)
 	return GetFdwRoutine(fdwhandler);
 }
 
+/*
+ * GetFdwRoutineForRelation - look up the handler of the foreign-data wrapper
+ * for the given foreign table, and retrieve its FdwRoutine struct.
+ *
+ * This function is preferred over GetFdwRoutineByRelId because it caches
+ * the data in the relcache entry, saving a number of catalog lookups.
+ *
+ * If makecopy is true then the returned data is freshly palloc'd in the
+ * caller's memory context.  Otherwise, it's a pointer to the relcache data,
+ * which will be lost in any relcache reset --- so don't rely on it long.
+ */
+FdwRoutine *
+GetFdwRoutineForRelation(Relation relation, bool makecopy)
+{
+	FdwRoutine *fdwroutine;
+	FdwRoutine *cfdwroutine;
+
+	if (relation->rd_fdwroutine == NULL)
+	{
+		/* Get the info by consulting the catalogs and the FDW code */
+		fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation));
+
+		/* Save the data for later reuse in CacheMemoryContext */
+		cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
+														sizeof(FdwRoutine));
+		memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine));
+		relation->rd_fdwroutine = cfdwroutine;
+
+		/* Give back the locally palloc'd copy regardless of makecopy */
+		return fdwroutine;
+	}
+
+	/* We have valid cached data --- does the caller want a copy? */
+	if (makecopy)
+	{
+		fdwroutine = (FdwRoutine *) palloc(sizeof(FdwRoutine));
+		memcpy(fdwroutine, relation->rd_fdwroutine, sizeof(FdwRoutine));
+		return fdwroutine;
+	}
+
+	/* Only a short-lived reference is needed, so just hand back cached copy */
+	return relation->rd_fdwroutine;
+}
+
 
 /*
  * deflist_to_tuplestore - Helper function to convert DefElem list to
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 0545f958f67aee26d89891e1dfcc727a492d47b3..86d5bb71b0a0a3811a2d5fa2f0682b8964ff037d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -410,9 +410,6 @@ set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Mark rel with estimated output rows, width, etc */
 	set_foreign_size_estimates(root, rel);
 
-	/* Get FDW routine pointers for the rel */
-	rel->fdwroutine = GetFdwRoutineByRelId(rte->relid);
-
 	/* Let FDW adjust the size estimates, if it can */
 	rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);
 }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index bff7aff593d89b5e2a6cbb2486b5c008fee3a3d5..954666ce04ce7b5fae849329ea1b67c34332878a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -26,6 +26,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -67,6 +68,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  *	min_attr	lowest valid AttrNumber
  *	max_attr	highest valid AttrNumber
  *	indexlist	list of IndexOptInfos for relation's indexes
+ *	fdwroutine	if it's a foreign table, the FDW function pointers
  *	pages		number of pages
  *	tuples		number of tuples
  *
@@ -374,6 +376,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 	rel->indexlist = indexinfos;
 
+	/* Grab the fdwroutine info using the relcache, while we have it */
+	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
+	else
+		rel->fdwroutine = NULL;
+
 	heap_close(relation, NoLock);
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ba03dfcbb2df848a0f061fffa68b8b052f9c87f3..5b1d1e5b10a53d7647efbb24b40b91ca0d5195c3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1846,6 +1846,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rd_fdwroutine)
+		pfree(relation->rd_fdwroutine);
 	pfree(relation);
 }
 
@@ -4410,7 +4412,7 @@ load_relcache_init_file(bool shared)
 		 * format is complex and subject to change).  They must be rebuilt if
 		 * needed by RelationCacheInitializePhase3.  This is not expected to
 		 * be a big performance hit since few system catalogs have such. Ditto
-		 * for index expressions, predicates, and exclusion info.
+		 * for index expressions, predicates, exclusion info, and FDW info.
 		 */
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
@@ -4420,6 +4422,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_exclops = NULL;
 		rel->rd_exclprocs = NULL;
 		rel->rd_exclstrats = NULL;
+		rel->rd_fdwroutine = NULL;
 
 		/*
 		 * Reset transient-state fields in the relcache entry
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 13dcbfdf8c73828b35d130033a8c68038fa77b97..562d5412df7021b01c8c71769b40007c9ebd349c 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -96,5 +96,6 @@ typedef struct FdwRoutine
 /* Functions in foreign/foreign.c */
 extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
 extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
+extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
 
 #endif   /* FDWAPI_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 06e1531e9a3385b99675c378d5834c1756a13150..a4daf772e57c8e1ad02a0ecb7bfa8c09b265f53b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -165,6 +165,17 @@ typedef struct RelationData
 	void	   *rd_amcache;		/* available for use by index AM */
 	Oid		   *rd_indcollation;	/* OIDs of index collations */
 
+	/*
+	 * foreign-table support
+	 *
+	 * rd_fdwroutine must point to a single memory chunk palloc'd in
+	 * CacheMemoryContext.  It will be freed and reset to NULL on a relcache
+	 * reset.
+	 */
+
+	/* use "struct" here to avoid needing to include fdwapi.h: */
+	struct FdwRoutine *rd_fdwroutine;	/* cached function pointers, or NULL */
+
 	/*
 	 * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new
 	 * version of a table, we need to make any toast pointers inserted into it