diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index a4aaae876ec..3b1273580d0 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -233,8 +233,10 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> (
    If the <replaceable class="parameter">state_data_type</replaceable>
    is <type>internal</type>, it's usually also appropriate to provide the
    <literal>SERIALFUNC</literal> and <literal>DESERIALFUNC</literal> parameters so that
-   parallel aggregation is possible.  Note that the aggregate must also be
-   marked <literal>PARALLEL SAFE</literal> to enable parallel aggregation.
+   parallel aggregation is possible.  Note that in order to enable parallel
+   aggregation the aggregate must also be marked <literal>PARALLEL SAFE</literal>
+   and the type of each argument into the aggregate function must provide a valid
+   send and receive function.
   </para>
 
   <para>
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 93e5658a354..625aa8c8479 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -570,10 +570,14 @@ get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
 			/*
 			 * If we have any aggs with transtype INTERNAL then we must check
 			 * whether they have serialization/deserialization functions; if
-			 * not, we can't serialize partial-aggregation results.
+			 * not, we can't serialize partial-aggregation results.  Some
+			 * serial/deserial functions may require the use of the input
+			 * type's send and receive functions, so we'll also disable this
+			 * if any of the input types are missing those.
 			 */
 			else if (aggtranstype == INTERNALOID &&
-					 (!OidIsValid(aggserialfn) || !OidIsValid(aggdeserialfn)))
+					 (!OidIsValid(aggserialfn) || !OidIsValid(aggdeserialfn) ||
+					  !agg_transtypes_have_sendreceive_funcs(aggref)))
 				costs->hasNonSerial = true;
 		}
 
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 377a7ed6d0a..e8a2c93a9b7 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "access/htup_details.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_constraint_fn.h"
 #include "catalog/pg_type.h"
@@ -29,6 +30,8 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
 
 
 typedef struct
@@ -1832,6 +1835,38 @@ get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes)
 	return numArguments;
 }
 
+/*
+ * agg_transtypes_have_sendreceive_funcs
+ *		Returns true if all of 'aggref's argtypes have send and receive
+ *		functions.
+ */
+bool
+agg_transtypes_have_sendreceive_funcs(Aggref *aggref)
+{
+	ListCell *lc;
+
+	foreach(lc, aggref->aggargtypes)
+	{
+		HeapTuple	typeTuple;
+		Form_pg_type pt;
+		Oid type = lfirst_oid(lc);
+
+		typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
+		if (!HeapTupleIsValid(typeTuple))
+			elog(ERROR, "cache lookup failed for type %u", type);
+		pt = (Form_pg_type) GETSTRUCT(typeTuple);
+
+		if (!OidIsValid(pt->typsend) || !OidIsValid(pt->typreceive))
+		{
+			ReleaseSysCache(typeTuple);
+			return false;
+		}
+		ReleaseSysCache(typeTuple);
+	}
+
+	return true;
+}
+
 /*
  * resolve_aggregate_transtype
  *	Identify the transition state value's datatype for an aggregate call.
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 0e6adffe57f..7fcf25a8ec8 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -30,6 +30,8 @@ extern List *expand_grouping_sets(List *groupingSets, int limit);
 
 extern int	get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes);
 
+extern bool agg_transtypes_have_sendreceive_funcs(Aggref *aggref);
+
 extern Oid resolve_aggregate_transtype(Oid aggfuncid,
 							Oid aggtranstype,
 							Oid *inputTypes,
